文档章节

【七】组合Action

Landas
 Landas
发布于 08/21 21:04
字数 1467
阅读 8
收藏 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
粉丝 3
博文 23
码字总数 48704
作品 0
深圳
程序员
play2.0文档-面向java开发者(6)

Action composition Action组合 This chapter introduces several ways to define generic action functionality. 这章节介绍几个定义通用action功能的方式。 Reminder about actions actio......

老盖
2012/04/12
0
0
Java8新的异步编程方式 CompletableFuture(二)

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

Tony沈哲
2017/10/21
0
0
一个简洁的状态管理库 - iFlow

iFlow 是一个简洁和强大的状态管理库,iFlow 没有任何依赖包,且非常小(5k)。 特性 支持普通function和class - 它很简单,同时也可设计符合各种需求状态管理架构。 Store组合 - Store Tree可...

unadlib
01/31
0
0
android游戏开发框架libgdx的使用(六)--演员和演出

本文使用的libgdx是0.92版本,和现在的最新版可能有一些不一样的地方。全文内容仅供参考。 上一篇说到UI,然后之前说到舞台,现在我们可以很轻松的构建一出戏了。 因为actor类在绘制是以x,y...

长平狐
2013/11/25
190
0
Struts2的struts.xml的配置细节

(七)Struts2的struts.xml的配置细节 这里说的struts.xml说到的包含内容如下: 1.include包含另一个xml 2.默认action,主要用于跳转不存在的页面,到我们自定义的一个好看的页面,而不是难看的错误...

Zhao-Qian
2013/12/09
0
0

没有更多内容

加载失败,请刷新页面

加载更多

flume -- fileChannel简要分析其过程

flume之event写入FileChannel doPut(event)-->获取共享锁后[log.lockShared();]-->FlumeEventPointer ptr = log.put(transactionID, event); 此处的log.put即将transactionID及event进行后续......

-九天-
29分钟前
2
0
Linux与FreeBSD有什么区别?

基础 许多人所称的“Linux”实际上不是 Linux。Linux 从技术上说只是 Linux 内核,典型的 Linux 发行版则包括了 Linux 内核和许多软件。这是为什么 Linux 有时被称为 GNU/Linux。事实上,许多...

linux-tao
37分钟前
3
0
jQuery学习笔记180924

jQuery - AJAX 简介 什么是 AJAX? AJAX = 异步 JavaScript 和 XML(Asynchronous JavaScript and XML)。 简短地说,在不重载整个网页的情况下,AJAX 通过后台加载数据,并在网页上进行显示...

颖伙虫
50分钟前
1
0
springboot整合vue小试牛刀

序 本文主要研究一下如何在springboot工程整合vue maven <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-we......

go4it
51分钟前
2
0
使用python的profiler工具

主要用来检测python coding的执行时间 fly profiler

steel7c4
55分钟前
2
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部