原文:zh.annas-archive.org/md5/3898a199ef873c2cd80ef5c3269070bb

译者:飞龙

协议:CC BY-NC-SA 4.0

第三章:利用模块

在本章中,我们将介绍以下菜谱:

  • 使用 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,我们需要采取以下步骤:

  1. 启用热重载运行foo_java应用程序:

    <span class="strong"><strong>    activator "~run"</strong></span>
    
  2. 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"
    
  3. 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());
          }
        }
    
  4. 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";
          }
        }
    
  5. foo_java/conf/routes中为新增的操作添加一个新的路由条目:

    GET     /admins     @controllers.AdminController.index
    
  6. 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 &lt;A&gt; A getControllerInstance(Class&lt;A&gt; clazz) {
            return ctx.getBean(clazz);
          }
        }
    
  7. 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 {
        }
    
  8. 请求我们的新路由并检查响应体以确认:

    <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>    &gt; GET /admins HTTP/1.1</strong></span>
    <span class="strong"><strong>    &gt; User-Agent: curl/7.37.1</strong></span>
    <span class="strong"><strong>    &gt; Host: localhost:9000</strong></span>
    <span class="strong"><strong>    &gt; Accept: */*</strong></span>
    <span class="strong"><strong>    &gt;</strong></span>
    <span class="strong"><strong>    &lt; HTTP/1.1 200 OK</strong></span>
    <span class="strong"><strong>    &lt; Content-Type: text/plain; charset=utf-8</strong></span>
    <span class="strong"><strong>    &lt; Content-Length: 35</strong></span>
    <span class="strong"><strong>    &lt;</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,我们需要采取以下步骤:

  1. 启用热重载运行foo_scala应用程序:

    <span class="strong"><strong>    activator "~run"</strong></span>
    
  2. build.sbt中将 Spring 声明为项目依赖项:

    "org.springframework" % "spring-context" % "3.2.2.RELEASE"
    
  3. 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))
             }
           }
    
  4. foo_scala/app/services/AdminServices.scala中创建一个管理服务类,内容如下:

    package services
         class AdminService {
          def foo = "foo"
        }
    
  5. foo_scala/conf/routes中为新增的操作添加一个新的路由条目:

    GET     /admins                    @controllers.AdminController.index
    
  6. 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)
          }
        }
    
  7. 添加 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
        }
    
  8. 请求我们的新路由并检查响应体以确认:

    <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>    &gt; GET /admins HTTP/1.1</strong></span>
    <span class="strong"><strong>    &gt; User-Agent: curl/7.37.1</strong></span>
    <span class="strong"><strong>    &gt; Host: 0.0.0.0:9000</strong></span>
    <span class="strong"><strong>    &gt; Accept: */*</strong></span>
    <span class="strong"><strong>    &gt;</strong></span>
    <span class="strong"><strong>    &lt; HTTP/1.1 200 OK</strong></span>
    <span class="strong"><strong>    &lt; Content-Type: text/plain; charset=utf-8</strong></span>
    <span class="strong"><strong>    &lt; Content-Length: 35</strong></span>
    <span class="strong"><strong>    &lt;</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,我们需要采取以下步骤:

  1. 启用 Hot-Reloading 运行foo_java应用程序:

    <span class="strong"><strong>    activator "~run"</strong></span>
    
  2. build.sbt中将guice模块声明为项目依赖项:

    "com.google.inject" % "guice" % "3.0"
    
  3. 通过修改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 &lt;T&gt; T getControllerInstance(Class&lt;T&gt; clazz) {
            return injector.getInstance(clazz);
          }
       }
    
  4. 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()));
          }
        }
    
  5. foo_java/app/services/CategoryService.java中创建一个分类服务接口,通过添加以下内容:

    package services;
         import java.util.List;
         public interface CategoryService {
          List&lt;String&gt; list();
        }
    
  6. foo_java/app/services/CategoryServicesImpl.java中创建一个分类服务实现类,通过添加以下内容:

    package services;
         import java.util.Arrays;
        import java.util.List;
         public class CategoryServiceImpl implements CategoryService {
          @Override
          public List&lt;String&gt; list() {
            return Arrays.asList(new String[] {"Manager", "Employee", "Contractor"});
          }
        }
    
  7. foo_java/conf/routes中为新增的操作添加一个新的路由条目:

    GET  /categories    @controllers.CategoryController.index
    
  8. 请求我们的新路由并检查响应头以确认我们对 HTTP 响应头的修改:

    $ curl -v http://localhost:9000/categories
        * Hostname was NOT found in DNS cache
        *   Trying ::1...
        * Connected to localhost (::1) port 9000 (#0)
        &gt; GET /categories HTTP/1.1
        &gt; User-Agent: curl/7.37.1
        &gt; Host: localhost:9000
        &gt; Accept: */*
        &gt;
        &lt; HTTP/1.1 200 OK
        &lt; Content-Type: application/json; charset=utf-8
        &lt; Content-Length: 35
        &lt;
        * Connection #0 to host localhost left intact
        ["Manager","Employee","Contractor"]%
    

对于 Scala,我们需要采取以下步骤:

  1. 启用 Hot-Reloading 运行foo_scala应用程序:

    <span class="strong"><strong>    activator "~run"</strong></span>
    
  2. build.sbt中将securesocial模块声明为项目依赖项:

    "com.google.inject" % "guice" % "3.0"
    
  3. 通过修改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)
          }
        }
    
  4. 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))
          }
           }
    
  5. 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")
        }
    
  6. foo_scala/conf/routes中为新增的操作添加一个新的路由条目:

    GET  /categories    @controllers.CategoryController.index
    
  7. 请求我们的新路由并检查响应头以确认我们对 HTTP 响应头的修改:

    $ curl -v http://localhost:9000/categories
        * Hostname was NOT found in DNS cache
        *   Trying ::1...
        * Connected to localhost (::1) port 9000 (#0)
        &gt; GET /categories HTTP/1.1
        &gt; User-Agent: curl/7.37.1
        &gt; Host: localhost:9000
        &gt; Accept: */*
        &gt;
        &lt; HTTP/1.1 200 OK
        &lt; Content-Type: application/json; charset=utf-8
        &lt; Content-Length: 35
        &lt;
        * 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

如何操作…

让我们采取以下步骤:

  1. 启用 Hot-Reloading 运行foo_scala应用程序:

    <span class="strong"><strong>    activator "~run"</strong></span>
    
  2. build.sbt中将 play-plugins-salat 声明为项目依赖项:

    "se.radley" %% "play-plugins-salat" % "1.5.0"
    
  3. 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"
    
  4. foo_scala/conf/play.plugins中声明 salat 插件:

    500:se.radley.plugin.salat.SalatPlugin
    
  5. foo_scala/conf/application.conf中声明 MongoDB 实例信息:

    mongodb.default.db = "cookbookdb"
    
  6. 通过添加以下内容修改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 =&gt;
            val post = request.body.validate[Warehouse]
            post.fold(
              errors =&gt; {
                BadRequest(Json.obj("status" -&gt;"error", "message" -&gt; JsError.toFlatJson(errors)))
              },
              warehouse =&gt; {
                Warehouse.create(warehouse)
                Created(Json.toJson(warehouse))
              }
            )
          }
        }
    
  7. 将新添加的操作的新路由添加到 foo_scala/conf/routes

    GET     /warehouses  controllers.WarehouseController.index
        POST    /warehouses      controllers.WarehouseController.create
    
  8. 将仓库模型的集合映射添加到 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)
        }
    
  9. 将 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
          }
        }
    
  10. 通过使用 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>    &gt; POST /warehouses HTTP/1.1</strong></span>
    <span class="strong"><strong>    &gt; User-Agent: curl/7.37.1</strong></span>
    <span class="strong"><strong>    &gt; Host: localhost:9000</strong></span>
    <span class="strong"><strong>    &gt; Accept: */*</strong></span>
    <span class="strong"><strong>    &gt; Content-type: application/json</strong></span>
    <span class="strong"><strong>    &gt; Content-Length: 48</strong></span>
    <span class="strong"><strong>    &gt;</strong></span>
    <span class="strong"><strong>    * upload completely sent off: 48 out of 48 bytes</strong></span>
    <span class="strong"><strong>    &lt; HTTP/1.1 201 Created</strong></span>
    <span class="strong"><strong>    &lt; Content-Type: application/json; charset=utf-8</strong></span>
    <span class="strong"><strong>    &lt; Content-Length: 47</strong></span>
    <span class="strong"><strong>    &lt;</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>
    
  11. 通过使用 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>    &gt; GET /warehouses HTTP/1.1</strong></span>
    <span class="strong"><strong>    &gt; User-Agent: curl/7.37.1</strong></span>
    <span class="strong"><strong>    &gt; Host: localhost:9000</strong></span>
    <span class="strong"><strong>    &gt; Accept: */*</strong></span>
    <span class="strong"><strong>    &gt;</strong></span>
    <span class="strong"><strong>    &lt; HTTP/1.1 200 OK</strong></span>
    <span class="strong"><strong>    &lt; Content-Type: application/json; charset=utf-8</strong></span>
    <span class="strong"><strong>    &lt; Content-Length: 241</strong></span>
    <span class="strong"><strong>    &lt;</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。

