文档章节

【二 HTTP编程】6. 组合Action

Landas
 Landas
发布于 08/21 21:04
字数 1471
阅读 12
收藏 0

本章描述了常用定义Action的方法。

自定义action builders

我们在action一章已经看过如何声明一个action——有request parameter、无request parameter、有body parser等等。你可以在 asynchronous programming 一章中了解更多。

构建actions的方法实际上定义在 ActionBuilder 特质中,我们用来声明actions的Action对象实际上就是此特质的实例。通过实现自己的ActionBuilder 可以定义可重用的 action stacks,然后可以用它来构建actions。

让我们从一个简单的日志装饰器开始,我们将用它来记录单个访问此action的请求。

第一种方法是在invokeBlock方法中硬编码,它将被每个此actionBuilder创建的action调用:

import play.api.mvc._

class LoggingAction @Inject()(parser: BodyParsers.Default)(implicit ec: Executioncontext)
    extends ActionBuilderImpl(parser) {
    override def invokeBlock[A](request: Request[A], block: (Request[A] => Future[Result]) = {
        Logger.info("Calling action")
        block(request)
    )
}

现在我们在controller中注入LogginAction的实例,和使用Action的方式一样:

class MyController @Inject()(loggingAction: LoggingAction, cc: ControllerComponents)
    extends AbstractController(cc) {
    def index = loggingAction {
        Ok("Hello World")
    }
}

由于ActionBuilder提供了所有构建actions的方法,因此也可以在这里声明一个自定义的body parser:

def submit = loggingAction(parse.text) { request =>
    Ok("Got a body " + request.body.length + "bytes long")
}

组合actions

在大多数应用中我们都希望同时使用多个action builders,比如一些用来做认证校验,一些用来提供各种不同类型的通用方法。我们不希望为各个action builder来重写logging action代码,而希望重用它。

可重用的action代码可以通过包装action来实现:

import play.api.mvc._

case class Logging[A](action: Action[A]) extends Action[A] {
    def apply(request: Request[A]): Future[Result] = {
        Logger.info("Calling action")
        action(request)
    }

    override def parser = action.parser
    override def executinContext = action.executionContext
}

也可以通过使用 Action 这个action builder的默认实现来构建actions:

import play.api.mvc._

def logging[A](action: Action[A]) = Action.async(action.parser) { request =>
    Logger.info("Calling action")
    action(request)
}

可以通过composeAction方法来向其他 action builder 混入自己的Action:

class LoggingAction @Inject()(parser: BodyParer.Default)(implicit ec: ExecutionContext)
    extends ActionBuilderImpl(parser) {
    override def invokeBlock[A](request: Request[A], block: (Request[A]) => Future[Result]) = {
        block(request)
    }
    override def composeAction[A](action: Action[A]) = new Logging(action)
}

现在我们可以像前面那样使用它了:

def index = loggingAction {
    Ok("Hello World")
}

也可以在包装actions中混入,而不使用action builder:

def index = Logging {
    Action {
        Ok("Hello World")
    }
}

更复杂的actions

目前为止我们展示的action还没有处理过request。我们可以如下操作它:

import play.api.mvc._
import play.api.mvc.request.RemoteConnection

def xForwardedFor[A](action: Action[A]) = Action.async(action.parser) { request =>
    val newRequest = request.headers.get("X-Forwarded-For") match {
        case None => request
        case Some(xff) => 
            val xffConnection = RemoteConnection(xff, request.connection.secure, None)
            request.withConnection(xffConnection)
    }
    action(newRequest)
}

注意:Play内置了对X-Forwarded-For的支持。

现在可以拦截request:

import play.api.mvc._
import play.api.mvc.Results._

def onlyHttps[A](action: Action[A]) = Action.async(action.parser) { request =>
    request.headers.get("X-Forwarded-Proto").collect {
        case "https" => action(request)
    } getOrElse {
        Future.successful(Forbidden("Only HTTPS requests allowed"))
    }
}

也可以用来修改要返回的result:

import play.api.mvc._

def addUaHeader[A](action: Action[A]) = Action.async(action.parser) { request =>
    action(request).map(_.withHeaders("X-UA-Compatible" -> "Chrome=1"))
}

不同的request类型

action组合允许你在HTTP请求/响应级别上做附加操作,但是你经常需要构建数据传输管道,以便为请求添加上下文或者执行某些校验。ActionFunction是一个request上的函数,通过输入输出的请求类型进行参数化。单个的 action function 一般为独立的模块,如验证、数据库查询、许可校验,或是其它你希望在action中共享的操作。

这里是一些预定义的ActionFunction:

  • ActionTransformer:用来修改request,如向request添加信息
  • ActionFilter:可以选择性的拦截requset并产生错误信息,而不会影响request内容
  • ActionRefiner:上面两个类的父类
  • ActionBuilder:一个特殊的类,以request作为输入参数来创建action

可以通过实现invokeBlock方法来定义任意的ActionFunction。通常这是将输入输出类型转换为自定义Request类型的便捷方式(通过使用WrappedRequest),但是这不是严格要求的。

认证

一个action function的常见例子就是认证。可以简单的定义一个认证action,用来从原始request中抽取认证信息,并添加到新的UserRequest中 。请注意因为它以简单的Request作为输入参数,因此它仍然是一个ActionBuilder:

import play.api.mvc._

class UserRequest[A](val user: Option[String], request: Request[A]) extends WrappedRequest[A](request) 

class UserAction @Inject()(val parser: BodyParsers.Default)(implicit val executionContext: ExecutionContext)
    extends ActionBuilder[UserRequest, AnyContent] with ActionTransformer[Request, UserRequest]{
    def transform[A](request: Request[A]) = Future.successful {
        new UserRequest(request.session.get("username"), request)
    }
}

Play内置了一个action builder用于认证。移步这里查看详情。

注意:内置的认证action builder仅仅是一个非常简单的工具。我们建议实现自己的认证helper来应对你的认证请求。

向request添加信息

以 Item 类的REST API为例。类似 /item/:itemId 的路径将对item发起查询。让我们把业务逻辑放到action function中。

首先,向UserRequest中添加一个Item:

import play.api.mvc._

class ItemRequest[A](val item: Item, request: UserRequest[A]) extends WrappedRequest[A](request){
    def username = request.username
}

现在我们添加一个action refiner来查询Item,并返回一个Either表示一个错误或者新的ItemRequest。注意这个action refiner定义在方法中,可以直接拿到item的id:

def ItemAction(itemId: String)(implicit ec: ExecutionContext) = new ActionRefiner[UserRequest, ItemRequest]{
    def executionContext = ec
    def refine[A](input: UserRequest[A]) = Future.successful{
        ItemDao.findById(itemId)
            .map(new ItemRequest(_, input))
            .toRight(NotFound)
    }
}

校验requests

最后我们对request进行校验。如校验UserAction中的user是否有权限来查询ItemAction中的item:

def PermissionCheckAction(implicit ec: ExecutionContext) = new ActionFilter[ItemRequst]{
    def executionContext = ec
    def filter[A](input: ItemRequest[A]) = Future.successful{
        if(!input.item.accessibleByUser(input.username))
            Some(Forbidden)
        else
            None
    }
}

合在一起

现在我们将它们串在一起(从ActionBuilder开始),使用andThen来创建action:

def tagItem(itemId: String, tag: String)(implicit ec: ExecutionContext) =
    (userAction andThen ItemAction(itemId) andThen PermissionCheckAction) {request =>
         request.item.addTag(tag)
         Ok("User " + request.username + " tagged " + request.item.id)
}

最后,Play提供了一个全局的filter API,用来做全局的切面。 

© 著作权归作者所有

Landas
粉丝 5
博文 33
码字总数 57625
作品 0
深圳
程序员
私信 提问
【二 HTTP编程】5. Body parsers

何为Body parser? 一个HTTP请求由请求头和请求体组成。header部分通常很小 —— 因此可以在内存中被安全的缓存,在Play中对应着RequestHeader模型。相对而言,body部分可能会非常大,这时它...

Landas
08/16
0
0
Java8新的异步编程方式 CompletableFuture(二)

上一篇文章,讲述了Future模式的机制、缺点,CompletableFuture产生的由来、静态工厂方法、complete()方法等等。 本文将继续整理CompletableFuture的特性。 3.3 转换 我们可以通过Completab...

Tony沈哲
2017/10/21
0
0
规则引擎及easyrules机制分析

一、什么是规则引擎 最近在工作中,遇到一类上层业务规则多变的场景。所以研究了规则引擎相关的内容,在这里可以与大家一起探讨。规则引擎相关的概念和相关业内使用,可以参考以下两篇文章,...

宸明
07/25
0
0
【完全跨域】异步上传文件并获得返回值

【完全跨域】异步上传文件并获得返回值 作者:php-note.com 发布于:2015-03-01 10:58 分类:JS/jQuery 浏览(595) AJAX可以进行数据的异步请求,但对于文件和跨域问题却束手无策。 Jsonp可以...

蜗牛奔跑
2015/11/02
0
1
【二 HTTP编程】1. Actions, Controllers and Results

什么是Action? 一个Play应用接收到的请求一般都交给 Action 来处理。 而一个 play.api.mvc.Action 本质上就是一个 (play.api.mvc.Request => play.api.mvc.Result) 函数,它被用来处理请求并...

Landas
07/21
0
0

没有更多内容

加载失败,请刷新页面

加载更多

可自定义扩展底部列表对话框ListBottomSheetDialogFragment

因为需要,为了方便,构建了一个可以自定义扩展的底部列表对话框,可以应付大部分场景。 效果图如下: 1.默认实现: 2.自定义列表实现 3.自定义头部和列表实现 一.可实现功能 1.默认可实现通...

明月春秋
今天
1
0
数据库---增删改查

增:insert into 表名(列名1,列名2) values(‘列值1’,‘列值2’) 多行数据处理:insert into 表名(列名1,列名2) select ‘列值1’,‘列值2’ union select ‘列值1.1’,‘列值2.2...

森林之下
今天
2
0
分布式/集群下session共享方案汇总

除去那些对容器依赖特别高的方案(如: 基于Tomcat的memcached-session-manager / tomcat-redis-session-manager,基于Jetty的jetty-nosql-memcache / jetty-session-redis ),自己整理了下...

哥本哈根的小哥
今天
2
0
Vue中的缩写:v-bind、v-on

v-bind 缩写:: 预期:any (with argument) | Object (without argument) 参数:attrOrProp (optional) 修饰符: .prop - 被用于绑定 DOM 属性。 .camel - (2.1.0+) 将 kebab-case 特性名转换......

文文1
今天
2
0
epoll中使用

1、一个线程epoll_wait时,另一个线程调用epoll_ctl是安全的。 2、使用edge触发,在socket有数据到来后,不收取数据,再次调用epoll_ctl将socket加入,仍会触发下一次动作。 asio用该方法来发...

gelare
今天
3
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部