【二 HTTP编程】6. 组合Action

本章描述了常用定义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,用来做全局的切面。 

转载于:https://my.oschina.net/landas/blog/1931714

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值