如何操作…

让我们采取以下步骤:

  1. 以启用热重载的方式运行 foo_scala 应用程序:

    <span class="strong"><strong>    activator "~run"</strong></span>
    
  2. 通过添加以下内容修改 foo_scala/app/controllers/WarehouseController.scala

    import java.text.SimpleDateFormat
        import play.api.libs.iteratee.Enumerator
         def upload = Action(parse.multipartFormData) { request =&gt;
            request.body.file("asset") match {
              case Some(asset) =&gt; {
                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 =&gt; {
                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" -&gt; id)) match {
              case Some(f) =&gt; Result(
                ResponseHeader(OK, Map(
                  CONTENT_LENGTH -&gt; f.length.toString,
                  CONTENT_TYPE -&gt; f.contentType.getOrElse(BINARY),
                  DATE -&gt; new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss 'GMT'", java.util.Locale.US).format(f.uploadDate)
                )),
                Enumerator.fromStream(f.inputStream)
              )
               case None =&gt; NotFound
            }
          }
    
  3. 将新添加的操作的新路由添加到 foo_scala/conf/routes

    POST /warehouses/assets/upload controllers.WarehouseController.upload
       GET  /warehouses/assets/:id controllers.WarehouseController.retrieveFile(id: ObjectId)
    
  4. 修改仓库模型在 foo_scala/app/models/Warehouse.scala 中的集合映射:

    val assets = gridFS("assets")
         def upload(asset: File) = {
          assets.createFile(asset)
        }
         def retrieve(filename: String) = {
          assets.find(filename)
        }
    
  5. 通过使用 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>    &gt; POST /warehouses/assets/upload HTTP/1.1</strong></span>
    <span class="strong"><strong>    &gt; User-Agent: curl/7.37.1</strong></span>
    <span class="strong"><strong>    &gt; Host: localhost:9000</strong></span>
    <span class="strong"><strong>    &gt; Accept: */*</strong></span>
    <span class="strong"><strong>    &gt; Content-Length: 13583</strong></span>
    <span class="strong"><strong>    &gt; Expect: 100-continue</strong></span>
    <span class="strong"><strong>    &gt; Content-Type: multipart/form-data; boundary=------------------------4a001bdeff39c089</strong></span>
    <span class="strong"><strong>    &gt;</strong></span>
    <span class="strong"><strong>    &lt; HTTP/1.1 100 Continue</strong></span>
    <span class="strong"><strong>    &lt; HTTP/1.1 200 OK</strong></span>
    <span class="strong"><strong>    &lt; Content-Type: text/plain; charset=utf-8</strong></span>
    <span class="strong"><strong>    &lt; Content-Length: 86</strong></span>
    <span class="strong"><strong>    &lt;</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>
    
  6. 通过在 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,我们需要采取以下步骤:

  1. 以启用热重载的方式运行 foo_java 应用程序:

    <span class="strong"><strong>    activator "~run"</strong></span>
    
  2. build.sbt 中将 Redis 声明为项目依赖项:

    "com.typesafe.play.plugins" %% "play-plugins-redis" % "2.3.1"
    
  3. build.sbt 中声明托管 Sedis 的存储库,它是 play-plugins-redis 的库依赖项:

    resolvers += "Sedis repository" at "http://pk11-scratch.googlecode.com/svn/trunk/"
    
  4. 通过在 foo_java/conf/play.plugins 中声明它来启用 play-mailer 插件:

    550:com.typesafe.plugin.RedisPlugin
    
  5. foo_java/conf/application.conf 中指定您的 Redis 主机信息:

    ehcacheplugin=disabled
        redis.uri="redis://127.0.0.1:6379"
    
  6. 通过在 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 &amp;&amp; value.trim().length() &gt; 0) {
               return ok("Retrieved from Cache: " + value);
             } else {
               Cache.set(key, "Let's Play with Redis!");
               return ok("Setting key value in the cache");
             } 
           }
    
  7. foo_java/conf/routes 中为新增的动作添加一个新的路由条目:

    GET     /cache  controllers.Application.displayFromCache
    
  8. 请求我们的新路由并检查响应体以确认我们的 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>    &gt; GET /cache HTTP/1.1</strong></span>
    <span class="strong"><strong>    &gt; User-Agent: curl/7.37.1</strong></span>
    <span class="strong"><strong>    &gt; Host: localhost:9000</strong></span>
    <span class="strong"><strong>    &gt; Accept: */*</strong></span>
    <span class="strong"><strong>    &gt;</strong></span>
    <span class="strong"><strong>    &lt; HTTP/1.1 200 OK</strong></span>
    <span class="strong"><strong>    &lt; Content-Type: text/plain; charset=utf-8</strong></span>
    <span class="strong"><strong>    &lt; Content-Length: 30</strong></span>
    <span class="strong"><strong>    &lt;</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>
    
  9. 再次请求 /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>    &gt; GET /cache HTTP/1.1</strong></span>
    <span class="strong"><strong>    &gt; User-Agent: curl/7.37.1</strong></span>
    <span class="strong"><strong>    &gt; Host: localhost:9000</strong></span>
    <span class="strong"><strong>    &gt; Accept: */*</strong></span>
    <span class="strong"><strong>    &gt;</strong></span>
    <span class="strong"><strong>    &lt; HTTP/1.1 200 OK</strong></span>
    <span class="strong"><strong>    &lt; Content-Type: text/plain; charset=utf-8</strong></span>
    <span class="strong"><strong>    &lt; Content-Length: 43</strong></span>
    <span class="strong"><strong>    &lt;</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,我们需要采取以下步骤:

  1. 启用 Hot-Reloading 运行 foo_scala 应用程序:

    <span class="strong"><strong>    activator "~run"</strong></span>
    
  2. build.sbt 中声明 Redis 为项目依赖项:

    "com.typesafe.play.plugins" %% "play-plugins-redis" % "2.3.1"
    
  3. build.sbt 中声明托管 Sedis 的仓库,它是 play-plugins-redis 的库依赖项:

    resolvers += "Sedis repository" at "http://pk11-scratch.googlecode.com/svn/trunk/"
    
  4. 通过在 foo_scala/conf/play.plugins 中声明启用 play-mailer 插件:

    550:com.typesafe.plugin.RedisPlugin
    
  5. foo_scala/conf/application.conf 中指定您的 Redis 主机信息:

    ehcacheplugin=disabled
        redis.uri="redis://127.0.0.1:6379"
    
  6. 通过在 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) =&gt; {
                 Ok("Retrieved from Cache: %s".format(myKey))
               }
               case None =&gt; {
                 Cache.set(key, "Let's Play with Redis!")
                 Ok("Setting key value in the cache")
               }
             }
           }
    
  7. foo_scala/conf/routes 中为新增的 action 添加一个新的路由条目:

    GET     /cache  controllers.Application.displayFromCache
    
  8. 请求我们的新路由并检查响应体以确认我们的 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>    &gt; GET /cache HTTP/1.1</strong></span>
    <span class="strong"><strong>    &gt; User-Agent: curl/7.37.1</strong></span>
    <span class="strong"><strong>    &gt; Host: localhost:9000</strong></span>
    <span class="strong"><strong>    &gt; Accept: */*</strong></span>
    <span class="strong"><strong>    &gt;</strong></span>
    <span class="strong"><strong>    &lt; HTTP/1.1 200 OK</strong></span>
    <span class="strong"><strong>    &lt; Content-Type: text/plain; charset=utf-8</strong></span>
    <span class="strong"><strong>    &lt; Content-Length: 30</strong></span>
    <span class="strong"><strong>    &lt;</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>
    
  9. 再次请求 /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>    &gt; GET /cache HTTP/1.1</strong></span>
    <span class="strong"><strong>    &gt; User-Agent: curl/7.37.1</strong></span>
    <span class="strong"><strong>    &gt; Host: localhost:9000</strong></span>
    <span class="strong"><strong>    &gt; Accept: */*</strong></span>
    <span class="strong"><strong>    &gt;</strong></span>
    <span class="strong"><strong>    &lt; HTTP/1.1 200 OK</strong></span>
    <span class="strong"><strong>    &lt; Content-Type: text/plain; charset=utf-8</strong></span>
    <span class="strong"><strong>    &lt; Content-Length: 43</strong></span>
    <span class="strong"><strong>    &lt;</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,我们需要采取以下步骤:

  1. 启用 Hot-Reloading 运行 foo_java 应用程序:

    <span class="strong"><strong>    activator "~run"</strong></span>
    
  2. build.sbt 中将 play-s3 声明为项目依赖项:

    "com.amazonaws" % "aws-java-sdk" % "1.3.11"
    
  3. 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"
    
  4. 通过添加以下代码修改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();
             }
           }
    
  5. foo_java/conf/routes中为新增的动作添加新的路由条目:

    GET     /s3_upload      controllers.Application.s3Upload
        POST    /s3_upload      controllers.Application.submitS3Upload
    
  6. 将 S3 文件上传提交视图模板添加到foo_java/app/views/s3.scala.html

    @helper.form(action = routes.Application.submitS3Upload, 'enctype -&gt; "multipart/form-data") {
          &lt;input type="file" name="profile"&gt;
          &lt;p&gt;
            &lt;input type="submit"&gt;
          &lt;/p&gt;
        }
    

对于 Scala,我们需要采取以下步骤:

  1. 以启用热重载的方式运行foo_scala应用程序:

    <span class="strong"><strong>    activator "~run"</strong></span>
    
  2. build.sbt中将 play-s3 声明为项目依赖项:

    "nl.rhinofly" %% "play-s3" % "5.0.2",
    
  3. 声明 play-s3 模块托管的自定义仓库:

    resolvers += "Rhinofly Internal Repository" at "http://maven-   repository.rhinofly.net:8081/artifactory/libs-release-local"
    
  4. 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"
    
  5. 修改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 =&gt;
             import play.api.Play
              request.body.file("profile") match {
               case Some(profileImage) =&gt; {
                 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 =&gt; {
                  BadRequest
                }
              }
            }
    
  6. 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 -&gt; "multipart/form-data") {
          &lt;input type="file" name="profile"&gt;
          &lt;p&gt;
            &lt;input type="submit"&gt;
          &lt;/p&gt;
        }
    

它是如何工作的…

在本菜谱中,我们创建了一个新的 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://&lt;YOUR_BUCKET_NAME&gt;.s3.amazonaws.com/&lt;FILENAME&gt;

您还可以使用 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 类型的数据库对象来说非常方便。

如何做到这一点…

我们需要采取以下步骤:

  1. 以启用热重载的方式运行foo_scala应用程序:

    <span class="strong"><strong>    activator "~run"</strong></span>
    
  2. build.sbt中将 play-slick 声明为项目依赖项:

    "com.typesafe.play" %% "play-slick" % "0.8.1"
    
  3. 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.*"
    
  4. 使用以下内容创建新的供应商控制器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 =&gt;
               Ok(Json.toJson(Suppliers.list))
             }
              def create = DBAction(BodyParsers.parse.json) { implicit rs =&gt;
               val post = rs.request.body.validate[Supplier]
               post.fold(
                 errors =&gt; {
                   BadRequest(Json.obj("status" -&gt;"error", "message" -&gt; JsError.toFlatJson(errors)))
                 },
                 supplier =&gt; {
                   Suppliers.create(supplier)
                   Created(Json.toJson(supplier))
                 }
               )
             }
           }
    
  5. 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) &lt;&gt; (Supplier.tupled, Supplier.unapply)
        }
         object Suppliers {
          val suppliers = TableQuery[Suppliers]
           def list(implicit s: Session) = suppliers.sortBy(m =&gt; m.name.asc).list
          def create(supplier: Supplier)(implicit s: Session) = suppliers.insert(supplier)
        }
    
  6. foo_scala/conf/routes中添加Supplier控制器的新的路由:

    GET  /suppliers    controllers.SupplierController.index
        POST  /suppliers    controllers.SupplierController.create
    
  7. 请求新的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>    &gt; POST /suppliers HTTP/1.1</strong></span>
    <span class="strong"><strong>    &gt; User-Agent: curl/7.37.1</strong></span>
    <span class="strong"><strong>    &gt; Host: localhost:9000</strong></span>
    <span class="strong"><strong>    &gt; Accept: */*</strong></span>
    <span class="strong"><strong>    &gt; Content-type: application/json</strong></span>
    <span class="strong"><strong>    &gt; Content-Length: 47</strong></span>
    <span class="strong"><strong>    &gt;</strong></span>
    <span class="strong"><strong>    * upload completely sent off: 47 out of 47 bytes</strong></span>
    <span class="strong"><strong>    &lt; HTTP/1.1 201 Created</strong></span>
    <span class="strong"><strong>    &lt; Content-Type: application/json; charset=utf-8</strong></span>
    <span class="strong"><strong>    &lt; Content-Length: 46</strong></span>
    <span class="strong"><strong>    &lt;</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>
    
  8. 请求列表路由并验证它确实正在从数据库返回记录:

    <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>    &gt; GET /suppliers HTTP/1.1</strong></span>
    <span class="strong"><strong>    &gt; User-Agent: curl/7.37.1</strong></span>
    <span class="strong"><strong>    &gt; Host: localhost:9000</strong></span>
    <span class="strong"><strong>    &gt; Accept: */*</strong></span>
    <span class="strong"><strong>    &gt;</strong></span>
    <span class="strong"><strong>    &lt; HTTP/1.1 200 OK</strong></span>
    <span class="strong"><strong>    &lt; Content-Type: application/json; charset=utf-8</strong></span>
    <span class="strong"><strong>    &lt; Content-Length: 110</strong></span>
    <span class="strong"><strong>    &lt;</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 请求我们的POSTGET端点,以便能够查看响应头和正文。

利用 play-mailer

对于这个菜谱,我们将探讨 Play 应用程序如何发送电子邮件。我们将使用 Play 模块 play-mailer 来实现这一点。我们将使用 Mandrill,一个云电子邮件服务,来发送电子邮件。有关 Mandrill 的更多信息,请参阅mandrill.com/

如何做到这一点…

对于 Java,我们需要采取以下步骤:

  1. 启用 Hot-Reloading 后运行foo_java应用程序:

    <span class="strong"><strong>    activator "~run"</strong></span>
    
  2. build.sbt中将 play-mailer 声明为项目依赖项:

    "com.typesafe.play.plugins" %% "play-plugins-mailer" % "2.3.1"
    
  3. 通过在foo_java/conf/play.plugins中声明它来启用 play-mailer 插件:

    1500:com.typesafe.plugin.CommonsMailerPlugin
    
  4. 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
    
  5. 通过在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&lt;Result&gt; emailSender() {
            Promise&lt;Boolean&gt; emailResult = Promise.promise(
                new F.Function0&lt;Boolean&gt;() {
                    @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 &lt;noreply@email.com&gt;");
                            mail.send("text");
                             return true;
                        } catch (Exception e) {
                            e.printStackTrace();
                            return false;
                        }
                    }
                }
            );
             return emailResult.map(
                new Function&lt;Boolean, Result&gt;() {
                    @Override
                    public Result apply(Boolean sent) throws Throwable {
                        if (sent) {
                            return ok("Email sent!");
                        } else {
                            return ok("Email was not sent!");
                        }
                    }
                }
            );
        }
    
  6. foo_java/conf/routes中为新增的操作添加一个新的路由条目:

    POST  /send_email    controllers.Application.emailSender
    
  7. 请求我们的新路由并检查响应头以确认我们对 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>    &gt; POST /send_email HTTP/1.1</strong></span>
    <span class="strong"><strong>    &gt; User-Agent: curl/7.37.1</strong></span>
    <span class="strong"><strong>    &gt; Host: localhost:9000</strong></span>
    <span class="strong"><strong>    &gt; Accept: */*</strong></span>
    <span class="strong"><strong>    &gt;</strong></span>
    <span class="strong"><strong>    &lt; HTTP/1.1 200 OK</strong></span>
    <span class="strong"><strong>    &lt; Content-Type: text/plain; charset=utf-8</strong></span>
    <span class="strong"><strong>    &lt; Content-Length: 11</strong></span>
    <span class="strong"><strong>    &lt;</strong></span>
    <span class="strong"><strong>    * Connection #0 to host localhost left intact</strong></span>
    <span class="strong"><strong>    Email sent!%</strong></span>
    

对于 Scala,我们需要采取以下步骤:

  1. 启用 Hot-Reloading 后运行foo_scala应用程序:

    <span class="strong"><strong>    activator "~run"</strong></span>
    
  2. build.sbt中将 play-mailer 声明为项目依赖项:

    "com.typesafe.play.plugins" %% "play-plugins-mailer" % "2.3.1"
    
  3. 通过在foo_scala/conf/play.plugins中声明它来启用 play-mailer 插件:

    1500:com.typesafe.plugin.CommonsMailerPlugin
    
  4. 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
    
  5. 通过在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 =&gt; 
            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 &lt;noreply@email.com&gt;")
          mail.send("text")
        }
    
  6. foo_scala/conf/routes中为新增的操作添加一个新的路由条目:

    POST  /send_email    controllers.Application.emailSender
    
  7. 请求我们的新路由并检查响应头以确认我们对 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>    &gt; POST /send_email HTTP/1.1</strong></span>
    <span class="strong"><strong>    &gt; User-Agent: curl/7.37.1</strong></span>
    <span class="strong"><strong>    &gt; Host: localhost:9000</strong></span>
    <span class="strong"><strong>    &gt; Accept: */*</strong></span>
    <span class="strong"><strong>    &gt;</strong></span>
    <span class="strong"><strong>    &lt; HTTP/1.1 200 OK</strong></span>
    <span class="strong"><strong>    &lt; Content-Type: text/plain; charset=utf-8</strong></span>
    <span class="strong"><strong>    &lt; Content-Length: 30</strong></span>
    <span class="strong"><strong>    &lt;</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,我们需要采取以下步骤:

  1. 启用 Hot-Reloading 后运行foo_java应用程序:

    <span class="strong"><strong>    activator "~run"</strong></span>
    
  2. build.sbt中将 Bootstrap 和 WebJars 声明为项目依赖项:

    "org.webjars" % "bootstrap" % "3.3.1",
        "org.webjars" %% "webjars-play" % "2.3.0"
    
  3. 通过添加以下代码修改foo_java/app/controllers/Application.java

    public static Result bootstrapped() {
             return ok(views.html.bootstrapped.render());
           }
    
  4. 将新的路由条目添加到foo_java/conf/routes

    GET     /webjars/*file        controllers.WebJarAssets.at(file)
        GET     /bootstrapped         controllers.Application.bootstrapped
    
  5. 创建新的布局视图模板foo_java/app/views/mainLayout.scala.html,内容如下:

    @(title: String)(content: Html)&lt;!DOCTYPE html&gt;
     &lt;html&gt;
    &lt;head&gt;
        &lt;meta charset="utf-8"&gt;
        &lt;meta http-equiv="X-UA-Compatible" content="IE=edge"&gt;
        &lt;meta name="viewport" content="width=device-width, initial-scale=1"&gt;
        &lt;meta name="description" content=""&gt;
        &lt;meta name="author" content=""&gt;
        &lt;title&gt;@title&lt;/title&gt;
        &lt;link rel="shortcut icon" type="image/png" href='@routes.Assets.at("images/favicon.png")'&gt;
        &lt;link rel="stylesheet" media="screen" href='@routes.WebJarAssets.at(WebJarAssets.locate("css/bootstrap.min.css"))' /&gt;
        &lt;link rel="stylesheet" media="screen" href='@routes.Assets.at("stylesheets/app.css")'/&gt;
        &lt;style&gt;
            body {
              padding-top: 50px;
            }
        &lt;/style&gt;
    &lt;/head&gt;
    &lt;body&gt;
    &lt;nav class="navbar navbar-inverse navbar-fixed-top" role="navigat   ion"&gt;
        &lt;div class="container-fluid"&gt;
            &lt;div class="navbar-header"&gt;
                &lt;button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar"&gt;
                    &lt;span class="sr-only"&gt;Toggle navigation&lt;/span&gt;
                    &lt;span class="icon-bar"&gt;&lt;/span&gt;
                    &lt;span class="icon-bar"&gt;&lt;/span&gt;
                    &lt;span class="icon-bar"&gt;&lt;/span&gt;
                &lt;/button&gt;
                &lt;a class="navbar-brand" href="#"&gt;Admin&lt;/a&gt;
            &lt;/div&gt;
            &lt;div id="navbar" class="navbar-collapse collapse"&gt;
                &lt;ul class="nav navbar-nav navbar-right"&gt;
                    &lt;li&gt;&lt;a href="#"&gt;Dashboard&lt;/a&gt;&lt;/li&gt;
                    &lt;li&gt;&lt;a href="#"&gt;Settings&lt;/a&gt;&lt;/li&gt;
                    &lt;li&gt;&lt;a href="#"&gt;Profile&lt;/a&gt;&lt;/li&gt;
                    &lt;li&gt;&lt;a href="#"&gt;Help&lt;/a&gt;&lt;/li&gt;
                &lt;/ul&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/nav&gt;
     &lt;div class="container-fluid"&gt;
        &lt;div class="row"&gt;
            &lt;div class="col-sm-3 col-md-2 sidebar"&gt;
                &lt;ul class="nav nav-sidebar"&gt;
                    &lt;li class="active"&gt;&lt;a href="#"&gt;Overview &lt;span class="sr-only"&gt;(current)&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
                    &lt;li&gt;&lt;a href="#"&gt;Reports&lt;/a&gt;&lt;/li&gt;
                    &lt;li&gt;&lt;a href="#"&gt;Analytics&lt;/a&gt;&lt;/li&gt;
                    &lt;li&gt;&lt;a href="#"&gt;Export&lt;/a&gt;&lt;/li&gt;
                &lt;/ul&gt;
                &lt;ul class="nav nav-sidebar"&gt;
                    &lt;li&gt;&lt;a href=""&gt;Users&lt;/a&gt;&lt;/li&gt;
                    &lt;li&gt;&lt;a href=""&gt;Audit Log&lt;/a&gt;&lt;/li&gt;
                &lt;/ul&gt;
                &lt;ul class="nav nav-sidebar"&gt;
                    &lt;li&gt;&lt;a href=""&gt;Sign out&lt;/a&gt;&lt;/li&gt;
                &lt;/ul&gt;
            &lt;/div&gt;
            &lt;div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main"&gt;
                @content
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/div&gt;
     &lt;/body&gt;
    &lt;/html&gt;
    
  6. foo_java/app/views/bootstrapped.scala.html中创建自举视图模板,内容如下:

    @mainLayout("Bootstrapped") {
    &lt;div class="hero-unit"&gt;
        &lt;h1&gt;Hello, world!&lt;/h1&gt;
        &lt;p&gt;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.&lt;/p&gt;
        &lt;p&gt;&lt;a href="#" class="btn btn-primary btn-large"&gt;Learn more &amp;raquo;&lt;/a&gt;&lt;/p&gt;
    &lt;/div&gt;
    }
    
  7. 使用网络浏览器请求我们的新自举路由(http://localhost:9000/bootstrapped)并检查使用 Bootstrap 模板渲染的页面:https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/play-fw-cb/img/TwofsdMh.jpg

对于 Scala,我们需要采取以下步骤:

  1. 启用热重载运行foo_scala应用程序:

    <span class="strong"><strong>    activator "~run"</strong></span>
    
  2. build.sbt中将 Bootstrap 和 WebJars 声明为项目依赖项:

    "org.webjars" % "bootstrap" % "3.3.1",
        "org.webjars" %% "webjars-play" % "2.3.0"
    
  3. 通过添加以下操作修改foo_scala/app/controllers/Application.scala

    def bootstrapped = Action {
             Ok(views.html.bootstrapped())
           }
    
  4. 将新的路由条目添加到foo_scala/conf/routes

    GET     /webjars/*file        controllers.WebJarAssets.at(file)
        GET     /bootstrapped         controllers.Application.bootstrapped
    
  5. 创建新的布局视图模板foo_scala/app/views/mainLayout.scala.html,内容如下:

    @(title: String)(content: Html)&lt;!DOCTYPE html&gt;
     &lt;html&gt;
    &lt;head&gt;
        &lt;meta charset="utf-8"&gt;
        &lt;meta http-equiv="X-UA-Compatible" content="IE=edge"&gt;
        &lt;meta name="viewport" content="width=device-width, initial-scale=1"&gt;
        &lt;meta name="description" content=""&gt;
        &lt;meta name="author" content=""&gt;
        &lt;title&gt;@title&lt;/title&gt;
        &lt;link rel="shortcut icon" type="image/png" href='@routes.Assets.at("images/favicon.png")'&gt;
        &lt;link rel="stylesheet" media="screen" href='@routes.WebJarAssets.at(WebJarAssets.locate("css/bootstrap.min.css"))' /&gt;
        &lt;link rel="stylesheet" media="screen" href='@routes.Assets.at("stylesheets/app.css")'/&gt;
        &lt;style&gt;
            body {
              padding-top: 50px;
            }
        &lt;/style&gt;
    &lt;/head&gt;
    &lt;body&gt;
    &lt;nav class="navbar navbar-inverse navbar-fixed-top" role="navigat   ion"&gt;
        &lt;div class="container-fluid"&gt;
            &lt;div class="navbar-header"&gt;
                &lt;button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar"&gt;
                    &lt;span class="sr-only"&gt;Toggle navigation&lt;/span&gt;
                    &lt;span class="icon-bar"&gt;&lt;/span&gt;
                    &lt;span class="icon-bar"&gt;&lt;/span&gt;
                    &lt;span class="icon-bar"&gt;&lt;/span&gt;
                &lt;/button&gt;
                &lt;a class="navbar-brand" href="#"&gt;Admin&lt;/a&gt;
            &lt;/div&gt;
            &lt;div id="navbar" class="navbar-collapse collapse"&gt;
                &lt;ul class="nav navbar-nav navbar-right"&gt;
                    &lt;li&gt;&lt;a href="#"&gt;Dashboard&lt;/a&gt;&lt;/li&gt;
                    &lt;li&gt;&lt;a href="#"&gt;Settings&lt;/a&gt;&lt;/li&gt;
                    &lt;li&gt;&lt;a href="#"&gt;Profile&lt;/a&gt;&lt;/li&gt;
                    &lt;li&gt;&lt;a href="#"&gt;Help&lt;/a&gt;&lt;/li&gt;
                &lt;/ul&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/nav&gt;
     &lt;div class="container-fluid"&gt;
        &lt;div class="row"&gt;
            &lt;div class="col-sm-3 col-md-2 sidebar"&gt;
                &lt;ul class="nav nav-sidebar"&gt;
                    &lt;li class="active"&gt;&lt;a href="#"&gt;Overview &lt;span class="sr-only"&gt;(current)&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
                    &lt;li&gt;&lt;a href="#"&gt;Reports&lt;/a&gt;&lt;/li&gt;
                    &lt;li&gt;&lt;a href="#"&gt;Analytics&lt;/a&gt;&lt;/li&gt;
                    &lt;li&gt;&lt;a href="#"&gt;Export&lt;/a&gt;&lt;/li&gt;
                &lt;/ul&gt;
                &lt;ul class="nav nav-sidebar"&gt;
                    &lt;li&gt;&lt;a href=""&gt;Users&lt;/a&gt;&lt;/li&gt;
                    &lt;li&gt;&lt;a href=""&gt;Audit Log&lt;/a&gt;&lt;/li&gt;
                &lt;/ul&gt;
                &lt;ul class="nav nav-sidebar"&gt;
                    &lt;li&gt;&lt;a href=""&gt;Sign out&lt;/a&gt;&lt;/li&gt;
                &lt;/ul&gt;
            &lt;/div&gt;
            &lt;div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main"&gt;
                @content
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/div&gt;
     &lt;/body&gt;
    &lt;/html&gt;
    
  6. foo_scala/app/views/bootstrapped.scala.html中创建自举视图模板,内容如下:

    @mainLayout("Bootstrapped") {
    &lt;div class="hero-unit"&gt;
        &lt;h1&gt;Hello, world!&lt;/h1&gt;
        &lt;p&gt;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.&lt;/p&gt;
        &lt;p&gt;&lt;a href="#" class="btn btn-primary btn-large"&gt;Learn more &amp;raquo;&lt;/a&gt;&lt;/p&gt;
    &lt;/div&gt;
    }
    
  7. 使用网络浏览器请求我们的新自举路由(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,我们需要执行以下步骤:

  1. 启用热重载功能运行foo_java应用程序:

    <span class="strong"><strong>    activator "~run"</strong></span>
    
  2. 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&lt;String, Product&gt; products = new HashMap&lt;String, Product&gt;();
             @BodyParser.Of(BodyParser.Json.class)
            public static Result create() {
                try {
                    Form&lt;Product&gt; 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());
                }
            }
        }
    
  3. 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;
            }
        }
    
  4. foo_java/conf/routes中为新增的操作添加一个新的路由条目:

    POST    /api/products       controllers.Products.create
    
  5. 请求新的路由并检查响应体以确认:

    <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>    &gt; POST /api/products HTTP/1.1</strong></span>
    <span class="strong"><strong>    &gt; User-Agent: curl/7.37.1</strong></span>
    <span class="strong"><strong>    &gt; Host: localhost:9000</strong></span>
    <span class="strong"><strong>    &gt; Accept: */*</strong></span>
    <span class="strong"><strong>    &gt; Content-type: application/json</strong></span>
    <span class="strong"><strong>    &gt; Content-Length: 43</strong></span>
    <span class="strong"><strong>    &gt;</strong></span>
    <span class="strong"><strong>    * upload completely sent off: 43 out of 43 bytes</strong></span>
    <span class="strong"><strong>    &lt; HTTP/1.1 201 Created</strong></span>
    <span class="strong"><strong>    &lt; Content-Type: application/json; charset=utf-8</strong></span>
    <span class="strong"><strong>    &lt; Content-Length: 42</strong></span>
    <span class="strong"><strong>    &lt;</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,我们需要执行以下步骤:

  1. 启用热重载功能运行foo_scala应用程序:

    <span class="strong"><strong>    activator "~run"</strong></span>
    
  2. 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 =&gt;
            val post = request.body.validate[Product]
            post.fold(
              errors =&gt; BadRequest(Json.obj("message" -&gt; JsError.toFlatJson(errors))),
              p =&gt; {
                try {
                  products += p
                  Created(Json.toJson(p))
                } catch {
                  case e: Exception =&gt; InternalServerError(e.getMessage)
                }
              }
            )
          }
        }
    
  3. foo_scala/app/models/Product.scala中创建一个产品模型类:

    package models
         case class Product(sku: String, title: String)
    
  4. foo_scala/conf/routes中为新增的操作添加一个新的路由条目:

    POST    /api/products       controllers.Products.create
    
  5. 请求新的路由并检查响应体以确认:

    <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>    &gt; POST /api/products HTTP/1.1</strong></span>
    <span class="strong"><strong>    &gt; User-Agent: curl/7.37.1</strong></span>
    <span class="strong"><strong>    &gt; Host: localhost:9000</strong></span>
    <span class="strong"><strong>    &gt; Accept: */*</strong></span>
    <span class="strong"><strong>    &gt; Content-type: application/json</strong></span>
    <span class="strong"><strong>    &gt; Content-Length: 43</strong></span>
    <span class="strong"><strong>    &gt;</strong></span>
    <span class="strong"><strong>    * upload completely sent off: 43 out of 43 bytes</strong></span>
    <span class="strong"><strong>    &lt; HTTP/1.1 201 Created</strong></span>
    <span class="strong"><strong>    &lt; Content-Type: application/json; charset=utf-8</strong></span>
    <span class="strong"><strong>    &lt; Content-Length: 42</strong></span>
    <span class="strong"><strong>    &lt;</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 =&gt; BadRequest(Json.obj("message" -&gt; 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>    &gt; POST /api/products HTTP/1.1</strong></span>
<span class="strong"><strong>    &gt; User-Agent: curl/7.37.1</strong></span>
<span class="strong"><strong>    &gt; Host: localhost:9000</strong></span>
<span class="strong"><strong>    &gt; Accept: */*</strong></span>
<span class="strong"><strong>    &gt; Content-type: application/json</strong></span>
<span class="strong"><strong>    &gt; Content-Length: 44</strong></span>
<span class="strong"><strong>    &gt;</strong></span>
<span class="strong"><strong>    * upload completely sent off: 44 out of 44 bytes</strong></span>
<span class="strong"><strong>    &lt; HTTP/1.1 400 Bad Request</strong></span>
<span class="strong"><strong>    &lt; Content-Type: application/json; charset=utf-8</strong></span>
<span class="strong"><strong>    &lt; Content-Length: 36</strong></span>
<span class="strong"><strong>    &lt;</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,我们需要执行以下步骤:

  1. 启用 Hot-Reloading 运行foo_java应用程序:

    <span class="strong"><strong>    activator "~run"</strong></span>
    
  2. 通过添加以下动作方法来修改foo_java/app/controllers/Products.java中的产品控制器:

    public static Result index() {
            return ok(toJson(products));
        }
    
  3. foo_java/conf/routes中为新增的操作添加一个新的路由条目:

    GET     /api/products       controllers.Products.index
    
  4. 请求新路由并检查响应头以确认我们对 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>    &gt; GET /api/products HTTP/1.1</strong></span>
    <span class="strong"><strong>    &gt; User-Agent: curl/7.37.1</strong></span>
    <span class="strong"><strong>    &gt; Host: localhost:9000</strong></span>
    <span class="strong"><strong>    &gt; Accept: */*</strong></span>
    <span class="strong"><strong>    &gt;</strong></span>
    <span class="strong"><strong>    &lt; HTTP/1.1 200 OK</strong></span>
    <span class="strong"><strong>    &lt; Content-Type: application/json; charset=utf-8</strong></span>
    <span class="strong"><strong>    &lt; Content-Length: 50</strong></span>
    <span class="strong"><strong>    &lt;</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,我们需要执行以下步骤:

  1. 启用 Hot-Reloading 运行foo_scala应用程序:

    <span class="strong"><strong>    activator "~run"</strong></span>
    
  2. 通过在foo_scala/app/controllers/Products.scala中添加以下动作方法来修改产品控制器:

    def index = Action {
          Ok(toJson(products))
        }
    
  3. foo_scala/conf/routes中为新增的操作添加一个新的路由条目:

    <span class="strong"><strong>    GET     /api/products       controllers.Products.index</strong></span>
    
  4. 请求我们的新路由并检查响应头以确认我们对 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>    &gt; GET /api/products HTTP/1.1</strong></span>
    <span class="strong"><strong>    &gt; User-Agent: curl/7.37.1</strong></span>
    <span class="strong"><strong>    &gt; Host: localhost:9000</strong></span>
    <span class="strong"><strong>    &gt; Accept: */*</strong></span>
    <span class="strong"><strong>    &gt;</strong></span>
    <span class="strong"><strong>    &lt; HTTP/1.1 200 OK</strong></span>
    <span class="strong"><strong>    &lt; Content-Type: application/json; charset=utf-8</strong></span>
    <span class="strong"><strong>    &lt; Content-Length: 50</strong></span>
    <span class="strong"><strong>    &lt;</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>    &gt; GET /api/products HTTP/1.1</strong></span>
