Play 框架秘籍(二)
在本章中,我们将介绍以下菜谱:使用 Spring 进行依赖注入使用 Guice 进行依赖注入利用 MongoDB利用 MongoDB 和 GridFS利用 Redis将 Play 应用程序与 Amazon S3 集成将 Play 应用程序与 Typesafe Slick 集成利用 play-mailer集成 Bootstrap 和 WebJars在本章中,我们将探讨如何利用 Play 和其他第三方
原文:
zh.annas-archive.org/md5/3898a199ef873c2cd80ef5c3269070bb
译者:飞龙
第三章:利用模块
在本章中,我们将介绍以下菜谱:
-
使用 Spring 进行依赖注入
-
使用 Guice 进行依赖注入
-
利用 MongoDB
-
利用 MongoDB 和 GridFS
-
利用 Redis
-
将 Play 应用程序与 Amazon S3 集成
-
将 Play 应用程序与 Typesafe Slick 集成
-
利用 play-mailer
-
集成 Bootstrap 和 WebJars
简介
在本章中,我们将探讨如何利用 Play 和其他第三方模块来处理现代 Web 应用程序的常见功能。随着 Web 应用程序和 Web 应用程序框架的成熟和演变,作为核心 Web 应用程序框架一部分的模块化和可扩展系统变得越来越重要。这可以通过 Play Framework 2.0 轻松实现。
使用 Spring 进行依赖注入
对于这个菜谱,我们将探讨如何将流行的 Spring 框架与 Play 应用程序集成。我们将使用 Spring 通过 Play 控制器和服务类进行 bean 实例化和注入。
如何做到这一点…
对于 Java,我们需要采取以下步骤:
-
启用热重载运行
foo_java
应用程序:<span class="strong"><strong> activator "~run"</strong></span>
-
在
build.sbt
中将 Spring 声明为项目依赖项:"org.springframework" % "spring-context" % "3.2.2.RELEASE", "org.springframework" % "spring-aop" % "3.2.2.RELEASE", "org.springframework" % "spring-expression" % "3.2.2.RELEASE"
-
在
foo_java/app/controllers/AdminController.java
中创建一个新的管理控制器,代码如下:package controllers; import play.*; import play.mvc.*; import org.springframework.beans.factory.annotation.Autowired; import services.AdminService; @org.springframework.stereotype.Controller public class AdminController { @Autowired private AdminService adminService; public Result index() { return play.mvc.Controller.ok("This is an admin-only resource: " + adminService.getFoo()); } }
-
在
foo_java/app/services/AdminServices.java
中创建一个管理服务接口类,并在foo_java/app/services/AdminServicesImpl.java
中创建一个模拟管理服务实现类,内容如下:// AdminService.java package services; public interface AdminService { String getFoo(); } // AdminServiceImpl.java package services; import org.springframework.stereotype.Service; @Service public class AdminServiceImpl implements AdminService { @Override public String getFoo() { return "foo"; } }
-
在
foo_java/conf/routes
中为新增的操作添加一个新的路由条目:GET /admins @controllers.AdminController.index
-
在
foo_java/app/Global.java
文件中添加一个Global
设置类,内容如下:import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import play.Application; import play.GlobalSettings; public class Global extends GlobalSettings { private ApplicationContext ctx; @Override public void onStart(Application app) { ctx = new AnnotationConfigApplicationContext(SpringConfig.class); } @Override public <A> A getControllerInstance(Class<A> clazz) { return ctx.getBean(clazz); } }
-
在
foo_java/app/SpringConfig.java
中添加 Spring 配置类,内容如下:import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @Configuration @ComponentScan({"controllers", "services"}) public class SpringConfig { }
-
请求我们的新路由并检查响应体以确认:
<span class="strong"><strong> $ curl -v http://localhost:9000/admins</strong></span> <span class="strong"><strong> * Hostname was NOT found in DNS cache</strong></span> <span class="strong"><strong> * Trying ::1...</strong></span> <span class="strong"><strong> * Connected to localhost (::1) port 9000 (#0)</strong></span> <span class="strong"><strong> > GET /admins HTTP/1.1</strong></span> <span class="strong"><strong> > User-Agent: curl/7.37.1</strong></span> <span class="strong"><strong> > Host: localhost:9000</strong></span> <span class="strong"><strong> > Accept: */*</strong></span> <span class="strong"><strong> ></strong></span> <span class="strong"><strong> < HTTP/1.1 200 OK</strong></span> <span class="strong"><strong> < Content-Type: text/plain; charset=utf-8</strong></span> <span class="strong"><strong> < Content-Length: 35</strong></span> <span class="strong"><strong> <</strong></span> <span class="strong"><strong> * Connection #0 to host localhost left intact</strong></span> <span class="strong"><strong> This is an admin-only resource: foo%</strong></span>
对于 Scala,我们需要采取以下步骤:
-
启用热重载运行
foo_scala
应用程序:<span class="strong"><strong> activator "~run"</strong></span>
-
在
build.sbt
中将 Spring 声明为项目依赖项:"org.springframework" % "spring-context" % "3.2.2.RELEASE"
-
在
foo_scala/app/controllers/AdminController.scala
中创建一个新的管理控制器,内容如下:package controllers import play.api.mvc.{Action, Controller} import services.AdminService class AdminController(implicit adminService: AdminService) extends Controller { def index = Action { Ok("This is an admin-only resource: %s".format(adminService.foo)) } }
-
在
foo_scala/app/services/AdminServices.scala
中创建一个管理服务类,内容如下:package services class AdminService { def foo = "foo" }
-
在
foo_scala/conf/routes
中为新增的操作添加一个新的路由条目:GET /admins @controllers.AdminController.index
-
在
foo_scala/app/Global.scala
中添加一个Global
设置类,内容如下:import org.springframework.context.ApplicationContext import org.springframework.context.annotation.AnnotationConfigApplicationContext object Global extends play.api.GlobalSettings { private val ctx: ApplicationContext = new AnnotationConfigApplicationContext(classOf[SpringConfig]) override def getControllerInstanceA: A = { return ctx.getBean(clazz) } }
-
添加 Spring 配置类到
foo_scala/app/SpringConfig.scala
,内容如下:import org.springframework.context.annotation.Configuration import org.springframework.context.annotation.Bean import controllers._ import services._ @Configuration class SpringConfig { @Bean implicit def adminService: AdminService = new AdminService @Bean def adminController: AdminController = new AdminController }
-
请求我们的新路由并检查响应体以确认:
<span class="strong"><strong> $ curl -v http://0.0.0.0:9000/admins</strong></span> <span class="strong"><strong> * Hostname was NOT found in DNS cache</strong></span> <span class="strong"><strong> * Trying 0.0.0.0...</strong></span> <span class="strong"><strong> * Connected to 0.0.0.0 (127.0.0.1) port 9000 (#0)</strong></span> <span class="strong"><strong> > GET /admins HTTP/1.1</strong></span> <span class="strong"><strong> > User-Agent: curl/7.37.1</strong></span> <span class="strong"><strong> > Host: 0.0.0.0:9000</strong></span> <span class="strong"><strong> > Accept: */*</strong></span> <span class="strong"><strong> ></strong></span> <span class="strong"><strong> < HTTP/1.1 200 OK</strong></span> <span class="strong"><strong> < Content-Type: text/plain; charset=utf-8</strong></span> <span class="strong"><strong> < Content-Length: 35</strong></span> <span class="strong"><strong> <</strong></span> <span class="strong"><strong> * Connection #0 to host 0.0.0.0 left intact</strong></span> <span class="strong"><strong> This is an admin-only resource: foo%</strong></span>
它是如何工作的…
在这个菜谱中,我们配置了我们的 Play 应用程序,使用 Spring 在我们的控制器和服务类中进行依赖注入。我们在Global
设置文件中配置了 Spring,并加载了SpringConfig
类,它将包含我们的 Spring 特定配置。
使用 Guice 进行依赖注入
对于这个菜谱,我们将探讨如何将 Google Guice 与 Play 应用程序集成。我们将使用 Guice 进行 Bean 实例化和通过 Play 控制器和服务类进行注入。
如何操作…
对于 Java,我们需要采取以下步骤:
-
启用 Hot-Reloading 运行
foo_java
应用程序:<span class="strong"><strong> activator "~run"</strong></span>
-
在
build.sbt
中将guice
模块声明为项目依赖项:"com.google.inject" % "guice" % "3.0"
-
通过修改
Global
设置类的内容来配置 Guice:import com.google.inject.AbstractModule; import com.google.inject.Guice; import com.google.inject.Injector; import com.google.inject.Singleton; import play.GlobalSettings; import services.*; public class Global extends GlobalSettings { private Injector injector = Guice.createInjector(new AbstractModule() { @Override protected void configure() { bind(CategoryService.class).to( CategoryServiceImpl.class).in(Singleton.class); } }); @Override public <T> T getControllerInstance(Class<T> clazz) { return injector.getInstance(clazz); } }
-
在
foo_java/app/controllers/CategoryController.java
中创建一个分类控制器,通过添加以下内容:package controllers; import com.google.inject.Inject; import play.libs.Json; import play.mvc.Controller; import play.mvc.Result; import services.CategoryService; public class CategoryController extends Controller { @Inject private CategoryService categoryService; public Result index() { return ok(Json.toJson(categoryService.list())); } }
-
在
foo_java/app/services/CategoryService.java
中创建一个分类服务接口,通过添加以下内容:package services; import java.util.List; public interface CategoryService { List<String> list(); }
-
在
foo_java/app/services/CategoryServicesImpl.java
中创建一个分类服务实现类,通过添加以下内容:package services; import java.util.Arrays; import java.util.List; public class CategoryServiceImpl implements CategoryService { @Override public List<String> list() { return Arrays.asList(new String[] {"Manager", "Employee", "Contractor"}); } }
-
在
foo_java/conf/routes
中为新增的操作添加一个新的路由条目:GET /categories @controllers.CategoryController.index
-
请求我们的新路由并检查响应头以确认我们对 HTTP 响应头的修改:
$ curl -v http://localhost:9000/categories * Hostname was NOT found in DNS cache * Trying ::1... * Connected to localhost (::1) port 9000 (#0) > GET /categories HTTP/1.1 > User-Agent: curl/7.37.1 > Host: localhost:9000 > Accept: */* > < HTTP/1.1 200 OK < Content-Type: application/json; charset=utf-8 < Content-Length: 35 < * Connection #0 to host localhost left intact ["Manager","Employee","Contractor"]%
对于 Scala,我们需要采取以下步骤:
-
启用 Hot-Reloading 运行
foo_scala
应用程序:<span class="strong"><strong> activator "~run"</strong></span>
-
在
build.sbt
中将securesocial
模块声明为项目依赖项:"com.google.inject" % "guice" % "3.0"
-
通过修改
Global
设置类的内容来配置 Guice:import com.google.inject.{Guice, AbstractModule} import play.api.GlobalSettings import services._ object Global extends GlobalSettings { val injector = Guice.createInjector(new AbstractModule { protected def configure() { bind(classOf[CategoryService]).to(classOf[CategoryServiceImpl]) } }) override def getControllerInstanceA: A = { injector.getInstance(controllerClass) } }
-
在
foo_scala/app/controllers/CategoryController.scala
中创建一个分类控制器,通过添加以下内容:package controllers import play.api.mvc._ import play.api.libs.json.Json._ import com.google.inject._ import services._ @Singleton class CategoryController @Inject()(categoryService: CategoryService) extends Controller { def index = Action { Ok(toJson(categoryService.list)) } }
-
在
foo_scala/app/services/CategoryService.scala
中创建一个分类服务,通过添加以下内容:package services trait CategoryService { def list: Seq[String] } class CategoryServiceImpl extends CategoryService { override def list: Seq[String] = Seq("Manager", "Employee", "Contractor") }
-
在
foo_scala/conf/routes
中为新增的操作添加一个新的路由条目:GET /categories @controllers.CategoryController.index
-
请求我们的新路由并检查响应头以确认我们对 HTTP 响应头的修改:
$ curl -v http://localhost:9000/categories * Hostname was NOT found in DNS cache * Trying ::1... * Connected to localhost (::1) port 9000 (#0) > GET /categories HTTP/1.1 > User-Agent: curl/7.37.1 > Host: localhost:9000 > Accept: */* > < HTTP/1.1 200 OK < Content-Type: application/json; charset=utf-8 < Content-Length: 35 < * Connection #0 to host localhost left intact ["Manager","Employee","Contractor"]%
它是如何工作的…
在这个菜谱中,我们配置了我们的 Play 应用程序,使用 Google Guice 在我们的控制器和服务类中实现依赖注入。我们在Global
设置文件中配置了 Guice,它将包含我们的 Guice 特定配置。
利用 MongoDB
对于这个菜谱,我们将探讨如何在 Play 应用程序中利用流行的 NoSQL 库 MongoDB。MongoDB 是最广泛使用的 NoSQL 数据库之一,它无疑已经成为许多现代 Web 应用程序的数据存储的有效选择。我们将使用 Scala 模块,play-plugins-salat,这是一个使用官方 MongoDB Scala 驱动程序 Casbah 的对象关系映射工具。这将是一个仅限 Scala 的菜谱。
有关 Casbah 的更多信息,请参阅github.com/mongodb/casbah
。
如何操作…
让我们采取以下步骤:
-
启用 Hot-Reloading 运行
foo_scala
应用程序:<span class="strong"><strong> activator "~run"</strong></span>
-
在
build.sbt
中将 play-plugins-salat 声明为项目依赖项:"se.radley" %% "play-plugins-salat" % "1.5.0"
-
在
build.sbt
中添加额外的 salat 和 MongoDB 指令:import play.PlayImport.PlayKeys._ import play.twirl.sbt.Import.TwirlKeys routesImport += "se.radley.plugin.salat.Binders._" TwirlKeys.templateImports += "org.bson.types.ObjectId"
-
在
foo_scala/conf/play.plugins
中声明 salat 插件:500:se.radley.plugin.salat.SalatPlugin
-
在
foo_scala/conf/application.conf
中声明 MongoDB 实例信息:mongodb.default.db = "cookbookdb"
-
通过添加以下内容修改
foo_scala/app/controllers/WarehouseController.scala
:package controllers import models._ import play.api.libs.json._ import play.api.mvc.{BodyParsers, Action, Controller} import se.radley.plugin.salat.Binders.ObjectId object WarehouseController extends Controller { implicit val objectIdReads = se.radley.plugin.salat.Binders.objectIdReads implicit val objectIdWrites = se.radley.plugin.salat.Binders.objectIdWrites implicit val warehouseWrites = Json.writes[Warehouse] implicit val warehouseReads = Json.reads[Warehouse] def index = Action { val list = Warehouse.list Ok(Json.toJson(list)) } def create = Action(BodyParsers.parse.json) { implicit request => val post = request.body.validate[Warehouse] post.fold( errors => { BadRequest(Json.obj("status" ->"error", "message" -> JsError.toFlatJson(errors))) }, warehouse => { Warehouse.create(warehouse) Created(Json.toJson(warehouse)) } ) } }
-
将新添加的操作的新路由添加到
foo_scala/conf/routes
:GET /warehouses controllers.WarehouseController.index POST /warehouses controllers.WarehouseController.create
-
将仓库模型的集合映射添加到
foo_scala/app/models/Warehouse.scala
:package models import play.api.Play.current import com.mongodb.casbah.commons.MongoDBObject import com.novus.salat.dao._ import se.radley.plugin.salat._ import se.radley.plugin.salat.Binders._ import mongoContext._ case class Warehouse(id: Option[ObjectId] = Some(new ObjectId), name: String, location: String) object Warehouse extends ModelCompanion[Warehouse, ObjectId] { val dao = new SalatDAOWarehouse, ObjectId) {} def list = dao.find(ref = MongoDBObject()).toList def create(w: Warehouse) = dao.save(w) }
-
将 Mongo 上下文添加到
foo_scala/app/models/mongoContext.scala
:package models import com.novus.salat.dao._ import com.novus.salat.annotations._ import com.mongodb.casbah.Imports._ import play.api.Play import play.api.Play.current package object mongoContext { implicit val context = { val context = new Context { val name = "global" override val typeHintStrategy = StringTypeHintStrategy(when = TypeHintFrequency.WhenNecessary, typeHint = "_t") } context.registerGlobalKeyOverride(remapThis = "id", toThisInstead = "_id") context.registerClassLoader(Play.classloader) context } }
-
通过使用
curl
访问仓库 post 端点来添加新的仓库记录:<span class="strong"><strong> $ curl -v -X POST http://localhost:9000/warehouses --header "Content-type: application/json" --data '{"name":"Warehouse A", "location":"Springfield"}'</strong></span> <span class="strong"><strong> * Hostname was NOT found in DNS cache</strong></span> <span class="strong"><strong> * Trying ::1...</strong></span> <span class="strong"><strong> * Connected to localhost (::1) port 9000 (#0)</strong></span> <span class="strong"><strong> > POST /warehouses HTTP/1.1</strong></span> <span class="strong"><strong> > User-Agent: curl/7.37.1</strong></span> <span class="strong"><strong> > Host: localhost:9000</strong></span> <span class="strong"><strong> > Accept: */*</strong></span> <span class="strong"><strong> > Content-type: application/json</strong></span> <span class="strong"><strong> > Content-Length: 48</strong></span> <span class="strong"><strong> ></strong></span> <span class="strong"><strong> * upload completely sent off: 48 out of 48 bytes</strong></span> <span class="strong"><strong> < HTTP/1.1 201 Created</strong></span> <span class="strong"><strong> < Content-Type: application/json; charset=utf-8</strong></span> <span class="strong"><strong> < Content-Length: 47</strong></span> <span class="strong"><strong> <</strong></span> <span class="strong"><strong> * Connection #0 to host localhost left intact</strong></span> <span class="strong"><strong> {"name":"Warehouse A","location":"Springfield"}</strong></span>
-
通过使用
curl
访问仓库索引端点来查看所有仓库记录:<span class="strong"><strong> $ curl -v http://localhost:9000/warehouses</strong></span> <span class="strong"><strong> * Hostname was NOT found in DNS cache</strong></span> <span class="strong"><strong> * Trying ::1...</strong></span> <span class="strong"><strong> * Connected to localhost (::1) port 9000 (#0)</strong></span> <span class="strong"><strong> > GET /warehouses HTTP/1.1</strong></span> <span class="strong"><strong> > User-Agent: curl/7.37.1</strong></span> <span class="strong"><strong> > Host: localhost:9000</strong></span> <span class="strong"><strong> > Accept: */*</strong></span> <span class="strong"><strong> ></strong></span> <span class="strong"><strong> < HTTP/1.1 200 OK</strong></span> <span class="strong"><strong> < Content-Type: application/json; charset=utf-8</strong></span> <span class="strong"><strong> < Content-Length: 241</strong></span> <span class="strong"><strong> <</strong></span> <span class="strong"><strong> * Connection #0 to host localhost left intact</strong></span> <span class="strong"><strong> [{"id":"5490fde9e0820cf6df38584c","name":"Warehouse A","location":"Springfield"}]%</strong></span>
它是如何工作的…
在这个菜谱中,我们创建了一个新的 URL 路由和操作,用于从 MongoDB 实例中插入和检索仓库记录。我们使用了 Play 模块 play-plugins-salat,并在 foo_scala/conf/application.conf
中配置了连接。然后我们在仓库模型类中映射了我们的 Mongo 集合:
case class Warehouse(id: Option[ObjectId] = Some(new ObjectId), name: String, location: String)
接下来,我们从仓库控制器中调用了适当的仓库伴生对象方法:
val list = Warehouse.list
Warehouse.create(warehouse)
我们还在仓库控制器中声明了我们的 JSON 绑定器,用于仓库模型和 MongoDB 的 ObjectId
:
implicit val objectIdReads = se.radley.plugin.salat.Binders.objectIdReads
implicit val objectIdWrites = se.radley.plugin.salat.Binders.objectIdWrites
implicit val warehouseWrites = Json.writes[Warehouse]
implicit val warehouseReads = Json.reads[Warehouse]
利用 MongoDB 和 GridFS
对于这个菜谱,我们将探索如何通过使用 MongoDB 和 GridFS 来存储和交付文件,通过 Play 应用程序。我们将继续添加到之前的菜谱。与之前的菜谱一样,这个菜谱将只使用 Scala。
如何操作…
让我们采取以下步骤:
-
以启用热重载的方式运行
foo_scala
应用程序:<span class="strong"><strong> activator "~run"</strong></span>
-
通过添加以下内容修改
foo_scala/app/controllers/WarehouseController.scala
:import java.text.SimpleDateFormat import play.api.libs.iteratee.Enumerator def upload = Action(parse.multipartFormData) { request => request.body.file("asset") match { case Some(asset) => { val gridFs = Warehouse.assets val uploadedAsset = gridFs.createFile(asset.ref.file) uploadedAsset.filename = asset.filename uploadedAsset.save() Ok("Asset is available at http://localhost:9000/warehouses/assets/%s".format(uploadedAsset.id)) } case None => { BadRequest } } } def retrieveFile(id: ObjectId) = Action { import com.mongodb.casbah.Implicits._ import play.api.libs.concurrent.Execution.Implicits._ val gridFs = Warehouse.assets gridFs.findOne(Map("_id" -> id)) match { case Some(f) => Result( ResponseHeader(OK, Map( CONTENT_LENGTH -> f.length.toString, CONTENT_TYPE -> f.contentType.getOrElse(BINARY), DATE -> new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss 'GMT'", java.util.Locale.US).format(f.uploadDate) )), Enumerator.fromStream(f.inputStream) ) case None => NotFound } }
-
将新添加的操作的新路由添加到
foo_scala/conf/routes
:POST /warehouses/assets/upload controllers.WarehouseController.upload GET /warehouses/assets/:id controllers.WarehouseController.retrieveFile(id: ObjectId)
-
修改仓库模型在
foo_scala/app/models/Warehouse.scala
中的集合映射:val assets = gridFS("assets") def upload(asset: File) = { assets.createFile(asset) } def retrieve(filename: String) = { assets.find(filename) }
-
通过使用
curl
访问仓库上传端点来上传新的仓库资产文件:<span class="strong"><strong> $ curl -v http://localhost:9000/warehouses/assets/upload -F "asset=@/tmp/1.jpg"</strong></span> <span class="strong"><strong> * Hostname was NOT found in DNS cache</strong></span> <span class="strong"><strong> * Trying ::1...</strong></span> <span class="strong"><strong> * Connected to localhost (::1) port 9000 (#0)</strong></span> <span class="strong"><strong> > POST /warehouses/assets/upload HTTP/1.1</strong></span> <span class="strong"><strong> > User-Agent: curl/7.37.1</strong></span> <span class="strong"><strong> > Host: localhost:9000</strong></span> <span class="strong"><strong> > Accept: */*</strong></span> <span class="strong"><strong> > Content-Length: 13583</strong></span> <span class="strong"><strong> > Expect: 100-continue</strong></span> <span class="strong"><strong> > Content-Type: multipart/form-data; boundary=------------------------4a001bdeff39c089</strong></span> <span class="strong"><strong> ></strong></span> <span class="strong"><strong> < HTTP/1.1 100 Continue</strong></span> <span class="strong"><strong> < HTTP/1.1 200 OK</strong></span> <span class="strong"><strong> < Content-Type: text/plain; charset=utf-8</strong></span> <span class="strong"><strong> < Content-Length: 86</strong></span> <span class="strong"><strong> <</strong></span> <span class="strong"><strong> * Connection #0 to host localhost left intact</strong></span> <span class="strong"><strong> Asset is available at http://localhost:9000/warehouses/assets/549121fbe082fc374fa6cb63%</strong></span>
-
通过在 Web 浏览器中访问前一步输出的 URL 来验证我们的文件交付 URL 路由是否正常工作:
<code class="literal">http://localhost:9000/warehouses/assets/549121fbe082fc374fa6cb63</code>
它是如何工作的…
在这个菜谱中,我们创建了新的 URL 路由和操作,这些操作将用于在 MongoDB 实例中使用 GridFS 上传和检索仓库资产文件。我们在 foo_scala/app/models/Warehouse.scala
中的集合映射文件中添加了 GridFS 引用:
val assets = gridFS("assets")
然后我们添加了文件上传和检索的相关方法:
def upload(asset: File) = {
assets.createFile(asset)
}
def retrieve(filename: String) = {
assets.find(filename)
}
接下来,我们在 foo_scala/app/controllers/WarehouseController.scala
中创建了操作,这些操作将处理实际的文件上传和检索请求。
利用 Redis
对于这个菜谱,我们将探索 Play 应用程序如何通过 Play 缓存与 Redis 集成。Redis 是一个广泛使用的键值数据库,通常用作现代 Web 应用程序的中介对象缓存。这个菜谱需要一个正在运行的 Redis 实例,我们的 Play 2 Web 应用程序可以与之交互。
如何操作…
对于 Java,我们需要采取以下步骤:
-
以启用热重载的方式运行
foo_java
应用程序:<span class="strong"><strong> activator "~run"</strong></span>
-
在
build.sbt
中将 Redis 声明为项目依赖项:"com.typesafe.play.plugins" %% "play-plugins-redis" % "2.3.1"
-
在
build.sbt
中声明托管 Sedis 的存储库,它是 play-plugins-redis 的库依赖项:resolvers += "Sedis repository" at "http://pk11-scratch.googlecode.com/svn/trunk/"
-
通过在
foo_java/conf/play.plugins
中声明它来启用 play-mailer 插件:550:com.typesafe.plugin.RedisPlugin
-
在
foo_java/conf/application.conf
中指定您的 Redis 主机信息:ehcacheplugin=disabled redis.uri="redis://127.0.0.1:6379"
-
通过在
foo_java/app/controllers/Application.java
中添加以下代码来修改foo_java
:import play.cache.*; public static Result displayFromCache() { final String key = "myKey"; String value = (String) Cache.get(key); if (value != null && value.trim().length() > 0) { return ok("Retrieved from Cache: " + value); } else { Cache.set(key, "Let's Play with Redis!"); return ok("Setting key value in the cache"); } }
-
在
foo_java/conf/routes
中为新增的动作添加一个新的路由条目:GET /cache controllers.Application.displayFromCache
-
请求我们的新路由并检查响应体以确认我们的
displayFromCache
动作是第一次设置键值:<span class="strong"><strong> $ curl -v http://localhost:9000/cache</strong></span> <span class="strong"><strong> * Hostname was NOT found in DNS cache</strong></span> <span class="strong"><strong> * Trying 127.0.0.1...</strong></span> <span class="strong"><strong> * Connected to localhost (127.0.0.1) port 9000 (#0)</strong></span> <span class="strong"><strong> > GET /cache HTTP/1.1</strong></span> <span class="strong"><strong> > User-Agent: curl/7.37.1</strong></span> <span class="strong"><strong> > Host: localhost:9000</strong></span> <span class="strong"><strong> > Accept: */*</strong></span> <span class="strong"><strong> ></strong></span> <span class="strong"><strong> < HTTP/1.1 200 OK</strong></span> <span class="strong"><strong> < Content-Type: text/plain; charset=utf-8</strong></span> <span class="strong"><strong> < Content-Length: 30</strong></span> <span class="strong"><strong> <</strong></span> <span class="strong"><strong> * Connection #0 to host localhost left intact</strong></span> <span class="strong"><strong> Setting key value in the cache%</strong></span>
-
再次请求
/cache
路由以查看缓存键的值:<span class="strong"><strong> $ curl -v http://localhost:9000/cache</strong></span> <span class="strong"><strong> * Hostname was NOT found in DNS cache</strong></span> <span class="strong"><strong> * Trying 127.0.0.1...</strong></span> <span class="strong"><strong> * Connected to localhost (127.0.0.1) port 9000 (#0)</strong></span> <span class="strong"><strong> > GET /cache HTTP/1.1</strong></span> <span class="strong"><strong> > User-Agent: curl/7.37.1</strong></span> <span class="strong"><strong> > Host: localhost:9000</strong></span> <span class="strong"><strong> > Accept: */*</strong></span> <span class="strong"><strong> ></strong></span> <span class="strong"><strong> < HTTP/1.1 200 OK</strong></span> <span class="strong"><strong> < Content-Type: text/plain; charset=utf-8</strong></span> <span class="strong"><strong> < Content-Length: 43</strong></span> <span class="strong"><strong> <</strong></span> <span class="strong"><strong> * Connection #0 to host localhost left intact</strong></span> <span class="strong"><strong> Retrieved from Cache: Let's Play with Redis!%</strong></span>
对于 Scala,我们需要采取以下步骤:
-
启用 Hot-Reloading 运行
foo_scala
应用程序:<span class="strong"><strong> activator "~run"</strong></span>
-
在
build.sbt
中声明 Redis 为项目依赖项:"com.typesafe.play.plugins" %% "play-plugins-redis" % "2.3.1"
-
在
build.sbt
中声明托管 Sedis 的仓库,它是 play-plugins-redis 的库依赖项:resolvers += "Sedis repository" at "http://pk11-scratch.googlecode.com/svn/trunk/"
-
通过在
foo_scala/conf/play.plugins
中声明启用 play-mailer 插件:550:com.typesafe.plugin.RedisPlugin
-
在
foo_scala/conf/application.conf
中指定您的 Redis 主机信息:ehcacheplugin=disabled redis.uri="redis://127.0.0.1:6379"
-
通过在
foo_scala/app/controllers/Application.scala
中添加以下代码来修改foo_scala
:import scala.util.Random import play.api.cache._ import play.api.Play.current def displayFromCache = Action { val key = "myKey" Cache.getAsString match { case Some(myKey) => { Ok("Retrieved from Cache: %s".format(myKey)) } case None => { Cache.set(key, "Let's Play with Redis!") Ok("Setting key value in the cache") } } }
-
在
foo_scala/conf/routes
中为新增的action
添加一个新的路由条目:GET /cache controllers.Application.displayFromCache
-
请求我们的新路由并检查响应体以确认我们的
displayFromCache
动作是第一次设置键值:<span class="strong"><strong> $ curl -v http://localhost:9000/cache</strong></span> <span class="strong"><strong> * Hostname was NOT found in DNS cache</strong></span> <span class="strong"><strong> * Trying ::1...</strong></span> <span class="strong"><strong> * Connected to localhost (::1) port 9000 (#0)</strong></span> <span class="strong"><strong> > GET /cache HTTP/1.1</strong></span> <span class="strong"><strong> > User-Agent: curl/7.37.1</strong></span> <span class="strong"><strong> > Host: localhost:9000</strong></span> <span class="strong"><strong> > Accept: */*</strong></span> <span class="strong"><strong> ></strong></span> <span class="strong"><strong> < HTTP/1.1 200 OK</strong></span> <span class="strong"><strong> < Content-Type: text/plain; charset=utf-8</strong></span> <span class="strong"><strong> < Content-Length: 30</strong></span> <span class="strong"><strong> <</strong></span> <span class="strong"><strong> * Connection #0 to host localhost left intact</strong></span> <span class="strong"><strong> Setting key value in the cache%</strong></span>
-
再次请求
/cache
路由以查看缓存键的值:<span class="strong"><strong> $ curl -v http://localhost:9000/cache</strong></span> <span class="strong"><strong> * Hostname was NOT found in DNS cache</strong></span> <span class="strong"><strong> * Trying 127.0.0.1...</strong></span> <span class="strong"><strong> * Connected to localhost (127.0.0.1) port 9000 (#0)</strong></span> <span class="strong"><strong> > GET /cache HTTP/1.1</strong></span> <span class="strong"><strong> > User-Agent: curl/7.37.1</strong></span> <span class="strong"><strong> > Host: localhost:9000</strong></span> <span class="strong"><strong> > Accept: */*</strong></span> <span class="strong"><strong> ></strong></span> <span class="strong"><strong> < HTTP/1.1 200 OK</strong></span> <span class="strong"><strong> < Content-Type: text/plain; charset=utf-8</strong></span> <span class="strong"><strong> < Content-Length: 43</strong></span> <span class="strong"><strong> <</strong></span> <span class="strong"><strong> * Connection #0 to host localhost left intact</strong></span> <span class="strong"><strong> Retrieved from Cache: Let's Play with Redis!%</strong></span>
它是如何工作的…
在这个菜谱中,我们创建了一个新的 URL 路由和动作,它将与我们 Redis 实例交互。为了继续这个菜谱,你需要以下正在运行的 Redis 实例来连接:
<span class="strong"><strong>$ redis-server /usr/local/etc/redis.conf</strong></span>
<span class="strong"><strong>[2407] 14 Apr 12:44:20.623 * Increased maximum number of open files to 10032 (it was originally set to 2560).</strong></span>
<span class="strong"><strong> _._</strong></span>
<span class="strong"><strong> _.-''__ ''-._</strong></span>
<span class="strong"><strong> _.-'' '. '_. ''-._ Redis 2.8.17 (00000000/0) 64 bit</strong></span>
<span class="strong"><strong> .-'' .-'''. '''\/ _.,_ ''-._</strong></span>
<span class="strong"><strong> ( ' , .-' | ', ) Running in stand alone mode</strong></span>
<span class="strong"><strong> |'-._'-...-' __...-.''-._|'' _.-'| Port: 6379</strong></span>
<span class="strong"><strong> | '-._ '._ / _.-' | PID: 2407</strong></span>
<span class="strong"><strong> '-._ '-._ '-./ _.-' _.-'</strong></span>
<span class="strong"><strong> |'-._'-._ '-.__.-' _.-'_.-'|</strong></span>
<span class="strong"><strong> | '-._'-._ _.-'_.-' | http://redis.io</strong></span>
<span class="strong"><strong> '-._ '-._'-.__.-'_.-' _.-'</strong></span>
<span class="strong"><strong> |'-._'-._ '-.__.-' _.-'_.-'|</strong></span>
<span class="strong"><strong> | '-._'-._ _.-'_.-' |</strong></span>
<span class="strong"><strong> '-._ '-._'-.__.-'_.-' _.-'</strong></span>
<span class="strong"><strong> '-._ '-.__.-' _.-'</strong></span>
<span class="strong"><strong> '-._ _.-'</strong></span>
<span class="strong"><strong> '-.__.-'</strong></span>
<span class="strong"><strong>[2407] 14 Apr 12:44:20.631 # Server started, Redis version 2.8.17</strong></span>
<span class="strong"><strong>[2407] 14 Apr 12:44:20.632 * DB loaded from disk: 0.001 seconds</strong></span>
<span class="strong"><strong>[2407] 14 Apr 12:44:20.632 * The server is now ready to accept connections on port 6379</strong></span>
有关安装和运行 Redis 服务器的更多信息,请参阅 redis.io/topics/quickstart
。
我们通过在 build.sbt
中声明必要的依赖项和仓库设置来配置了 Play Redis 模块。然后我们在 conf/application.conf
中配置了到 Redis 实例的连接。最后,我们在 conf/play.plugins
中加载了 Redis play-plugin:
550:com.typesafe.plugin.RedisPlugin
当 displayFromCache
动作被调用时,有两个不同的功能。首先,它尝试从缓存中检索一个值。如果它能够从 Redis 缓存中检索到一个值,它将在响应体中打印该值的正文。如果它无法从 Redis 缓存中检索到一个值,它将设置一个随机字符串值到键,并在响应体中打印一个状态消息。
我们随后使用 curl
测试这条新路由,并访问了两次该路由;动作在响应体中打印出两条不同的消息。
将 Play 应用程序与 Amazon S3 集成
对于这个菜谱,我们将探讨 Play 应用程序如何直接将文件上传到 Amazon Web Services (AWS) S3,这是一个流行的云存储解决方案。
有关 S3 的更多信息,请参阅 aws.amazon.com/s3/
。
如何做到这一点…
对于 Java,我们需要采取以下步骤:
-
启用 Hot-Reloading 运行
foo_java
应用程序:<span class="strong"><strong> activator "~run"</strong></span>
-
在
build.sbt
中将 play-s3 声明为项目依赖项:"com.amazonaws" % "aws-java-sdk" % "1.3.11"
-
在
foo_java/conf/application.conf
中指定您的 AWS 凭据:aws.accessKeyId="YOUR S3 ACCESS KEY" aws.secretKey="YOUR S3 SECRET KEY" fooscala.s3.bucketName="YOUR S3 BUCKET NAME"
-
通过添加以下代码修改
foo_java/app/controllers/Application.java
:import com.amazonaws.auth.*; import com.amazonaws.services.s3.*; import com.amazonaws.services.s3.model.*; public static Result s3Upload() { return ok(views.html.s3.render()); } public static Result submitS3Upload() { Http.MultipartFormData body = request().body().asMultipartFormData(); Http.MultipartFormData.FilePart profileImage = body.getFile("profile"); if (profileImage != null) { try { File file = profileImage.getFile(); String filename = profileImage.getFilename(); String accessKey = Play.application().configuration().getString("aws.accessKeyId"); String secret = Play.application().configuration().getString("aws.secretKey"); String bucketName = Play.application().configuration().getString("fooscala.s3.bucketName"); try { AWSCredentials awsCredentials = new BasicAWSCredentials(accessKey, secret); AmazonS3 s3Client = new AmazonS3Client(awsCredentials); AccessControlList acl = new AccessControlList(); acl.grantPermission(GroupGrantee.AllUsers, Permission.Read); s3Client.createBucket(bucketName); s3Client.putObject(new PutObjectRequest(bucketName, filename, file).withAccessControlList(acl)); String img = "http://" + bucketName+ ".s3.amazonaws.com/" + filename; return ok("Image uploaded: " + img); } catch (Exception e) { e.printStackTrace(); return ok("Image was not uploaded"); } } catch(Exception e) { return internalServerError(e.getMessage()); } } else { return badRequest(); } }
-
在
foo_java/conf/routes
中为新增的动作添加新的路由条目:GET /s3_upload controllers.Application.s3Upload POST /s3_upload controllers.Application.submitS3Upload
-
将 S3 文件上传提交视图模板添加到
foo_java/app/views/s3.scala.html
:@helper.form(action = routes.Application.submitS3Upload, 'enctype -> "multipart/form-data") { <input type="file" name="profile"> <p> <input type="submit"> </p> }
对于 Scala,我们需要采取以下步骤:
-
以启用热重载的方式运行
foo_scala
应用程序:<span class="strong"><strong> activator "~run"</strong></span>
-
在
build.sbt
中将 play-s3 声明为项目依赖项:"nl.rhinofly" %% "play-s3" % "5.0.2",
-
声明 play-s3 模块托管的自定义仓库:
resolvers += "Rhinofly Internal Repository" at "http://maven- repository.rhinofly.net:8081/artifactory/libs-release-local"
-
在
foo_scala/conf/application.conf
中指定您的 AWS 凭证:aws.accessKeyId="YOUR S3 ACCESS KEY" aws.secretKey="YOUR S3 SECRET KEY" fooscala.s3.bucketName="YOUR S3 BUCKET NAME"
-
修改
foo_scala/app/controllers/Application.scala
文件,添加以下代码:import play.api.Play.current import fly.play.s3._ def s3Upload = Action { Ok(s3()) } def submitS3Upload = Action(parse.multipartFormData) { request => import play.api.Play request.body.file("profile") match { case Some(profileImage) => { val bucketName = Play.current.configuration.getString("fooscala.s3.bucketName").get val bucket = S3(bucketName) val filename = profileImage.filename val contentType = profileImage.contentType val byteArray = Files.toByteArray(profileImage.ref.file) val result = bucket.add(BucketFile(filename, contentType.get, byteArray, Option(PUBLIC_READ), None)) val future = Await.result(result, 10 seconds) Ok("Image uploaded to: http://%s.s3.amazonaws.com/%s".format(bucketName, filename)) } case None => { BadRequest } } }
-
在
foo_scala/conf/routes
中为新增的动作添加新的路由条目:GET /s3_upload controllers.Application.s3Upload POST /s3_upload controllers.Application.submitS3Upload Add the s3 file upload submission view template in foo_scala/app/views/s3.scala.html: @helper.form(action = routes.Application.submitS3Upload, 'enctype -> "multipart/form-data") { <input type="file" name="profile"> <p> <input type="submit"> </p> }
它是如何工作的…
在本菜谱中,我们创建了一个新的 URL 路由和动作,用于接收上传的文件。然后我们通过在conf/application.conf
中提供 S3 访问密钥和秘密密钥,使用 RhinoFly S3 模块将此文件推送到 Amazon S3。我们还指定了conf/application.conf
中的 S3 存储桶名称以供将来使用。我们可以通过使用 Play 的配置 API 来检索此值:
val bucketName = Play.current.configuration.getString("fooscala.s3.bucketName").get
我们随后将上传文件的存储位置打印到响应体中,以便于验证:
Ok("Image uploaded to: http://%s.s3.amazonaws.com/%s".format(bucketName, filename))
您现在应该在网页浏览器中看到以下文本的响应:
Image uploaded to: http://<YOUR_BUCKET_NAME>.s3.amazonaws.com/<FILENAME>
您还可以使用 AWS 管理控制台在 S3 部分验证文件上传:
https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/play-fw-cb/img/Vt98tzVB.jpg
将 Typesafe Slick 集成到 Play 应用程序中
在本菜谱中,我们将探讨如何使用play-slick
模块将 Typesafe Slick 与 Play 应用程序集成。Typesafe Slick 是一个基于 Scala 构建的关系映射工具,对于管理类似原生 Scala 类型的数据库对象来说非常方便。
如何做到这一点…
我们需要采取以下步骤:
-
以启用热重载的方式运行
foo_scala
应用程序:<span class="strong"><strong> activator "~run"</strong></span>
-
在
build.sbt
中将 play-slick 声明为项目依赖项:"com.typesafe.play" %% "play-slick" % "0.8.1"
-
在
foo_scala/conf/application.conf
中指定您的数据库主机信息:db.default.driver=org.h2.Driver db.default.url="jdbc:h2:mem:play" db.default.user=sa db.default.password="" slick.default="models.*"
-
使用以下内容创建新的供应商控制器
foo_scala/app/controllers/SupplierController.scala
:package controllers import play.api.mvc.{BodyParsers, Controller} import play.api.db.slick._ import play.api.libs.json._ import play.api.Play.current import models._ object SupplierController extends Controller { implicit val supplierWrites = Json.writes[Supplier] implicit val supplierReads = Json.reads[Supplier] def index = DBAction { implicit rs => Ok(Json.toJson(Suppliers.list)) } def create = DBAction(BodyParsers.parse.json) { implicit rs => val post = rs.request.body.validate[Supplier] post.fold( errors => { BadRequest(Json.obj("status" ->"error", "message" -> JsError.toFlatJson(errors))) }, supplier => { Suppliers.create(supplier) Created(Json.toJson(supplier)) } ) } }
-
在
foo_scala/app/models/Suppliers.scala
文件中创建供应商的 Slick 映射,内容如下:package models import scala.slick.driver.H2Driver.simple._ import scala.slick.lifted.Tag case class Supplier(id: Option[Int], name: String, contactNo: String) class Suppliers(tag: Tag) extends TableSupplier { def id = columnInt def name = columnString def contactNo = columnString def * = (id.?, name, contactNo) <> (Supplier.tupled, Supplier.unapply) } object Suppliers { val suppliers = TableQuery[Suppliers] def list(implicit s: Session) = suppliers.sortBy(m => m.name.asc).list def create(supplier: Supplier)(implicit s: Session) = suppliers.insert(supplier) }
-
在
foo_scala/conf/routes
中添加Supplier
控制器的新的路由:GET /suppliers controllers.SupplierController.index POST /suppliers controllers.SupplierController.create
-
请求新的
Post
路由并检查响应头和体,以确认记录已插入数据库:<span class="strong"><strong> $ curl -v -X POST http://localhost:9000/suppliers --header "Content-type: application/json" --data '{"name":"Ned Flanders", "contactNo":"555-1234"}'</strong></span> <span class="strong"><strong> * Hostname was NOT found in DNS cache</strong></span> <span class="strong"><strong> * Trying ::1...</strong></span> <span class="strong"><strong> * Connected to localhost (::1) port 9000 (#0)</strong></span> <span class="strong"><strong> > POST /suppliers HTTP/1.1</strong></span> <span class="strong"><strong> > User-Agent: curl/7.37.1</strong></span> <span class="strong"><strong> > Host: localhost:9000</strong></span> <span class="strong"><strong> > Accept: */*</strong></span> <span class="strong"><strong> > Content-type: application/json</strong></span> <span class="strong"><strong> > Content-Length: 47</strong></span> <span class="strong"><strong> ></strong></span> <span class="strong"><strong> * upload completely sent off: 47 out of 47 bytes</strong></span> <span class="strong"><strong> < HTTP/1.1 201 Created</strong></span> <span class="strong"><strong> < Content-Type: application/json; charset=utf-8</strong></span> <span class="strong"><strong> < Content-Length: 46</strong></span> <span class="strong"><strong> <</strong></span> <span class="strong"><strong> * Connection #0 to host localhost left intact</strong></span> <span class="strong"><strong> {"name":"Ned Flanders","contactNo":"555-1234"}%</strong></span>
-
请求列表路由并验证它确实正在从数据库返回记录:
<span class="strong"><strong> $ curl -v http://localhost:9000/suppliers</strong></span> <span class="strong"><strong> * Hostname was NOT found in DNS cache</strong></span> <span class="strong"><strong> * Trying ::1...</strong></span> <span class="strong"><strong> * Connected to localhost (::1) port 9000 (#0)</strong></span> <span class="strong"><strong> > GET /suppliers HTTP/1.1</strong></span> <span class="strong"><strong> > User-Agent: curl/7.37.1</strong></span> <span class="strong"><strong> > Host: localhost:9000</strong></span> <span class="strong"><strong> > Accept: */*</strong></span> <span class="strong"><strong> ></strong></span> <span class="strong"><strong> < HTTP/1.1 200 OK</strong></span> <span class="strong"><strong> < Content-Type: application/json; charset=utf-8</strong></span> <span class="strong"><strong> < Content-Length: 110</strong></span> <span class="strong"><strong> <</strong></span> <span class="strong"><strong> * Connection #0 to host localhost left intact</strong></span> <span class="strong"><strong> [{"id":1,"name":"Maud Flanders","contactNo":"555-1234"},{"id":2,"name":"Ned Flanders","contactNo":"712-1234"}]%</strong></span>
它是如何工作的…
在本菜谱中,我们创建了一个新的 URL 路由和动作,用于从H2
数据库中创建和检索供应商。我们使用 Typesafe Slick 作为关系映射工具来创建查询和插入。我们首先在build.sbt
中声明了所需的依赖项。接下来,我们在foo_scala/app/models/Supplier.scala
中定义了供应商的映射属性。
在映射文件中,我们声明了我们的 case class 供应商。我们还声明了我们的 Slick 表映射类。最后,我们添加了我们的供应商对象类,该类应包含所有数据插入和查询所需的功能。我们将适当的路由添加到conf/routes
文件中,并运行数据库演变。这允许 Slick 自动管理表创建和列同步。为了测试我们的实现,我们使用 curl 请求我们的POST
和GET
端点,以便能够查看响应头和正文。
利用 play-mailer
对于这个菜谱,我们将探讨 Play 应用程序如何发送电子邮件。我们将使用 Play 模块 play-mailer 来实现这一点。我们将使用 Mandrill,一个云电子邮件服务,来发送电子邮件。有关 Mandrill 的更多信息,请参阅mandrill.com/
。
如何做到这一点…
对于 Java,我们需要采取以下步骤:
-
启用 Hot-Reloading 后运行
foo_java
应用程序:<span class="strong"><strong> activator "~run"</strong></span>
-
在
build.sbt
中将 play-mailer 声明为项目依赖项:"com.typesafe.play.plugins" %% "play-plugins-mailer" % "2.3.1"
-
通过在
foo_java/conf/play.plugins
中声明它来启用 play-mailer 插件:1500:com.typesafe.plugin.CommonsMailerPlugin
-
在
foo_java/conf/application.conf
中指定你的smtp
主机信息:smtp.host=smtp.mandrillapp.com smtp.port=25 smtp.user="YOUR OWN USER HERE" smtp.password="YOUR OWN PASSWORD HERE" smtp.mock=true
-
通过在
foo_java/app/controllers/Application.java
中添加以下代码来修改foo_java/app/controllers/Application.java
:import play.libs.F; import play.libs.F.Function; import play.libs.F.Promise; import com.typesafe.plugin.*; public static Promise<Result> emailSender() { Promise<Boolean> emailResult = Promise.promise( new F.Function0<Boolean>() { @Override public Boolean apply() throws Throwable { try { MailerAPI mail = play.Play.application().plugin(MailerPlugin.class).email(); mail.setSubject("mailer"); mail.setRecipient("ginduc@dynamicobjx.com"); mail.setFrom("Play Cookbook <noreply@email.com>"); mail.send("text"); return true; } catch (Exception e) { e.printStackTrace(); return false; } } } ); return emailResult.map( new Function<Boolean, Result>() { @Override public Result apply(Boolean sent) throws Throwable { if (sent) { return ok("Email sent!"); } else { return ok("Email was not sent!"); } } } ); }
-
在
foo_java/conf/routes
中为新增的操作添加一个新的路由条目:POST /send_email controllers.Application.emailSender
-
请求我们的新路由并检查响应头以确认我们对 HTTP 响应头的修改:
<span class="strong"><strong> $ curl -v -X POST http://localhost:9000/send_email</strong></span> <span class="strong"><strong> * Hostname was NOT found in DNS cache</strong></span> <span class="strong"><strong> * Trying ::1...</strong></span> <span class="strong"><strong> * Connected to localhost (::1) port 9000 (#0)</strong></span> <span class="strong"><strong> > POST /send_email HTTP/1.1</strong></span> <span class="strong"><strong> > User-Agent: curl/7.37.1</strong></span> <span class="strong"><strong> > Host: localhost:9000</strong></span> <span class="strong"><strong> > Accept: */*</strong></span> <span class="strong"><strong> ></strong></span> <span class="strong"><strong> < HTTP/1.1 200 OK</strong></span> <span class="strong"><strong> < Content-Type: text/plain; charset=utf-8</strong></span> <span class="strong"><strong> < Content-Length: 11</strong></span> <span class="strong"><strong> <</strong></span> <span class="strong"><strong> * Connection #0 to host localhost left intact</strong></span> <span class="strong"><strong> Email sent!%</strong></span>
对于 Scala,我们需要采取以下步骤:
-
启用 Hot-Reloading 后运行
foo_scala
应用程序:<span class="strong"><strong> activator "~run"</strong></span>
-
在
build.sbt
中将 play-mailer 声明为项目依赖项:"com.typesafe.play.plugins" %% "play-plugins-mailer" % "2.3.1"
-
通过在
foo_scala/conf/play.plugins
中声明它来启用 play-mailer 插件:1500:com.typesafe.plugin.CommonsMailerPlugin
-
在
foo_scala/conf/application.conf
中指定你的smtp
主机信息:smtp.host=smtp.mandrillapp.com smtp.port=25 smtp.user="YOUR OWN USER HERE" smtp.password="YOUR OWN PASSWORD HERE" smtp.mock=true
-
通过在
foo_scala/app/controllers/Application.scala
中添加以下操作来修改foo_scala/app/controllers/Application.scala
:import scala.concurrent._ import com.typesafe.plugin._ import play.api.libs.concurrent.Execution.Implicits._ import play.api.Play.current def emailSender = Action.async { sendEmail.map { messageId => Ok("Sent email with Message ID: " + messageId) } } def sendEmail = Future { val mail = use[MailerPlugin].email mail.setSubject("Play mailer") mail.setRecipient("ginduc@dynamicobjx.com") mail.setFrom("Play Cookbook <noreply@email.com>") mail.send("text") }
-
在
foo_scala/conf/routes
中为新增的操作添加一个新的路由条目:POST /send_email controllers.Application.emailSender
-
请求我们的新路由并检查响应头以确认我们对 HTTP 响应头的修改:
<span class="strong"><strong> $ curl -v -X POST http://localhost:9000/send_email</strong></span> <span class="strong"><strong> * Hostname was NOT found in DNS cache</strong></span> <span class="strong"><strong> * Trying ::1...</strong></span> <span class="strong"><strong> * Connected to localhost (::1) port 9000 (#0)</strong></span> <span class="strong"><strong> > POST /send_email HTTP/1.1</strong></span> <span class="strong"><strong> > User-Agent: curl/7.37.1</strong></span> <span class="strong"><strong> > Host: localhost:9000</strong></span> <span class="strong"><strong> > Accept: */*</strong></span> <span class="strong"><strong> ></strong></span> <span class="strong"><strong> < HTTP/1.1 200 OK</strong></span> <span class="strong"><strong> < Content-Type: text/plain; charset=utf-8</strong></span> <span class="strong"><strong> < Content-Length: 30</strong></span> <span class="strong"><strong> <</strong></span> <span class="strong"><strong> * Connection #0 to host localhost left intact</strong></span> <span class="strong"><strong> Sent email with Message ID: ()%</strong></span>
它是如何工作的…
在这个菜谱中,我们创建了一个新的 URL 路由和操作,该操作将调用我们新添加的sendMail
函数。我们在foo_scala/build.sbt
中声明了模块依赖,并在foo_scala/conf/application.conf
中指定了我们的smtp
服务器设置。之后,我们使用终端中的curl
调用了 URL 路由来测试我们的电子邮件发送者。你现在应该能在你的电子邮件客户端软件中收到电子邮件。
集成 Bootstrap 和 WebJars
对于这个菜谱,我们将探讨如何将流行的前端框架 Bootstrap 与 Play 2 Web 应用程序集成和利用。我们将使用 WebJars 来集成 Bootstrap,这是一个将前端库打包成 JAR 文件的工具,然后可以轻松管理(在我们的情况下,通过sbt
)。
如何做到这一点…
对于 Java,我们需要采取以下步骤:
-
启用 Hot-Reloading 后运行
foo_java
应用程序:<span class="strong"><strong> activator "~run"</strong></span>
-
在
build.sbt
中将 Bootstrap 和 WebJars 声明为项目依赖项:"org.webjars" % "bootstrap" % "3.3.1", "org.webjars" %% "webjars-play" % "2.3.0"
-
通过添加以下代码修改
foo_java/app/controllers/Application.java
:public static Result bootstrapped() { return ok(views.html.bootstrapped.render()); }
-
将新的路由条目添加到
foo_java/conf/routes
:GET /webjars/*file controllers.WebJarAssets.at(file) GET /bootstrapped controllers.Application.bootstrapped
-
创建新的布局视图模板
foo_java/app/views/mainLayout.scala.html
,内容如下:@(title: String)(content: Html)<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="description" content=""> <meta name="author" content=""> <title>@title</title> <link rel="shortcut icon" type="image/png" href='@routes.Assets.at("images/favicon.png")'> <link rel="stylesheet" media="screen" href='@routes.WebJarAssets.at(WebJarAssets.locate("css/bootstrap.min.css"))' /> <link rel="stylesheet" media="screen" href='@routes.Assets.at("stylesheets/app.css")'/> <style> body { padding-top: 50px; } </style> </head> <body> <nav class="navbar navbar-inverse navbar-fixed-top" role="navigat ion"> <div class="container-fluid"> <div class="navbar-header"> <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" href="#">Admin</a> </div> <div id="navbar" class="navbar-collapse collapse"> <ul class="nav navbar-nav navbar-right"> <li><a href="#">Dashboard</a></li> <li><a href="#">Settings</a></li> <li><a href="#">Profile</a></li> <li><a href="#">Help</a></li> </ul> </div> </div> </nav> <div class="container-fluid"> <div class="row"> <div class="col-sm-3 col-md-2 sidebar"> <ul class="nav nav-sidebar"> <li class="active"><a href="#">Overview <span class="sr-only">(current)</span></a></li> <li><a href="#">Reports</a></li> <li><a href="#">Analytics</a></li> <li><a href="#">Export</a></li> </ul> <ul class="nav nav-sidebar"> <li><a href="">Users</a></li> <li><a href="">Audit Log</a></li> </ul> <ul class="nav nav-sidebar"> <li><a href="">Sign out</a></li> </ul> </div> <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main"> @content </div> </div> </div> </body> </html>
-
在
foo_java/app/views/bootstrapped.scala.html
中创建自举视图模板,内容如下:@mainLayout("Bootstrapped") { <div class="hero-unit"> <h1>Hello, world!</h1> <p>This is a template for a simple marketing or informational website. It includes a large callout called the hero unit and three supporting pieces of content. Use it as a starting point to create something more unique.</p> <p><a href="#" class="btn btn-primary btn-large">Learn more &raquo;</a></p> </div> }
-
使用网络浏览器请求我们的新自举路由(
http://localhost:9000/bootstrapped
)并检查使用 Bootstrap 模板渲染的页面:https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/play-fw-cb/img/TwofsdMh.jpg
对于 Scala,我们需要采取以下步骤:
-
启用热重载运行
foo_scala
应用程序:<span class="strong"><strong> activator "~run"</strong></span>
-
在
build.sbt
中将 Bootstrap 和 WebJars 声明为项目依赖项:"org.webjars" % "bootstrap" % "3.3.1", "org.webjars" %% "webjars-play" % "2.3.0"
-
通过添加以下操作修改
foo_scala/app/controllers/Application.scala
:def bootstrapped = Action { Ok(views.html.bootstrapped()) }
-
将新的路由条目添加到
foo_scala/conf/routes
:GET /webjars/*file controllers.WebJarAssets.at(file) GET /bootstrapped controllers.Application.bootstrapped
-
创建新的布局视图模板
foo_scala/app/views/mainLayout.scala.html
,内容如下:@(title: String)(content: Html)<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="description" content=""> <meta name="author" content=""> <title>@title</title> <link rel="shortcut icon" type="image/png" href='@routes.Assets.at("images/favicon.png")'> <link rel="stylesheet" media="screen" href='@routes.WebJarAssets.at(WebJarAssets.locate("css/bootstrap.min.css"))' /> <link rel="stylesheet" media="screen" href='@routes.Assets.at("stylesheets/app.css")'/> <style> body { padding-top: 50px; } </style> </head> <body> <nav class="navbar navbar-inverse navbar-fixed-top" role="navigat ion"> <div class="container-fluid"> <div class="navbar-header"> <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" href="#">Admin</a> </div> <div id="navbar" class="navbar-collapse collapse"> <ul class="nav navbar-nav navbar-right"> <li><a href="#">Dashboard</a></li> <li><a href="#">Settings</a></li> <li><a href="#">Profile</a></li> <li><a href="#">Help</a></li> </ul> </div> </div> </nav> <div class="container-fluid"> <div class="row"> <div class="col-sm-3 col-md-2 sidebar"> <ul class="nav nav-sidebar"> <li class="active"><a href="#">Overview <span class="sr-only">(current)</span></a></li> <li><a href="#">Reports</a></li> <li><a href="#">Analytics</a></li> <li><a href="#">Export</a></li> </ul> <ul class="nav nav-sidebar"> <li><a href="">Users</a></li> <li><a href="">Audit Log</a></li> </ul> <ul class="nav nav-sidebar"> <li><a href="">Sign out</a></li> </ul> </div> <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main"> @content </div> </div> </div> </body> </html>
-
在
foo_scala/app/views/bootstrapped.scala.html
中创建自举视图模板,内容如下:@mainLayout("Bootstrapped") { <div class="hero-unit"> <h1>Hello, world!</h1> <p>This is a template for a simple marketing or informational website. It includes a large callout called the hero unit and three supporting pieces of content. Use it as a starting point to create something more unique.</p> <p><a href="#" class="btn btn-primary btn-large">Learn more &raquo;</a></p> </div> }
-
使用网络浏览器请求我们的新自举路由(
http://localhost:9000/bootstrapped
)并检查使用 Bootstrap 模板渲染的页面:https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/play-fw-cb/img/Zhuvkkee.jpg
它是如何工作的……
在这个菜谱中,我们不是单独下载 Bootstrap 并手动管理不同版本,而是使用了 Play 模块和 WebJars,在build.sbt
中将 Bootstrap 声明为前端依赖。我们创建了包含 Bootstrap 模板的新视图模板。然后我们创建了一个新的 URL 路由,将利用这些基于 Bootstrap 的新视图。
第四章. 创建和使用 Web API
在本章中,我们将介绍以下食谱:
-
创建一个
POST
API 端点 -
创建一个
GET
API 端点 -
创建一个
PUT
API 端点 -
创建一个
DELETE
API 端点 -
使用 HTTP 基本身份验证保护 API 端点
-
消费外部 Web API
-
使用 OAuth 通过 Twitter API
简介
在本章中,我们将探讨如何使用 Play 2.0 创建 REST API 并与其他外部基于 Web 的 API 进行交互,在我们的例子中,是 Twitter API。
随着独立 Web 服务之间数据交换的日益流行,REST API 已经成为了一种流行的方法,不仅用于消费外部数据,还用于接收传入数据以进行进一步处理和持久化,以及向授权客户端公开数据。基于 RESTful API 规范,HTTP 方法 POST
用于插入新记录,HTTP 方法 GET
用于检索数据。HTTP 方法 PUT
用于更新现有记录,最后,HTTP 方法 DELETE
用于删除记录。
我们将看到如何利用不同的 Play 2.0 库来构建我们自己的 REST API 端点,并使用新的 Play WS 库访问其他基于 Web 的 API。
创建一个 POST
API 端点
在本食谱中,我们将探索如何使用 Play 2.0 创建一个 RESTful POST
端点来向我们的 API 添加新记录。
如何做到这一点…
对于 Java,我们需要执行以下步骤:
-
启用热重载功能运行
foo_java
应用程序:<span class="strong"><strong> activator "~run"</strong></span>
-
在
foo_java/app/controllers/Products.java
中创建一个新的产品控制器,内容如下:package controllers; import java.util.*; import play.data.Form; import play.mvc.*; import models.Product; import static play.libs.Json.toJson; public class Products extends Controller { public static Map<String, Product> products = new HashMap<String, Product>(); @BodyParser.Of(BodyParser.Json.class) public static Result create() { try { Form<Product> form = Form.form(Product.class).bindFromRequest(); if (form.hasErrors()) { return badRequest(form.errorsAsJson()); } else { Product product = form.get(); products.put(product.getSku(), product); return created(toJson(product)); } } catch (Exception e) { return internalServerError(e.getMessage()); } } }
-
在
foo_java/app/models/Product.java
中创建一个产品模型类:package models; import play.data.validation.Constraints; public class Product implements java.io.Serializable { @Constraints.Required private String sku; @Constraints.Required private String title; public String getSku() { return sku; } public void setSku(String sku) { this.sku = sku; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } }
-
在
foo_java/conf/routes
中为新增的操作添加一个新的路由条目:POST /api/products controllers.Products.create
-
请求新的路由并检查响应体以确认:
<span class="strong"><strong> $ curl -v -X POST http://localhost:9000/api/products --data '{"sku":"abc", "title":"Macbook Pro Retina"}' --header "Content-type: application/json"</strong></span> <span class="strong"><strong> * Hostname was NOT found in DNS cache</strong></span> <span class="strong"><strong> * Trying ::1...</strong></span> <span class="strong"><strong> * Connected to localhost (::1) port 9000 (#0)</strong></span> <span class="strong"><strong> > POST /api/products HTTP/1.1</strong></span> <span class="strong"><strong> > User-Agent: curl/7.37.1</strong></span> <span class="strong"><strong> > Host: localhost:9000</strong></span> <span class="strong"><strong> > Accept: */*</strong></span> <span class="strong"><strong> > Content-type: application/json</strong></span> <span class="strong"><strong> > Content-Length: 43</strong></span> <span class="strong"><strong> ></strong></span> <span class="strong"><strong> * upload completely sent off: 43 out of 43 bytes</strong></span> <span class="strong"><strong> < HTTP/1.1 201 Created</strong></span> <span class="strong"><strong> < Content-Type: application/json; charset=utf-8</strong></span> <span class="strong"><strong> < Content-Length: 42</strong></span> <span class="strong"><strong> <</strong></span> <span class="strong"><strong> * Connection #0 to host localhost left intact</strong></span> <span class="strong"><strong> {"sku":"abc","title":"Macbook Pro Retina"}%</strong></span>
对于 Scala,我们需要执行以下步骤:
-
启用热重载功能运行
foo_scala
应用程序:<span class="strong"><strong> activator "~run"</strong></span>
-
在
foo_scala/app/controllers/Products.scala
中创建一个新的产品控制器,内容如下:package controllers import models.Product import play.api.libs.json.{JsError, Json} import play.api.libs.json.Json._ import play.api.mvc._ object Products extends Controller { implicit private val productWrites = Json.writes[Product] implicit private val productReads = Json.reads[Product] private val products: scala.collection.mutable.ListBuffer[Product] = scala.collection.mutable.ListBuffer[Product]() def create = Action(BodyParsers.parse.json) { implicit request => val post = request.body.validate[Product] post.fold( errors => BadRequest(Json.obj("message" -> JsError.toFlatJson(errors))), p => { try { products += p Created(Json.toJson(p)) } catch { case e: Exception => InternalServerError(e.getMessage) } } ) } }
-
在
foo_scala/app/models/Product.scala
中创建一个产品模型类:package models case class Product(sku: String, title: String)
-
在
foo_scala/conf/routes
中为新增的操作添加一个新的路由条目:POST /api/products controllers.Products.create
-
请求新的路由并检查响应体以确认:
<span class="strong"><strong> $ curl -v -X POST http://localhost:9000/api/products --data '{"sku":"abc", "title":"Macbook Pro Retina"}' --header "Content-type: application/json"</strong></span> <span class="strong"><strong> * Hostname was NOT found in DNS cache</strong></span> <span class="strong"><strong> * Trying ::1...</strong></span> <span class="strong"><strong> * Connected to localhost (::1) port 9000 (#0)</strong></span> <span class="strong"><strong> > POST /api/products HTTP/1.1</strong></span> <span class="strong"><strong> > User-Agent: curl/7.37.1</strong></span> <span class="strong"><strong> > Host: localhost:9000</strong></span> <span class="strong"><strong> > Accept: */*</strong></span> <span class="strong"><strong> > Content-type: application/json</strong></span> <span class="strong"><strong> > Content-Length: 43</strong></span> <span class="strong"><strong> ></strong></span> <span class="strong"><strong> * upload completely sent off: 43 out of 43 bytes</strong></span> <span class="strong"><strong> < HTTP/1.1 201 Created</strong></span> <span class="strong"><strong> < Content-Type: application/json; charset=utf-8</strong></span> <span class="strong"><strong> < Content-Length: 42</strong></span> <span class="strong"><strong> <</strong></span> <span class="strong"><strong> * Connection #0 to host localhost left intact</strong></span> <span class="strong"><strong> {"sku":"abc","title":"Macbook Pro Retina"}%</strong></span>
它是如何工作的…
在本食谱中,我们使用 Play 2.0 实现了一个 RESTful POST
请求。第一步是创建我们的控制器和模型类。对于模型类,我们声明了两个基本的产品字段。我们将它们标注为必填字段。这允许 Play 在产品绑定到请求体时验证这两个字段。
// Java
@Constraints.Required
private String sku;
对于强制执行必需请求参数的 Scala 等价物,我们使用 scala.Option
类声明可选参数。然而,在本食谱中,为了保持 Java 和 Scala 食谱的一致性,将不需要使用 scala.Option
,我们将在我们的案例类中强制执行必需字段,如下所示:
case class Product(sku: String, title: String)
我们在控制器类中创建了一个动作方法,该方法将处理产品的POST
请求。我们确保在数据绑定过程中play.data.Form
对象没有遇到任何验证错误;然而,如果遇到问题,它将通过badRequest()
辅助函数返回 HTTP 状态码 400:
// Java
if (form.hasErrors()) {
return badRequest(form.errorsAsJson());
}
// Scala
errors => BadRequest(Json.obj("message" -> JsError.toFlatJson(errors))),
如果没有遇到错误,我们继续持久化我们的新产品并返回由created()
辅助函数包装的 HTTP 状态码 201:
// Java
return created(toJson(product));
// Scala
Created(Json.toJson(p))
我们然后在conf/routes
文件中声明了我们的新POST
路由。最后,我们使用命令行工具curl
模拟 HTTP POST
请求来测试我们的路由。要验证我们的端点是否执行了POST
表单字段的验证,请从之前的curl
命令中省略标题参数,您将看到适当的错误消息:
<span class="strong"><strong> $ curl -v -X POST http://localhost:9000/api/products --data '{"sku":"abc"}' --header "Content-type: application/json"</strong></span>
<span class="strong"><strong> * Hostname was NOT found in DNS cache</strong></span>
<span class="strong"><strong> * Trying ::1...</strong></span>
<span class="strong"><strong> * Connected to localhost (::1) port 9000 (#0)</strong></span>
<span class="strong"><strong> > POST /api/products HTTP/1.1</strong></span>
<span class="strong"><strong> > User-Agent: curl/7.37.1</strong></span>
<span class="strong"><strong> > Host: localhost:9000</strong></span>
<span class="strong"><strong> > Accept: */*</strong></span>
<span class="strong"><strong> > Content-type: application/json</strong></span>
<span class="strong"><strong> > Content-Length: 44</strong></span>
<span class="strong"><strong> ></strong></span>
<span class="strong"><strong> * upload completely sent off: 44 out of 44 bytes</strong></span>
<span class="strong"><strong> < HTTP/1.1 400 Bad Request</strong></span>
<span class="strong"><strong> < Content-Type: application/json; charset=utf-8</strong></span>
<span class="strong"><strong> < Content-Length: 36</strong></span>
<span class="strong"><strong> <</strong></span>
<span class="strong"><strong> * Connection #0 to host localhost left intact</strong></span>
<span class="strong"><strong> {"title":["This field is required"]}%</strong></span>
创建一个 GET API 端点
对于这个菜谱,我们将创建一个 RESTful 的GET
端点,它将返回一个 JSON 对象的集合。
如何做…
对于 Java,我们需要执行以下步骤:
-
启用 Hot-Reloading 运行
foo_java
应用程序:<span class="strong"><strong> activator "~run"</strong></span>
-
通过添加以下动作方法来修改
foo_java/app/controllers/Products.java
中的产品控制器:public static Result index() { return ok(toJson(products)); }
-
在
foo_java/conf/routes
中为新增的操作添加一个新的路由条目:GET /api/products controllers.Products.index
-
请求新路由并检查响应头以确认我们对 HTTP 响应头的修改:
<span class="strong"><strong> $ curl -v http://localhost:9000/api/products</strong></span> <span class="strong"><strong> * Hostname was NOT found in DNS cache</strong></span> <span class="strong"><strong> * Trying ::1...</strong></span> <span class="strong"><strong> * Connected to localhost (::1) port 9000 (#0)</strong></span> <span class="strong"><strong> > GET /api/products HTTP/1.1</strong></span> <span class="strong"><strong> > User-Agent: curl/7.37.1</strong></span> <span class="strong"><strong> > Host: localhost:9000</strong></span> <span class="strong"><strong> > Accept: */*</strong></span> <span class="strong"><strong> ></strong></span> <span class="strong"><strong> < HTTP/1.1 200 OK</strong></span> <span class="strong"><strong> < Content-Type: application/json; charset=utf-8</strong></span> <span class="strong"><strong> < Content-Length: 50</strong></span> <span class="strong"><strong> <</strong></span> <span class="strong"><strong> * Connection #0 to host localhost left intact</strong></span> <span class="strong"><strong> {"abc":{"sku":"abc","title":"Macbook Pro Retina"},"def":{"sku":"def","title":"iPad Air"}}%</strong></span>
对于 Scala,我们需要执行以下步骤:
-
启用 Hot-Reloading 运行
foo_scala
应用程序:<span class="strong"><strong> activator "~run"</strong></span>
-
通过在
foo_scala/app/controllers/Products.scala
中添加以下动作方法来修改产品控制器:def index = Action { Ok(toJson(products)) }
-
在
foo_scala/conf/routes
中为新增的操作添加一个新的路由条目:<span class="strong"><strong> GET /api/products controllers.Products.index</strong></span>
-
请求我们的新路由并检查响应头以确认我们对 HTTP 响应头的修改:
<span class="strong"><strong> $ curl -v http://localhost:9000/api/products</strong></span> <span class="strong"><strong> * Hostname was NOT found in DNS cache</strong></span> <span class="strong"><strong> * Trying ::1...</strong></span> <span class="strong"><strong> * Connected to localhost (::1) port 9000 (#0)</strong></span> <span class="strong"><strong> > GET /api/products HTTP/1.1</strong></span> <span class="strong"><strong> > User-Agent: curl/7.37.1</strong></span> <span class="strong"><strong> > Host: localhost:9000</strong></span> <span class="strong"><strong> > Accept: */*</strong></span> <span class="strong"><strong> ></strong></span> <span class="strong"><strong> < HTTP/1.1 200 OK</strong></span> <span class="strong"><strong> < Content-Type: application/json; charset=utf-8</strong></span> <span class="strong"><strong> < Content-Length: 50</strong></span> <span class="strong"><strong> <</strong></span> <span class="strong"><strong> * Connection #0 to host localhost left intact</strong></span> <span class="strong"><strong> {"abc":{"sku":"abc","title":"Macbook Pro Retina"},"def":{"sku":"def","title":"iPad Air"}}%</strong></span>
如何工作…
在这个菜谱中,我们实现了一个返回产品记录列表的 API 端点。我们通过声明一个新的动作方法来实现这一点,该方法从我们的数据存储中检索记录,将对象转换为 JSON,并返回一个 JSON 集合:
// Java
return ok(toJson(products));
// Scala
Ok(toJson(products))
我们为GET
端点声明了一个新的路由条目,并使用curl
验证端点的功能。
如果数据存储为空,端点将返回一个空的 JSON 数组:
<span class="strong"><strong> # Empty product list </strong></span>
<span class="strong"><strong> $ curl -v http://localhost:9000/api/products</strong></span>
<span class="strong"><strong> * Hostname was NOT found in DNS cache</strong></span>
<span class="strong"><strong> * Trying ::1...</strong></span>
<span class="strong"><strong> * Connected to localhost (::1) port 9000 (#0)</strong></span>
<span class="strong"><strong> > GET /api/products HTTP/1.1</strong></span>
<span class="strong"><strong> > User-Agent: curl/7.37.1</strong></span>
<span class="strong"><strong> > Host: localhost:9000</strong></span>
<span class="strong"><strong> > Accept: */*</strong></span>
<span class="strong"><strong> ></strong></span>
<span class="strong"><strong> < HTTP/1.1 200 OK</strong></span>
<span class="strong"><strong> < Content-Type: application/json; charset=utf-8</strong></span>
<span class="strong"><strong> < Content-Length: 2</strong></span>
<span class="strong"><strong> <</strong></span>
<span class="strong"><strong> * Connection #0 to host localhost left intact</strong></span>
<span class="strong"><strong> []%</strong></span>
创建一个 PUT API 端点
在这个菜谱中,我们将使用 Play 2.0 实现一个 RESTful 的PUT
API 端点,以更新我们数据存储中的一个现有记录。
如何做…
对于 Java,我们需要执行以下步骤:
-
启用 Hot-Reloading 运行
foo_java
应用程序:<span class="strong"><strong> activator "~run"</strong></span>
-
通过添加以下动作来修改
foo_java/app/controllers/Products.java
:@BodyParser.Of(BodyParser.Json.class) public static Result edit(String id) { try { Product product = products.get(id); if (product != null) { Form<Product> form = Form.form(Product.class).bindFromRequest(); if (form.hasErrors()) { return badRequest(form.errorsAsJson()); } else { Product productForm = form.get(); product.setTitle(productForm.getTitle()); products.put(product.getSku(), product); return ok(toJson(product)); } } else { return notFound(); } } catch (Exception e) { return internalServerError(e.getMessage()); } }
-
在
foo_java/conf/routes
中为新增的操作添加一个新的路由:PUT /api/products/:id controllers.Products.edit(id: String)
-
使用
curl
,我们将更新我们数据存储中的一个现有产品:<span class="strong"><strong> $ curl -v -X PUT http://localhost:9000/api/products/def --data '{"sku":"def", "title":"iPad 3 Air"}' --header "Content-type: application/json"</strong></span> <span class="strong"><strong> * Hostname was NOT found in DNS cache</strong></span> <span class="strong"><strong> * Trying ::1...</strong></span> <span class="strong"><strong> * Connected to localhost (::1) port 9000 (#0)</strong></span> <span class="strong"><strong> > PUT /api/products/def HTTP/1.1</strong></span> <span class="strong"><strong> > User-Agent: curl/7.37.1</strong></span> <span class="strong"><strong> > Host: localhost:9000</strong></span> <span class="strong"><strong> > Accept: */*</strong></span> <span class="strong"><strong> > Content-type: application/json</strong></span> <span class="strong"><strong> > Content-Length: 35</strong></span> <span class="strong"><strong> ></strong></span> <span class="strong"><strong> * upload completely sent off: 35 out of 35 bytes</strong></span> <span class="strong"><strong> < HTTP/1.1 200 OK</strong></span> <span class="strong"><strong> < Content-Type: application/json; charset=utf-8</strong></span> <span class="strong"><strong> < Content-Length: 34</strong></span> <span class="strong"><strong> <</strong></span> <span class="strong"><strong> * Connection #0 to host localhost left intact</strong></span> <span class="strong"><strong> {"sku":"def","title":"iPad 3 Air"}%</strong></span>
对于 Scala,我们需要执行以下步骤:
-
启用 Hot-Reloading 运行
foo_scala
应用程序:<span class="strong"><strong> activator "~run"</strong></span>
-
通过添加以下动作来修改
foo_scala/app/controllers/Products.scala
:def edit(id: String) = Action(BodyParsers.parse.json) { implicit request => val post = request.body.validate[Product] post.fold( errors => BadRequest(Json.obj("message" -> JsError.toFlatJson(errors))), p => { products.find(_.sku equals id) match { case Some(product) => { try { products -= product products += p Ok(Json.toJson(p)) } catch { case e: Exception => InternalServerError(e.getMessage) } } case None => NotFound } } ) }
-
在
foo_scala/conf/routes
中为新增的动作添加一个新的路由:PUT /api/products/:id controllers.Products.edit(id: String)
-
使用
curl
,我们将更新我们数据存储中的现有产品:<span class="strong"><strong> $ curl -v -X PUT http://localhost:9000/api/products/def --data '{"sku":"def", "title":"iPad 3 Air"}' --header "Content-type: application/json"</strong></span> <span class="strong"><strong> * Hostname was NOT found in DNS cache</strong></span> <span class="strong"><strong> * Trying ::1...</strong></span> <span class="strong"><strong> * Connected to localhost (::1) port 9000 (#0)</strong></span> <span class="strong"><strong> > PUT /api/products/def HTTP/1.1</strong></span> <span class="strong"><strong> > User-Agent: curl/7.37.1</strong></span> <span class="strong"><strong> > Host: localhost:9000</strong></span> <span class="strong"><strong> > Accept: */*</strong></span> <span class="strong"><strong> > Content-type: application/json</strong></span> <span class="strong"><strong> > Content-Length: 35</strong></span> <span class="strong"><strong> ></strong></span> <span class="strong"><strong> * upload completely sent off: 35 out of 35 bytes</strong></span> <span class="strong"><strong> < HTTP/1.1 200 OK</strong></span> <span class="strong"><strong> < Content-Type: application/json; charset=utf-8</strong></span> <span class="strong"><strong> < Content-Length: 34</strong></span> <span class="strong"><strong> <</strong></span> <span class="strong"><strong> * Connection #0 to host localhost left intact</strong></span> <span class="strong"><strong> {"sku":"def","title":"iPad 3 Air"}%</strong></span>
它是如何工作的…
在本菜谱中,我们创建了一个新的 URL 路由和动作,该动作将更新我们数据存储中的现有记录。我们在产品控制器类中添加了一个新的动作,并在 conf/routes
中为它声明了一个新的路由。在我们的 edit
动作中,我们声明该动作期望请求体为 JSON 格式:
// Java
@BodyParser.Of(BodyParser.Json.class)
// Scala
def edit(id: String) = Action(BodyParsers.parse.json) { implicit request =>
我们通过在我们的数据存储中进行查找来检查传入的 ID 值是否有效。对于无效的 ID,我们发送 HTTP 状态码 404:
// Java
return notFound();
// Scala
case None => NotFound
我们还检查任何表单验证错误,并在出现错误时返回适当的状态码:
// Java
if (form.hasErrors()) {
return badRequest(form.errorsAsJson());
}
// Scala
errors => BadRequest(Json.obj("message" -> JsError.toFlatJson(errors))),
最后,我们使用 curl
测试了新的产品 PUT
动作。我们可以进一步通过测试它如何处理无效的 ID 和无效的请求体来验证 PUT
端点:
<span class="strong"><strong># Passing an invalid Product ID:</strong></span>
<span class="strong"><strong>$ curl -v -X PUT http://localhost:9000/api/products/XXXXXX</strong></span>
<span class="strong"><strong>* Hostname was NOT found in DNS cache</strong></span>
<span class="strong"><strong>* Trying ::1...</strong></span>
<span class="strong"><strong>* Connected to localhost (::1) port 9000 (#0)</strong></span>
<span class="strong"><strong>> PUT /api/products/XXXXXX HTTP/1.1</strong></span>
<span class="strong"><strong>> User-Agent: curl/7.37.1</strong></span>
<span class="strong"><strong>> Host: localhost:9000</strong></span>
<span class="strong"><strong>> Accept: */*</strong></span>
<span class="strong"><strong>></strong></span>
<span class="strong"><strong>< HTTP/1.1 404 Not Found</strong></span>
<span class="strong"><strong>< Content-Length: 0</strong></span>
<span class="strong"><strong><</strong></span>
<span class="strong"><strong>* Connection #0 to host localhost left intact</strong></span>
<span class="strong"><strong># PUT requests with form validation error</strong></span>
<span class="strong"><strong>$ curl -v -X PUT http://localhost:9000/api/products/def --data '{}' --header "Content-type: application/json"</strong></span>
<span class="strong"><strong>* Hostname was NOT found in DNS cache</strong></span>
<span class="strong"><strong>* Trying ::1...</strong></span>
<span class="strong"><strong>* Connected to localhost (::1) port 9000 (#0)</strong></span>
<span class="strong"><strong>> PUT /api/products/def HTTP/1.1</strong></span>
<span class="strong"><strong>> User-Agent: curl/7.37.1</strong></span>
<span class="strong"><strong>> Host: localhost:9000</strong></span>
<span class="strong"><strong>> Accept: */*</strong></span>
<span class="strong"><strong>> Content-type: application/json</strong></span>
<span class="strong"><strong>> Content-Length: 2</strong></span>
<span class="strong"><strong>></strong></span>
<span class="strong"><strong>* upload completely sent off: 2 out of 2 bytes</strong></span>
<span class="strong"><strong>< HTTP/1.1 400 Bad Request</strong></span>
<span class="strong"><strong>< Content-Type: application/json; charset=utf-8</strong></span>
<span class="strong"><strong>< Content-Length: 69</strong></span>
<span class="strong"><strong><</strong></span>
<span class="strong"><strong>* Connection #0 to host localhost left intact</strong></span>
<span class="strong"><strong>{"title":["This field is required"],"sku":["This field is required"]}%</strong></span>
创建 DELETE API 端点
在本菜谱中,我们将实现一个 RESTful DELETE
API 端点,从我们的数据存储中删除记录。
如何操作…
对于 Java,我们需要执行以下步骤:
-
以启用热重载的方式运行
foo_java
应用程序:<span class="strong"><strong> activator "~run"</strong></span>
-
通过添加以下动作修改
foo_java/app/controllers/Products.java
:public static Result delete(String id) { try { Product product = products.get(id); if (product != null) { products.remove(product); return noContent(); } else { return notFound(); } } catch (Exception e) { return internalServerError(e.getMessage()); } }
-
在
foo_java/conf/routes
中为新增的动作添加一个新的路由:DELETE /api/products/:id controllers.Products.delete(id: String)
-
使用
curl
,按照以下方式删除现有记录:<span class="strong"><strong> $ curl -v -X DELETE http://localhost:9000/api/products/def</strong></span> <span class="strong"><strong> * Hostname was NOT found in DNS cache</strong></span> <span class="strong"><strong> * Trying ::1...</strong></span> <span class="strong"><strong> * Connected to localhost (::1) port 9000 (#0)</strong></span> <span class="strong"><strong> > DELETE /api/products/def HTTP/1.1</strong></span> <span class="strong"><strong> > User-Agent: curl/7.37.1</strong></span> <span class="strong"><strong> > Host: localhost:9000</strong></span> <span class="strong"><strong> > Accept: */*</strong></span> <span class="strong"><strong> ></strong></span> <span class="strong"><strong> < HTTP/1.1 204 No Content</strong></span> <span class="strong"><strong> < Content-Length: 0</strong></span> <span class="strong"><strong> <</strong></span> <span class="strong"><strong> * Connection #0 to host localhost left intact</strong></span>
对于 Scala,我们需要执行以下步骤:
-
以启用热重载的方式运行
foo_scala
应用程序:<span class="strong"><strong> activator "~run"</strong></span>
-
通过添加以下动作修改
foo_scala/app/controllers/Products.scala
:def delete(id: String) = BasicAuthAction { products.find(_.sku equals id) match { case Some(product) => { try { products -= product NoContent } catch { case e: Exception => InternalServerError(e.getMessage) } } case None => NotFound } }
-
在
foo_scala/conf/routes
中为新增的动作添加一个新的路由:DELETE /api/products/:id controllers.Products.delete(id: String)
-
使用
curl
,按照以下方式删除现有记录:<span class="strong"><strong> $ curl -v -X DELETE http://localhost:9000/api/products/def</strong></span> <span class="strong"><strong> * Hostname was NOT found in DNS cache</strong></span> <span class="strong"><strong> * Trying ::1...</strong></span> <span class="strong"><strong> * Connected to localhost (::1) port 9000 (#0)</strong></span> <span class="strong"><strong> > DELETE /api/products/def HTTP/1.1</strong></span> <span class="strong"><strong> > User-Agent: curl/7.37.1</strong></span> <span class="strong"><strong> > Host: localhost:9000</strong></span> <span class="strong"><strong> > Accept: */*</strong></span> <span class="strong"><strong> ></strong></span> <span class="strong"><strong> < HTTP/1.1 204 No Content</strong></span> <span class="strong"><strong> < Content-Length: 0</strong></span> <span class="strong"><strong> <</strong></span> <span class="strong"><strong> * Connection #0 to host localhost left intact</strong></span>
它是如何工作的…
在本菜谱中,我们创建了一个新的 URL 路由和动作,用于删除现有的产品记录。我们声明了 delete
动作通过传入的 ID 参数来查找记录。我们确保在无效 ID 的情况下返回适当的 HTTP 状态码,在这种情况下,HTTP 状态码 404:
// Java
return notFound();
// Scala
case None => NotFound
我们确保返回适当的 HTTP 状态码以表示记录删除成功,在这种情况下,HTTP 状态码 204:
// Java
return noContent();
// Scala
NoContent
我们还可以测试 DELETE
端点并验证它是否正确处理无效的 ID:
<span class="strong"><strong> $ curl -v -X DELETE http://localhost:9000/api/products/XXXXXX</strong></span>
<span class="strong"><strong> * Hostname was NOT found in DNS cache</strong></span>
<span class="strong"><strong> * Trying ::1...</strong></span>
<span class="strong"><strong> * Connected to localhost (::1) port 9000 (#0)</strong></span>
<span class="strong"><strong> > DELETE /api/products/asd HTTP/1.1</strong></span>
<span class="strong"><strong> > User-Agent: curl/7.37.1</strong></span>
<span class="strong"><strong> > Host: localhost:9000</strong></span>
<span class="strong"><strong> > Accept: */*</strong></span>
<span class="strong"><strong> ></strong></span>
<span class="strong"><strong> < HTTP/1.1 404 Not Found</strong></span>
<span class="strong"><strong> < Content-Length: 0</strong></span>
<span class="strong"><strong> <</strong></span>
<span class="strong"><strong> * Connection #0 to host localhost left intact</strong></span>
使用 HTTP 基本认证保护 API 端点
在本菜谱中,我们将探讨如何使用 Play 2.0 的 HTTP 基本认证方案来保护 API 端点。我们将使用 Apache Commons Codec 库进行 Base64 编码和解码。这个依赖项被 Play 隐式导入,我们不需要在 build.sbt
的库依赖中显式声明它。
如何操作…
对于 Java,我们需要执行以下步骤:
-
以启用热重载的方式运行
foo_java
应用程序:<span class="strong"><strong> activator "~run"</strong></span>
-
在
foo_java/app/controllers/BasicAuthenticator.java
中创建一个新的play.mvc.Security.Authenticator
实现类,内容如下:package controllers; import org.apache.commons.codec.binary.Base64; import play.mvc.Http; import play.mvc.Result; import play.mvc.Security; public class BasicAuthenticator extends Security.Authenticator { private static final String AUTHORIZATION = "authorization"; private static final String WWW_AUTHENTICATE = "WWW-Authenticate"; private static final String REALM = "Basic realm=\"API Realm\""; @Override public String getUsername(Http.Context ctx) { try { String authHeader = ctx.request().getHeader(AUTHORIZATION); if (authHeader != null) { ctx.response().setHeader(WWW_AUTHENTICATE, REALM); String auth = authHeader.substring(6); byte[] decodedAuth = Base64.decodeBase64(auth); String[] credentials = new String(decodedAuth, "UTF-8").split(":"); if (credentials != null && credentials.length == 2) { String username = credentials[0]; String password = credentials[1]; if (isAuthenticated(username, password)) { return username; } else { return null; } } } return null; } catch (Exception e) { return null; } } private boolean isAuthenticated(String username, String password) { return username != null && username.equals("ned") && password != null && password.equals("flanders"); } @Override public Result onUnauthorized(Http.Context context) { return unauthorized(); } }
-
通过向 API 操作添加以下注解来修改
foo_java/app/controllers/Products.java
:<span class="strong"><strong> @Security.Authenticated(BasicAuthenticator.class)</strong></span> public static Result create() <span class="strong"><strong> @Security.Authenticated(BasicAuthenticator.class)</strong></span> public static Result index() <span class="strong"><strong> @Security.Authenticated(BasicAuthenticator.class)</strong></span> public static Result edit(String id) <span class="strong"><strong> @Security.Authenticated(BasicAuthenticator.class)</strong></span> public static Result delete(String id)
-
使用
curl
,发送一个请求到我们之前所做的现有 RESTfulGET
端点;你现在将看到一个未授权的响应:<span class="strong"><strong> $ curl -v http://localhost:9000/api/products</strong></span> <span class="strong"><strong> * Hostname was NOT found in DNS cache</strong></span> <span class="strong"><strong> * Trying 127.0.0.1...</strong></span> <span class="strong"><strong> * Connected to localhost (127.0.0.1) port 9000 (#0)</strong></span> <span class="strong"><strong> > GET /api/products HTTP/1.1</strong></span> <span class="strong"><strong> > User-Agent: curl/7.37.1</strong></span> <span class="strong"><strong> > Host: localhost:9000</strong></span> <span class="strong"><strong> > Accept: */*</strong></span> <span class="strong"><strong> ></strong></span> <span class="strong"><strong> < HTTP/1.1 401 Unauthorized</strong></span> <span class="strong"><strong> < Content-Length: 0</strong></span> <span class="strong"><strong> <</strong></span> <span class="strong"><strong> * Connection #0 to host localhost left intact</strong></span>
-
再次使用
curl
,发送另一个请求到现有的 RESTfulGET
端点,这次带有用户凭据,ned
(用户名)和flanders
(密码):<span class="strong"><strong> $ curl -v -u "ned:flanders" http://localhost:9000/api/products</strong></span> <span class="strong"><strong> * Hostname was NOT found in DNS cache</strong></span> <span class="strong"><strong> * Trying 127.0.0.1...</strong></span> <span class="strong"><strong> * Connected to localhost (127.0.0.1) port 9000 (#0)</strong></span> <span class="strong"><strong> * Server auth using Basic with user 'ned'</strong></span> <span class="strong"><strong> > GET /api/products HTTP/1.1</strong></span> <span class="strong"><strong> > Authorization: Basic bmVkOmZsYW5kZXJz</strong></span> <span class="strong"><strong> > User-Agent: curl/7.37.1</strong></span> <span class="strong"><strong> > Host: localhost:9000</strong></span> <span class="strong"><strong> > Accept: */*</strong></span> <span class="strong"><strong> ></strong></span> <span class="strong"><strong> < HTTP/1.1 200 OK</strong></span> <span class="strong"><strong> < Content-Type: application/json; charset=utf-8</strong></span> <span class="strong"><strong> < WWW-Authenticate: Basic realm="API Realm"</strong></span> <span class="strong"><strong> < Content-Length: 2</strong></span> <span class="strong"><strong> <</strong></span> <span class="strong"><strong> * Connection #0 to host localhost left intact</strong></span> <span class="strong"><strong> {}%</strong></span>
对于 Scala,我们需要执行以下步骤:
-
启用热重载功能运行
foo_scala
应用程序:activator "~run"
-
在
foo_scala/app/controllers/BasicAuthAction.scala
中创建一个新的ActionBuilder
类,并添加以下内容:package controllers import controllers.Products._ import org.apache.commons.codec.binary.Base64 import play.api.mvc._ import scala.concurrent.Future object BasicAuthAction extends ActionBuilder[Request] { def invokeBlockA => Future[Result]) = { try { request.headers.get("authorization") match { case Some(headers) => { val auth = headers.substring(6) val decodedAuth = Base64.decodeBase64(auth) val credentials = new String(decodedAuth, "UTF-8").split(":") if (credentials != null && credentials.length == 2 && isAuthenticated(credentials(0), credentials(1))) { block(request) } else { unauthorized } } case None => unauthorized } } catch { case e: Exception => Future.successful(InternalServerError(e.getMessage)) } } def unauthorized = Future.successful(Unauthorized.withHeaders("WWW-Authenticate" -> "Basic realm=\"API Realm\"")) def isAuthenticated(username: String, password: String) = username != null && username.equals("ned") && password != null && password.equals("flanders") }
-
通过添加新创建的
ActionBuilder
类和 API 操作来修改foo_scala/app/controllers/Products.scala
:def index = BasicAuthAction def create = BasicAuthAction(BodyParsers.parse.json) def edit(id: String) = BasicAuthAction(BodyParsers.parse.json) def delete(id: String) = BasicAuthAction
-
使用
curl
,发送一个请求到我们之前所做的现有 RESTfulGET
端点;你现在将看到一个未授权的响应:<span class="strong"><strong> $ curl -v http://localhost:9000/api/products</strong></span> <span class="strong"><strong> * Hostname was NOT found in DNS cache</strong></span> <span class="strong"><strong> * Trying 127.0.0.1...</strong></span> <span class="strong"><strong> * Connected to localhost (127.0.0.1) port 9000 (#0)</strong></span> <span class="strong"><strong> > GET /api/products HTTP/1.1</strong></span> <span class="strong"><strong> > User-Agent: curl/7.37.1</strong></span> <span class="strong"><strong> > Host: localhost:9000</strong></span> <span class="strong"><strong> > Accept: */*</strong></span> <span class="strong"><strong> > </strong></span> <span class="strong"><strong> < HTTP/1.1 401 Unauthorized</strong></span> <span class="strong"><strong> < Content-Length: 0</strong></span> <span class="strong"><strong> <</strong></span> <span class="strong"><strong> * Connection #0 to host localhost left intact</strong></span>
-
再次使用
curl
,发送另一个请求到现有的 RESTfulGET
端点,这次带有用户凭据,ned
(用户名)和flanders
(密码):<span class="strong"><strong> $ curl -v -u "ned:flanders" http://localhost:9000/api/products</strong></span> <span class="strong"><strong> * Hostname was NOT found in DNS cache</strong></span> <span class="strong"><strong> * Trying 127.0.0.1...</strong></span> <span class="strong"><strong> * Connected to localhost (127.0.0.1) port 9000 (#0)</strong></span> <span class="strong"><strong> * Server auth using Basic with user 'ned'</strong></span> <span class="strong"><strong> > GET /api/products HTTP/1.1</strong></span> <span class="strong"><strong> > Authorization: Basic bmVkOmZsYW5kZXJz</strong></span> <span class="strong"><strong> > User-Agent: curl/7.37.1</strong></span> <span class="strong"><strong> > Host: localhost:9000</strong></span> <span class="strong"><strong> > Accept: */*</strong></span> <span class="strong"><strong> ></strong></span> <span class="strong"><strong> < HTTP/1.1 200 OK</strong></span> <span class="strong"><strong> < Content-Type: application/json; charset=utf-8</strong></span> <span class="strong"><strong> < Content-Length: 2</strong></span> <span class="strong"><strong> <</strong></span> <span class="strong"><strong> * Connection #0 to host localhost left intact</strong></span> <span class="strong"><strong> {}%</strong></span>
它是如何工作的…
在这个菜谱中,我们使用 Play 2.0 的 HTTP 基本认证方案对 RESTful API 端点进行了安全保护。我们为 Java 和 Scala 创建了相应的安全实现类。对于每个安全实现类,BasicAuthenticator.java
和BasicAuthAction.scala
,我们检索了授权头并解码了值字符串以解密我们传递的用户凭据:
// Java
String authHeader = ctx.request().getHeader(AUTHORIZATION);
if (authHeader != null) {
ctx.response().setHeader(WWW_AUTHENTICATE, REALM);
String auth = authHeader.substring(6);
byte[] decodedAuth = Base64.decodeBase64(auth);
String[] credentials = new String(decodedAuth, "UTF-8").split(":");
}
// Scala
request.headers.get("authorization") match {
case Some(headers) => {
val auth = headers.substring(6)
val decodedAuth = Base64.decodeBase64(auth)
val credentials = new String(decodedAuth, "UTF-8").split(":")
}
}
一旦我们获得了用户名和密码,我们就调用了isAuthenticated
函数来检查用户凭据的有效性:
// Java
if (credentials != null && credentials.length == 2) {
String username = credentials[0];
String password = credentials[1];
if (isAuthenticated(username, password)) {
return username;
} else {
return null;
}
}
// Scala
if (credentials != null && credentials.length == 2 &&
isAuthenticated(credentials(0), credentials(1))) {
block(request)
} else {
unauthorized
}
我们通过注解 Java API 操作并声明为 API 操作类来利用安全实现类:
// Java
@Security.Authenticated(BasicAuthenticator.class)
public static Result index() {
return ok(toJson(products));
}
// Scala
def index = BasicAuthAction {
Ok(toJson(products))
}
使用curl
,我们还可以检查我们的安全 API 操作是否处理未认证的请求:
<span class="strong"><strong> $ curl -v http://localhost:9000/api/products</strong></span>
<span class="strong"><strong> * Hostname was NOT found in DNS cache</strong></span>
<span class="strong"><strong> * Trying ::1...</strong></span>
<span class="strong"><strong> * Connected to localhost (::1) port 9000 (#0)</strong></span>
<span class="strong"><strong> > GET /api/products HTTP/1.1</strong></span>
<span class="strong"><strong> > User-Agent: curl/7.37.1</strong></span>
<span class="strong"><strong> > Host: localhost:9000</strong></span>
<span class="strong"><strong> > Accept: */*</strong></span>
<span class="strong"><strong> ></strong></span>
<span class="strong"><strong> < HTTP/1.1 401 Unauthorized</strong></span>
<span class="strong"><strong> < WWW-Authenticate: Basic realm="API Realm"</strong></span>
<span class="strong"><strong> < Content-Length: 0</strong></span>
<span class="strong"><strong> <</strong></span>
<span class="strong"><strong> * Connection #0 to host localhost left intact</strong></span>
消费外部 Web API
在这个菜谱中,我们将探索 Play WS API,从 Play 2 Web 应用程序中消费外部 Web 服务。随着 Web 应用程序需求的发展,我们对外部数据服务的依赖性越来越大,例如外汇汇率、实时天气数据等。Play WS 库为我们提供了与外部 Web 服务接口的 API。
如何做到这一点…
对于 Java,我们需要执行以下步骤:
-
启用热重载功能运行
foo_java
应用程序:<span class="strong"><strong> activator "~run"</strong></span>
-
在
build.sbt
中将playWs
声明为项目依赖项:libraryDependencies ++= Seq( javaWs )
-
在
foo_java/app/controllers/WebClient.java
中创建一个新的控制器并添加以下内容:package controllers; import com.fasterxml.jackson.databind.JsonNode; import play.libs.F; import play.libs.F.Promise; import play.libs.ws.WS; import play.mvc.Controller; import play.mvc.Result; public class WebClient extends Controller { public static Promise<Result> getTodos() { Promise<play.libs.ws.WSResponse> todos = WS.url("http://jsonplaceholder.typicode.com/todos").get(); return todos.map( new F.Function<play.libs.ws.WSResponse, Result>() { public Result apply(play.libs.ws.WSResponse res) { JsonNode json = res.asJson(); return ok("Todo Title: " + json.findValuesAsText("title")); } } ); } }
-
在
foo_java/conf/routes
中为新增的操作添加一个新的路由条目:GET /client/get_todos controllers.WebClient.getTodos
-
使用
curl
,我们将能够测试我们的新操作如何消费外部 Web API:<span class="strong"><strong> $ curl -v http://localhost:9000/client/get_todos</strong></span> <span class="strong"><strong> * Hostname was NOT found in DNS cache</strong></span> <span class="strong"><strong> * Trying ::1...</strong></span> <span class="strong"><strong> * Connected to localhost (::1) port 9000 (#0)</strong></span> <span class="strong"><strong> > GET /client/get_todos HTTP/1.1</strong></span> <span class="strong"><strong> > User-Agent: curl/7.37.1</strong></span> <span class="strong"><strong> > Host: localhost:9000</strong></span> <span class="strong"><strong> > Accept: */*</strong></span> <span class="strong"><strong> ></strong></span> <span class="strong"><strong> < HTTP/1.1 200 OK</strong></span> <span class="strong"><strong> < Content-Type: text/plain; charset=utf-8</strong></span> <span class="strong"><strong> < Content-Length: 8699</strong></span> <span class="strong"><strong> <</strong></span> <span class="strong"><strong> Todo Title: [delectus aut autem, quis ut nam facilis et officia qui, fugiat veniam minus, et porro tempora, laboriosam mollitia et enim quasi adipisci quia provident illum, qui ullam ratione quibusdam voluptatem quia omnis, illo expedita</strong></span>
对于 Scala,我们需要执行以下步骤:
-
启用热重载功能运行
foo_scala
应用程序:<span class="strong"><strong> activator "~run"</strong></span>
-
在
build.sbt
中将playWs
声明为项目依赖项:libraryDependencies ++= Seq( ws )
-
在
foo_scala/app/controllers/WebClient.scala
中创建一个新的控制器并添加以下内容:package controllers import play.api.libs.concurrent.Execution.Implicits.defaultContext import play.api.Play.current import play.api.libs.ws.WS import play.api.mvc.{Action, Controller} object WebClient extends Controller { def getTodos = Action.async { WS.url("http://jsonplaceholder.typicode.com/todos").get().map { res => Ok("Todo Title: " + (res.json \\ "title").map(_.as[String])) } } }
-
在
foo_scala/conf/routes
中为新增的操作添加一个新的路由条目:GET /client/get_todos controllers.WebClient.getTodos
-
使用
curl
,我们将能够测试我们的新操作如何消费外部 Web API:<span class="strong"><strong> $ curl -v http://localhost:9000/client/get_todos</strong></span> <span class="strong"><strong> * Hostname was NOT found in DNS cache</strong></span> <span class="strong"><strong> * Trying ::1...</strong></span> <span class="strong"><strong> * Connected to localhost (::1) port 9000 (#0)</strong></span> <span class="strong"><strong> > GET /client/get_todos HTTP/1.1</strong></span> <span class="strong"><strong> > User-Agent: curl/7.37.1</strong></span> <span class="strong"><strong> > Host: localhost:9000</strong></span> <span class="strong"><strong> > Accept: */*</strong></span> <span class="strong"><strong> ></strong></span> <span class="strong"><strong> < HTTP/1.1 200 OK</strong></span> <span class="strong"><strong> < Content-Type: text/plain; charset=utf-8</strong></span> <span class="strong"><strong> < Content-Length: 8699</strong></span> <span class="strong"><strong> <</strong></span> <span class="strong"><strong> Todo Title: [delectus aut autem, quis ut nam facilis et officia qui, fugiat veniam minus, et porro tempora, laboriosam mollitia et enim quasi adipisci quia provident illum, qui ullam ratione quibusdam voluptatem quia omnis, illo expedita</strong></span>
它是如何工作的…
在本菜谱中,我们利用了 Play 2.0 插件 WS 来消费外部 Web API。我们创建了一个新的路由和AsynchronousAction
方法。在操作中,我们将外部 API 的 URL 传递给 WS api,并指定它将是一个GET
操作:
// Java
Promise<play.libs.ws.WSResponse> todos = WS.url("http://jsonplaceholder.typicode.com/todos").get();
// Scala
WS.url("http://jsonplaceholder.typicode.com/todos").get()
我们然后解析了 JSON 响应,并将其管道输入到新创建的路由/client/get_todos
的结果响应中:
// Java
return todos.map(
new F.Function<play.libs.ws.WSResponse, Result>() {
public Result apply(play.libs.ws.WSResponse res) {
JsonNode json = res.asJson();
return ok("Todo Title: " + json.findValuesAsText("title"));
}
}
);
// Scala
Ok("Todo Title: " + (res.json \\ "title").map(_.as[String]))
使用 Twitter API 和 OAuth
在本菜谱中,我们将探讨如何使用 Play 2.0 的内置 OAuth 支持从 Twitter API 检索推文。
如何做到这一点…
对于 Java,我们需要执行以下步骤:
-
启用 Hot-Reloading 运行
foo_java
应用程序:<span class="strong"><strong> activator "~run"</strong></span>
-
在
foo_java/conf/application.conf
中指定您的 Twitter API 信息:tw.consumerKey="YOUR TWITTER DEV CONSUMER KEY HERE" tw.consumerSecret="YOUR TWITTER DEV CONSUMER SECRET HERE" tw.accessToken="YOUR TWITTER DEV ACCESS TOKEN HERE" tw.accessTokenSecret="YOUR TWITTER DEV ACCESS TOKEN SECRET HERE"
-
在
foo_java/app/controllers/WebClient.java
中修改WebClient
控制器,按照以下操作:// Add additional imports at the top section of the class file import play.Play; import play.libs.oauth.OAuth; import play.libs.oauth.OAuth.OAuthCalculator; import play.libs.ws.WSResponse; import java.util.HashMap; import java.util.Iterator; import java.util.Map; // Add the Action method public static Promise<Result> getTweets(String hashtag) { final String url = "https://api.twitter.com/1.1/search/tweets.json?q=%40" + hashtag; final OAuth.ConsumerKey consumerInfo = new OAuth.ConsumerKey( Play.application().configuration().getString("tw.consumerKey"), Play.application().configuration().getString("tw.consumerSecret") ); final OAuth.RequestToken tokens = new OAuth.RequestToken( Play.application().configuration().getString("tw.accessToken"), Play.application().configuration().getString("tw.accessTokenSecret") ); Promise<play.libs.ws.WSResponse> twRequest = WS.url(url).sign(new OAuthCalculator(consumerInfo, tokens)).get(); return twRequest.map( new F.Function<WSResponse, Result>(){ @Override public Result apply(WSResponse res) throws Throwable { Map<String, String> map = new HashMap<String, String>(); JsonNode root = res.asJson(); for (JsonNode json : root.get("statuses")) { map.put( json.findValue("user").findValue("screen_name").asText(), json.findValue("text").asText() ); } return ok(views.html.tweets.render(map)); } } ); }
-
在
foo_java/conf/routes
中为getTweets(hashtag: String)
操作添加新的路由:GET /client/get_tweets/:hashtag controllers.WebClient.getTweets(hashtag)
-
在
foo_java/app/views/tweets.scala.html
中添加一个新的视图模板,内容如下:@(tweets: Map[String, String]) <ul> @tweets.map { tw => <li><strong>@@@tw._1</strong> says <i>"@tw._2"</i></li> } </ul>
-
使用网络浏览器访问
/client/get_tweets/:hashtag
路由以查看从 Twitter API 检索的推文:https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/play-fw-cb/img/gqMspVre.jpg
对于 Scala,我们需要执行以下步骤:
-
启用 Hot-Reloading 运行
foo_scala
应用程序:<span class="strong"><strong> activator "~run"</strong></span>
-
在
foo_scala/conf/application.conf
中指定您的 Twitter API 信息:tw.consumerKey="YOUR TWITTER DEV CONSUMER KEY HERE" tw.consumerSecret="YOUR TWITTER DEV CONSUMER SECRET HERE" tw.accessToken="YOUR TWITTER DEV ACCESS TOKEN HERE" tw.accessTokenSecret="YOUR TWITTER DEV ACCESS TOKEN SECRET HERE"
-
在
foo_scala/app/controllers/WebClient.scala
中修改WebClient
控制器,按照以下操作:def getTweets(hashtag: String) = Action.async { import play.api.Play val consumerInfo = ConsumerKey( Play.application.configuration.getString("tw.consumerKey").get, Play.application.configuration.getString("tw.consumerSecret").get ) val tokens = RequestToken( Play.application.configuration.getString("tw.accessToken").get, Play.application.configuration.getString("tw.accessTokenSecret").get ) val url = "https://api.twitter.com/1.1/search/tweets.json?q=%40" + hashtag WS.url(url).sign(OAuthCalculator(consumerInfo, tokens)).get().map { res => val tweets = ListBuffer[(String, String)]() (res.json \ "statuses").as[List[JsObject]].map { tweet => tweets += (( (tweet \ "user" \ "screen_name").as[String], (tweet \ "text").as[String] )) } Ok(views.html.tweets(tweets.toList)) } }
-
在
foo_scala/conf/routes
中为getTweets(hashtag: String)
操作添加新的路由:GET /client/get_tweets/:hashtag controllers.WebClient.getTweets(hashtag)
-
在
foo_scala/app/views/tweets.scala.html
中添加一个新的视图模板,内容如下:@(tweets: List[(String, String)]) <ul> @tweets.map { tw => <li><strong>@@@tw._1</strong> says <i>"@tw._2"</i></li> } </ul>
-
使用网络浏览器访问
/client/get_tweets/:hashtag
路由以查看从 Twitter API 检索的推文,如下截图所示:https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/play-fw-cb/img/FmkrgheD.jpg
它是如何工作的…
在本菜谱中,我们创建了一个新的 URL 路由和操作来检索和显示由请求路由/client/get_tweets/:hashtag
中指定的 hashtag 标记的推文。我们通过从conf/application.conf
(记得在dev.twitter.com
注册 Twitter 开发者账户并为本菜谱生成消费者和访问令牌)检索所需的 Twitter API 消费者和访问令牌密钥来实现操作方法:
// Java
final OAuth.ConsumerKey consumerInfo = new OAuth.ConsumerKey(
Play.application().configuration().getString("tw.consumerKey"),
Play.application().configuration().getString("tw.consumerSecret")
);
final OAuth.RequestToken tokens = new OAuth.RequestToken(
Play.application().configuration().getString("tw.accessToken"),
Play.application().configuration()
.getString("tw.accessTokenSecret")
);
// Scala
val consumerInfo = ConsumerKey(
Play.application.configuration.getString("tw.consumerKey").get,
Play.application.configuration.getString("tw.consumerSecret").get
)
val tokens = RequestToken(
Play.application.configuration.getString("tw.accessToken").get,
Play.application.configuration.getString("tw.accessTokenSecret").get
)
我们将这些凭证传递给 Play 类OAuthCalculator
,当我们访问 Twitter 搜索 API 端点时:
// Java
Promise<play.libs.ws.WSResponse> twRequest =
WS.url(url).sign(new OAuthCalculator(consumerInfo, tokens)).get();
// Scala
WS.url(url).sign(OAuthCalculator(consumerInfo, tokens)).get()
一旦 Twitter API 响应返回,我们解析响应的 JSON 并将其推送到一个中间集合对象,然后将其传递给我们的视图模板:
// Java
Map<String, String> map = new HashMap<String, String>();
JsonNode root = res.asJson();
for (JsonNode json : root.get("statuses")) {
map.put(
json.findValue("user")
.findValue("screen_name").asText(),
json.findValue("text").asText()
);
}
return ok(views.html.tweets.render(map));
// Scala
val tweets = ListBuffer[(String, String)]()
(res.json \ "statuses").as[List[JsObject]].map { tweet =>
tweets += ((
(tweet \ "user" \ "screen_name").as[String],
(tweet \ "text").as[String]
))
}
Ok(views.html.tweets(tweets.toList))
更多推荐
所有评论(0)