<span class="strong"><strong>    &gt; User-Agent: curl/7.37.1</strong></span>
<span class="strong"><strong>    &gt; Host: localhost:9000</strong></span>
<span class="strong"><strong>    &gt; Accept: */*</strong></span>
<span class="strong"><strong>    &gt;</strong></span>
<span class="strong"><strong>    &lt; HTTP/1.1 200 OK</strong></span>
<span class="strong"><strong>    &lt; Content-Type: application/json; charset=utf-8</strong></span>
<span class="strong"><strong>    &lt; Content-Length: 2</strong></span>
<span class="strong"><strong>    &lt;</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,我们需要执行以下步骤:

  1. 启用 Hot-Reloading 运行foo_java应用程序:

    <span class="strong"><strong>    activator "~run"</strong></span>
    
  2. 通过添加以下动作来修改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&lt;Product&gt; 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());
            }
        }
    
  3. foo_java/conf/routes中为新增的操作添加一个新的路由:

    PUT     /api/products/:id   controllers.Products.edit(id: String)
    
  4. 使用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>    &gt; PUT /api/products/def HTTP/1.1</strong></span>
    <span class="strong"><strong>    &gt; User-Agent: curl/7.37.1</strong></span>
    <span class="strong"><strong>    &gt; Host: localhost:9000</strong></span>
    <span class="strong"><strong>    &gt; Accept: */*</strong></span>
    <span class="strong"><strong>    &gt; Content-type: application/json</strong></span>
    <span class="strong"><strong>    &gt; Content-Length: 35</strong></span>
    <span class="strong"><strong>    &gt;</strong></span>
    <span class="strong"><strong>    * upload completely sent off: 35 out of 35 bytes</strong></span>
    <span class="strong"><strong>    &lt; HTTP/1.1 200 OK</strong></span>
    <span class="strong"><strong>    &lt; Content-Type: application/json; charset=utf-8</strong></span>
    <span class="strong"><strong>    &lt; Content-Length: 34</strong></span>
    <span class="strong"><strong>    &lt;</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,我们需要执行以下步骤:

  1. 启用 Hot-Reloading 运行foo_scala应用程序:

    <span class="strong"><strong>    activator "~run"</strong></span>
    
  2. 通过添加以下动作来修改foo_scala/app/controllers/Products.scala

    def edit(id: String) = Action(BodyParsers.parse.json) { implicit request =&gt;
          val post = request.body.validate[Product]
          post.fold(
            errors =&gt; BadRequest(Json.obj("message" -&gt; JsError.toFlatJson(errors))),
            p =&gt; {
              products.find(_.sku equals id) match {
                case Some(product) =&gt; {
                  try {
                    products -= product
                    products += p
                     Ok(Json.toJson(p))
                  } catch {
                    case e: Exception =&gt; InternalServerError(e.getMessage)
                  }
                }
                case None =&gt; NotFound
              }
            }
          )
        }
    
  3. foo_scala/conf/routes 中为新增的动作添加一个新的路由:

    PUT     /api/products/:id   controllers.Products.edit(id: String)
    
  4. 使用 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>    &gt; PUT /api/products/def HTTP/1.1</strong></span>
    <span class="strong"><strong>    &gt; User-Agent: curl/7.37.1</strong></span>
    <span class="strong"><strong>    &gt; Host: localhost:9000</strong></span>
    <span class="strong"><strong>    &gt; Accept: */*</strong></span>
    <span class="strong"><strong>    &gt; Content-type: application/json</strong></span>
    <span class="strong"><strong>    &gt; Content-Length: 35</strong></span>
    <span class="strong"><strong>    &gt;</strong></span>
    <span class="strong"><strong>    * upload completely sent off: 35 out of 35 bytes</strong></span>
    <span class="strong"><strong>    &lt; HTTP/1.1 200 OK</strong></span>
    <span class="strong"><strong>    &lt; Content-Type: application/json; charset=utf-8</strong></span>
    <span class="strong"><strong>    &lt; Content-Length: 34</strong></span>
    <span class="strong"><strong>    &lt;</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 =&gt;

我们通过在我们的数据存储中进行查找来检查传入的 ID 值是否有效。对于无效的 ID,我们发送 HTTP 状态码 404:

// Java 
    return notFound();
     // Scala 
    case None =&gt; NotFound

我们还检查任何表单验证错误,并在出现错误时返回适当的状态码:

// Java 
    if (form.hasErrors()) {
      return badRequest(form.errorsAsJson());
    }
     // Scala 
    errors =&gt; BadRequest(Json.obj("message" -&gt; 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>&gt; PUT /api/products/XXXXXX HTTP/1.1</strong></span>
<span class="strong"><strong>&gt; User-Agent: curl/7.37.1</strong></span>
<span class="strong"><strong>&gt; Host: localhost:9000</strong></span>
<span class="strong"><strong>&gt; Accept: */*</strong></span>
<span class="strong"><strong>&gt;</strong></span>
<span class="strong"><strong>&lt; HTTP/1.1 404 Not Found</strong></span>
<span class="strong"><strong>&lt; Content-Length: 0</strong></span>
<span class="strong"><strong>&lt;</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>&gt; PUT /api/products/def HTTP/1.1</strong></span>
<span class="strong"><strong>&gt; User-Agent: curl/7.37.1</strong></span>
<span class="strong"><strong>&gt; Host: localhost:9000</strong></span>
<span class="strong"><strong>&gt; Accept: */*</strong></span>
<span class="strong"><strong>&gt; Content-type: application/json</strong></span>
<span class="strong"><strong>&gt; Content-Length: 2</strong></span>
<span class="strong"><strong>&gt;</strong></span>
<span class="strong"><strong>* upload completely sent off: 2 out of 2 bytes</strong></span>
<span class="strong"><strong>&lt; HTTP/1.1 400 Bad Request</strong></span>
<span class="strong"><strong>&lt; Content-Type: application/json; charset=utf-8</strong></span>
<span class="strong"><strong>&lt; Content-Length: 69</strong></span>
<span class="strong"><strong>&lt;</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,我们需要执行以下步骤:

  1. 以启用热重载的方式运行 foo_java 应用程序:

    <span class="strong"><strong>    activator "~run"</strong></span>
    
  2. 通过添加以下动作修改 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());
            }
        }
    
  3. foo_java/conf/routes 中为新增的动作添加一个新的路由:

    DELETE  /api/products/:id   controllers.Products.delete(id: String)
    
  4. 使用 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>    &gt; DELETE /api/products/def HTTP/1.1</strong></span>
    <span class="strong"><strong>    &gt; User-Agent: curl/7.37.1</strong></span>
    <span class="strong"><strong>    &gt; Host: localhost:9000</strong></span>
    <span class="strong"><strong>    &gt; Accept: */*</strong></span>
    <span class="strong"><strong>    &gt;</strong></span>
    <span class="strong"><strong>    &lt; HTTP/1.1 204 No Content</strong></span>
    <span class="strong"><strong>    &lt; Content-Length: 0</strong></span>
    <span class="strong"><strong>    &lt;</strong></span>
    <span class="strong"><strong>    * Connection #0 to host localhost left intact</strong></span>
    

对于 Scala,我们需要执行以下步骤:

  1. 以启用热重载的方式运行 foo_scala 应用程序:

    <span class="strong"><strong>    activator "~run"</strong></span>
    
  2. 通过添加以下动作修改 foo_scala/app/controllers/Products.scala

    def delete(id: String) = BasicAuthAction {
        products.find(_.sku equals id) match {
          case Some(product) =&gt; {
            try {
              products -= product
              NoContent
            } catch {
              case e: Exception =&gt; InternalServerError(e.getMessage)
            }
          }
          case None =&gt; NotFound
        }
      }
    
  3. foo_scala/conf/routes 中为新增的动作添加一个新的路由:

    DELETE  /api/products/:id   controllers.Products.delete(id: String)
    
  4. 使用 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>    &gt; DELETE /api/products/def HTTP/1.1</strong></span>
    <span class="strong"><strong>    &gt; User-Agent: curl/7.37.1</strong></span>
    <span class="strong"><strong>    &gt; Host: localhost:9000</strong></span>
    <span class="strong"><strong>    &gt; Accept: */*</strong></span>
    <span class="strong"><strong>    &gt;</strong></span>
    <span class="strong"><strong>    &lt; HTTP/1.1 204 No Content</strong></span>
    <span class="strong"><strong>    &lt; Content-Length: 0</strong></span>
    <span class="strong"><strong>    &lt;</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 =&gt; 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>    &gt; DELETE /api/products/asd HTTP/1.1</strong></span>
<span class="strong"><strong>    &gt; User-Agent: curl/7.37.1</strong></span>
<span class="strong"><strong>    &gt; Host: localhost:9000</strong></span>
<span class="strong"><strong>    &gt; Accept: */*</strong></span>
<span class="strong"><strong>    &gt;</strong></span>
<span class="strong"><strong>    &lt; HTTP/1.1 404 Not Found</strong></span>
<span class="strong"><strong>    &lt; Content-Length: 0</strong></span>
<span class="strong"><strong>    &lt;</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,我们需要执行以下步骤:

  1. 以启用热重载的方式运行 foo_java 应用程序:

    <span class="strong"><strong>    activator "~run"</strong></span>
    
  2. 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 &amp;&amp; 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 &amp;&amp; username.equals("ned") &amp;&amp;
                    password != null &amp;&amp; password.equals("flanders");
            }
             @Override
            public Result onUnauthorized(Http.Context context) {
                return unauthorized();
            }
        }
    
  3. 通过向 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)
    
  4. 使用curl,发送一个请求到我们之前所做的现有 RESTful GET端点;你现在将看到一个未授权的响应:

    <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>    &gt; GET /api/products HTTP/1.1</strong></span>
    <span class="strong"><strong>    &gt; User-Agent: curl/7.37.1</strong></span>
    <span class="strong"><strong>    &gt; Host: localhost:9000</strong></span>
    <span class="strong"><strong>    &gt; Accept: */*</strong></span>
    <span class="strong"><strong>    &gt;</strong></span>
    <span class="strong"><strong>    &lt; HTTP/1.1 401 Unauthorized</strong></span>
    <span class="strong"><strong>    &lt; Content-Length: 0</strong></span>
    <span class="strong"><strong>    &lt;</strong></span>
    <span class="strong"><strong>    * Connection #0 to host localhost left intact</strong></span>
    
  5. 再次使用curl,发送另一个请求到现有的 RESTful GET端点,这次带有用户凭据,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>    &gt; GET /api/products HTTP/1.1</strong></span>
    <span class="strong"><strong>    &gt; Authorization: Basic bmVkOmZsYW5kZXJz</strong></span>
    <span class="strong"><strong>    &gt; User-Agent: curl/7.37.1</strong></span>
    <span class="strong"><strong>    &gt; Host: localhost:9000</strong></span>
    <span class="strong"><strong>    &gt; Accept: */*</strong></span>
    <span class="strong"><strong>    &gt;</strong></span>
    <span class="strong"><strong>    &lt; HTTP/1.1 200 OK</strong></span>
    <span class="strong"><strong>    &lt; Content-Type: application/json; charset=utf-8</strong></span>
    <span class="strong"><strong>    &lt; WWW-Authenticate: Basic realm="API Realm"</strong></span>
    <span class="strong"><strong>    &lt; Content-Length: 2</strong></span>
    <span class="strong"><strong>    &lt;</strong></span>
    <span class="strong"><strong>    * Connection #0 to host localhost left intact</strong></span>
    <span class="strong"><strong>    {}%</strong></span>
    

对于 Scala,我们需要执行以下步骤:

  1. 启用热重载功能运行foo_scala应用程序:

    activator "~run"
    
  2. 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 =&gt; Future[Result]) = {
            try {
              request.headers.get("authorization") match {
                case Some(headers) =&gt; {
                  val auth = headers.substring(6)
                  val decodedAuth = Base64.decodeBase64(auth)
                  val credentials = new String(decodedAuth, "UTF-8").split(":")
                   if (credentials != null &amp;&amp; credentials.length == 2 &amp;&amp;
                      isAuthenticated(credentials(0), credentials(1))) {
                    block(request)
                  } else {
                    unauthorized
                  }
                }
                case None =&gt; unauthorized
              }
            } catch {
              case e: Exception =&gt; Future.successful(InternalServerError(e.getMessage))
            }
          }
           def unauthorized = Future.successful(Unauthorized.withHeaders("WWW-Authenticate" -&gt; "Basic realm=\"API Realm\""))
           def isAuthenticated(username: String, password: String) = username != null &amp;&amp; username.equals("ned") &amp;&amp; password != null &amp;&amp; password.equals("flanders")
        }
    
  3. 通过添加新创建的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
    
  4. 使用curl,发送一个请求到我们之前所做的现有 RESTful GET端点;你现在将看到一个未授权的响应:

    <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>    &gt; GET /api/products HTTP/1.1</strong></span>
    <span class="strong"><strong>    &gt; User-Agent: curl/7.37.1</strong></span>
    <span class="strong"><strong>    &gt; Host: localhost:9000</strong></span>
    <span class="strong"><strong>    &gt; Accept: */*</strong></span>
    <span class="strong"><strong>    &gt;  </strong></span>
    <span class="strong"><strong>    &lt; HTTP/1.1 401 Unauthorized</strong></span>
    <span class="strong"><strong>    &lt; Content-Length: 0</strong></span>
    <span class="strong"><strong>    &lt;</strong></span>
    <span class="strong"><strong>    * Connection #0 to host localhost left intact</strong></span>
    
  5. 再次使用curl,发送另一个请求到现有的 RESTful GET端点,这次带有用户凭据,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>    &gt; GET /api/products HTTP/1.1</strong></span>
    <span class="strong"><strong>    &gt; Authorization: Basic bmVkOmZsYW5kZXJz</strong></span>
    <span class="strong"><strong>    &gt; User-Agent: curl/7.37.1</strong></span>
    <span class="strong"><strong>    &gt; Host: localhost:9000</strong></span>
    <span class="strong"><strong>    &gt; Accept: */*</strong></span>
    <span class="strong"><strong>    &gt;</strong></span>
    <span class="strong"><strong>    &lt; HTTP/1.1 200 OK</strong></span>
    <span class="strong"><strong>    &lt; Content-Type: application/json; charset=utf-8</strong></span>
     <span class="strong"><strong>    &lt; Content-Length: 2</strong></span>
    <span class="strong"><strong>    &lt;</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.javaBasicAuthAction.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) =&gt; {
          val auth = headers.substring(6)
          val decodedAuth = Base64.decodeBase64(auth)
          val credentials = new String(decodedAuth, "UTF-8").split(":")
        }
    }

一旦我们获得了用户名和密码,我们就调用了isAuthenticated函数来检查用户凭据的有效性:

// Java
    if (credentials != null &amp;&amp; credentials.length == 2) {
     String username = credentials[0];
        String password = credentials[1];
        if (isAuthenticated(username, password)) {
            return username;
        } else {
            return null;
        }
    }
     // Scala
    if (credentials != null &amp;&amp; credentials.length == 2 &amp;&amp;
          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>    &gt; GET /api/products HTTP/1.1</strong></span>
<span class="strong"><strong>    &gt; User-Agent: curl/7.37.1</strong></span>
<span class="strong"><strong>    &gt; Host: localhost:9000</strong></span>
<span class="strong"><strong>    &gt; Accept: */*</strong></span>
<span class="strong"><strong>    &gt;</strong></span>
<span class="strong"><strong>    &lt; HTTP/1.1 401 Unauthorized</strong></span>
<span class="strong"><strong>    &lt; WWW-Authenticate: Basic realm="API Realm"</strong></span>
<span class="strong"><strong>    &lt; Content-Length: 0</strong></span>
<span class="strong"><strong>    &lt;</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,我们需要执行以下步骤:

  1. 启用热重载功能运行foo_java应用程序:

    <span class="strong"><strong>    activator "~run"</strong></span>
    
  2. build.sbt中将playWs声明为项目依赖项:

    libraryDependencies ++= Seq(
            javaWs
        )
    
  3. 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&lt;Result&gt; getTodos() {
                Promise&lt;play.libs.ws.WSResponse&gt; todos = WS.url("http://jsonplaceholder.typicode.com/todos").get();
                return todos.map(
                    new F.Function&lt;play.libs.ws.WSResponse, Result&gt;() {
                        public Result apply(play.libs.ws.WSResponse res) {
                            JsonNode json = res.asJson();
                            return ok("Todo Title: " + json.findValuesAsText("title"));
                        }
                    }
                );
            }
        }
    
  4. foo_java/conf/routes中为新增的操作添加一个新的路由条目:

    GET     /client/get_todos   controllers.WebClient.getTodos
    
  5. 使用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>    &gt; GET /client/get_todos HTTP/1.1</strong></span>
    <span class="strong"><strong>    &gt; User-Agent: curl/7.37.1</strong></span>
    <span class="strong"><strong>    &gt; Host: localhost:9000</strong></span>
    <span class="strong"><strong>    &gt; Accept: */*</strong></span>
    <span class="strong"><strong>    &gt;</strong></span>
    <span class="strong"><strong>    &lt; HTTP/1.1 200 OK</strong></span>
    <span class="strong"><strong>    &lt; Content-Type: text/plain; charset=utf-8</strong></span>
    <span class="strong"><strong>    &lt; Content-Length: 8699</strong></span>
    <span class="strong"><strong>    &lt;</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,我们需要执行以下步骤:

  1. 启用热重载功能运行foo_scala应用程序:

    <span class="strong"><strong>    activator "~run"</strong></span>
    
  2. build.sbt中将playWs声明为项目依赖项:

    libraryDependencies ++= Seq(
            ws
        )
    
  3. 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 =&gt;
              Ok("Todo Title: " + (res.json \\ "title").map(_.as[String]))
            }
          }
        }
    
  4. foo_scala/conf/routes中为新增的操作添加一个新的路由条目:

    GET     /client/get_todos   controllers.WebClient.getTodos
    
  5. 使用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>    &gt; GET /client/get_todos HTTP/1.1</strong></span>
    <span class="strong"><strong>    &gt; User-Agent: curl/7.37.1</strong></span>
    <span class="strong"><strong>    &gt; Host: localhost:9000</strong></span>
    <span class="strong"><strong>    &gt; Accept: */*</strong></span>
    <span class="strong"><strong>    &gt;</strong></span>
    <span class="strong"><strong>    &lt; HTTP/1.1 200 OK</strong></span>
    <span class="strong"><strong>    &lt; Content-Type: text/plain; charset=utf-8</strong></span>
    <span class="strong"><strong>    &lt; Content-Length: 8699</strong></span>
    <span class="strong"><strong>    &lt;</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&lt;play.libs.ws.WSResponse&gt; 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&lt;play.libs.ws.WSResponse, Result&gt;() {
        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,我们需要执行以下步骤:

  1. 启用 Hot-Reloading 运行foo_java应用程序:

    <span class="strong"><strong>    activator "~run"</strong></span>
    
  2. 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"
    
  3. 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&lt;Result&gt; 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&lt;play.libs.ws.WSResponse&gt; twRequest = WS.url(url).sign(new OAuthCalculator(consumerInfo, tokens)).get();
            return twRequest.map(
                new F.Function&lt;WSResponse, Result&gt;(){
                    @Override
                    public Result apply(WSResponse res) throws Throwable {
                        Map&lt;String, String&gt; map = new HashMap&lt;String, String&gt;();
                        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));
                    }
                }
            );
        }
    
  4. foo_java/conf/routes中为getTweets(hashtag: String)操作添加新的路由:

    GET /client/get_tweets/:hashtag controllers.WebClient.getTweets(hashtag)
    
  5. foo_java/app/views/tweets.scala.html中添加一个新的视图模板,内容如下:

    @(tweets: Map[String, String])
         &lt;ul&gt;
          @tweets.map { tw =&gt;
            &lt;li&gt;&lt;strong&gt;@@@tw._1&lt;/strong&gt; says &lt;i&gt;"@tw._2"&lt;/i&gt;&lt;/li&gt;
          }
        &lt;/ul&gt;
    
  6. 使用网络浏览器访问/client/get_tweets/:hashtag路由以查看从 Twitter API 检索的推文:https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/play-fw-cb/img/gqMspVre.jpg

对于 Scala,我们需要执行以下步骤:

  1. 启用 Hot-Reloading 运行foo_scala应用程序:

    <span class="strong"><strong>    activator "~run"</strong></span>
    
  2. 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"
    
  3. 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 =&gt;
            val tweets = ListBuffer[(String, String)]()
            (res.json \ "statuses").as[List[JsObject]].map { tweet =&gt;
              tweets += ((
                (tweet \ "user" \ "screen_name").as[String],
                (tweet \ "text").as[String]
              ))
            }
             Ok(views.html.tweets(tweets.toList))
          }
        }
    
  4. foo_scala/conf/routes中为getTweets(hashtag: String)操作添加新的路由:

    GET /client/get_tweets/:hashtag controllers.WebClient.getTweets(hashtag)
    
  5. foo_scala/app/views/tweets.scala.html中添加一个新的视图模板,内容如下:

    @(tweets: List[(String, String)])
         &lt;ul&gt;
          @tweets.map { tw =&gt;
            &lt;li&gt;&lt;strong&gt;@@@tw._1&lt;/strong&gt; says &lt;i&gt;"@tw._2"&lt;/i&gt;&lt;/li&gt;
          }
        &lt;/ul&gt;
    
  6. 使用网络浏览器访问/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&lt;play.libs.ws.WSResponse&gt; twRequest = 
WS.url(url).sign(new OAuthCalculator(consumerInfo, tokens)).get();
     // Scala 
    WS.url(url).sign(OAuthCalculator(consumerInfo, tokens)).get()

一旦 Twitter API 响应返回,我们解析响应的 JSON 并将其推送到一个中间集合对象,然后将其传递给我们的视图模板:

// Java 
    Map&lt;String, String&gt; map = new HashMap&lt;String, String&gt;();
    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 =&gt;
      tweets += ((
        (tweet \ "user" \ "screen_name").as[String],
        (tweet \ "text").as[String]
      ))
    }
     Ok(views.html.tweets(tweets.toList))
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