一、自定义action

从一个日志装饰器的例子开始

1.在invokeBlock方法中实现

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)
}
}

在控制器中使用依赖注入来得到一个实例

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

ActionBuilder提供了所有构建action的方法,因此也适用于自定义的解析器

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

2.编写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 executionContext = action.executionContext
}
我们也可以使用Action动作构建器来构建动作,而无需定义我们自己的动作类
import play.api.mvc._

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

也可以使用composeAction方法将操作混合到操作构建器中

class LoggingAction @Inject() (parser: BodyParsers.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")}

我们也可以在没有动作构建器的情况下混合包装动作

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

3.更复杂的actions

上面都是不影响请求的操作。当然,我们也可以读取和修改传入的请求对象,play支持X-Forwarded-For headers

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)
}

阻断请求

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"))
}
}

修改返回的结果

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

二、不同的请求类型

1.一些预先定义好的实现了ActionFunction的特质

  • ActionTransformer 可以更改请求,例如增加额外的信息
  • ActionFilter 可以选择性的拦截请求,例如产生错误,不改变请求值
  • ActionRefiner 是上面两种情况的一般情况
  • ActionBuilder i请求作为输入的特例,因此可以构建actions

2.认证

我们可以轻松实现我们自己的身份验证操作转换器,该转换器根据原始请求确定用户并将其添加到新的UserRequest。请注意,这也是一个ActionBuilder因为它使用一个简单Request作为输入

import play.api.mvc._
class UserRequest[A](val username: 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还提供内置身份验证操作构建器。有关这方面的信息以及如何使用它可以在这里找到。内置的身份验证操作构建器只是一个简便的帮助工具,可以将简单情况下实现身份验证所需的代码最小化,其实现方式与上述示例非常相似。

如果内置的助手不合适,可以自己编写

3.向请求添加信息

现在让我们考虑一个适用于Item对象的REST API 。/item/:itemId下可能有许多路径,每个路径都要查找item。在这种情况下,把下面的逻辑放入一个action函数可能会很有用

首先,我们将创建一个请求对象,把Item添加到我们的UserRequest

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

现在我们将创建一个查询item的action,要么返回一个错误(Left)要么返回一个新的ItemRequestRight)。请注意,这个action精炼是定义在在获取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)
}
}

4.验证请求

最后,我们可能需要一个action函数来验证请求是否应该继续。例如,也许我们想要检查UserAction中的用户是否有权限访问ItemAction中的item,如果没有就报错

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

5.现在我们可以将这些action函数连接在一起(从一个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还提供了一个全局过滤器API,这对于全局交叉问题很有用。

三、语言、内容的设置

1.语言

你可以使用play.api.mvc.RequestHeader#acceptLanguagesAccept-Language header中检索到请求可接受的语言列表,并根据其质量值对它们进行排序。Play在play.api.mvc.Controller#lang方法中使用它,提供一个隐式play.api.i18n.Lang值给你的actions,因此它们会自动使用最好的语言(如果你的应用程序支持的话,否则使用应用程序的默认语言)

2.内容

类似地,该play.api.mvc.RequestHeader#acceptedTypes方法给出请求的可接受结果的MIME类型列表。它从Accept header中检索到它们,并根据它们的质量因子对它们进行排序。

实际上,Accept header并不包含MIME类型,除了媒体范围(一个接收所有文本结果的请求可能会设置text/*范围,*/*范围意味着可接收所有结果类型)。控制器提供更高级的render方法来帮助您处理媒体范围。例如下面的action定义

val list = Action { implicit request =>
val items = Item.findAll
render {
case Accepts.Html() => Ok(views.html.list(items))
case Accepts.Json() => Ok(Json.toJson(items))
}
}

Accepts.Html()Accepts.Json()是提取器,测试给定的范围是否匹配text/htmlapplication/json。为了确定优先权,该render方法把play.api.http.MediaRange的部分函数带到play.api.mvc.Result,并尝试将其应用到在请求中找到的每个范围

如果你的函数没有可支持的范围,则返回NotAcceptable。

例如,如果客户端使用以下值作为Accept header发出请求,Accept*/*;q=0.5,application/json意味着它接受任何结果类型但偏好JSON,上面的代码将返回JSON表示。另一种情况application/xml意味着它只接受XML,则上面的代码将返回NotAcceptable

3.请求提取器

请参阅play.api.mvc.AcceptExtractors.Accepts对象的API文档,以获取render方法中开箱即用支持的MIME类型列表。你可以使用play.api.mvc.Accepting case类轻松地为给定的MIME类型创建自己的提取器,

例如下面的代码创建一个提取器来检查媒体范围是否匹配audio/mp3MIME类型:

val AcceptsMp3 = Accepting("audio/mp3")
render {
case AcceptsMp3() => ???
}
}

四、错误的处理 

1.支持自定义错误处理

如果使用BuiltInComponents构造自己的应用程序,要覆盖该httpErrorHandler方法以返回你的自定义处理程序的实例。

如果使用运行时依赖注入(例如Guice),则可以在运行时动态加载错误处理程序。最简单的方法是在根包创建一个类ErrorHandler(实现了HttpErrorHandler,例如:

import play.api.http.HttpErrorHandler
import play.api.mvc._
import play.api.mvc.Results._
import scala.concurrent._
import javax.inject.Singleton @Singleton
class ErrorHandler extends HttpErrorHandler { def onClientError(request: RequestHeader, statusCode: Int, message: String) = {
Future.successful(
Status(statusCode)("A client error occurred: " + message)
)
} def onServerError(request: RequestHeader, exception: Throwable) = {
Future.successful(
InternalServerError("A server error occurred: " + exception.getMessage)
)
}
}

如果你不想将错误处理程序置于根包中,或者你想要为不同的环境配置不同的错误处理程序,你可以通过在application.conf中配置play.http.errorHandler来实现,例如:play.http.errorHandler = "com.example.ErrorHandler"

2.扩展默认的错误处理程序

Play的默认错误处理提供了许多有用的函数。例如,在开发模式下,当发生服务器错误时,Play将尝试在你的应用程序中找到并渲染导致该异常的代码片段,以便可以快速查看并识别问题。你可能希望在生产中提供自定义服务器错误,同时仍在开发中维护该功能。

为了促进这一点,Play提供了一种DefaultHttpErrorHandler可以覆盖的便捷方法,以便可以将自定义逻辑与Play现有的行为混合在一起。例如,要仅在生产环境中提供自定义服务器错误消息,而且保留开发错误消息,并且还想提供特定的禁止错误页面

import javax.inject._

import play.api.http.DefaultHttpErrorHandler
import play.api._
import play.api.mvc._
import play.api.mvc.Results._
import play.api.routing.Router
import scala.concurrent._ @Singleton
class ErrorHandler @Inject() (
env: Environment,
config: Configuration,
sourceMapper: OptionalSourceMapper,
router: Provider[Router]
) extends DefaultHttpErrorHandler(env, config, sourceMapper, router) { override def onProdServerError(request: RequestHeader, exception: UsefulException) = {
Future.successful(
InternalServerError("A server error occurred: " + exception.getMessage)
)
} override def onForbidden(request: RequestHeader, message: String) = {
Future.successful(
Forbidden("You're not allowed to access this resource.")
)
}
}

查看完整的API文档DefaultHttpErrorHandler以查看可用于覆盖的方法以及如何利用它们

PLAY2.6-SCALA(五) Action的组合、范围的设置以及错误的处理的更多相关文章

  1. Struts(五)Action的访问

    在struts开发中,Action作为框架的核心类,实现对用户的请求的处理,Action被称为业务逻辑控制器.一个Action类代表一次请求或调用.Action就是用来处理一次用户请求的对象 Acti ...

  2. struts2 笔记01 登录、常用配置参数、Action访问Servlet API 和设置Action中对象的值、命名空间和乱码处理、Action中包含多个方法如何调用

    Struts2登录 1. 需要注意:Struts2需要运行在JRE1.5及以上版本 2. 在web.xml配置文件中,配置StrutsPrepareAndExecuteFilter或FilterDis ...

  3. LoadRunner中Action的迭代次数的设置和运行场景中设置

    LoadRunner中Action的迭代次数的设置和运行场景中设置 LoadRunner是怎么重复迭代和怎么增加并发运行的呢? 另外,在参数化时,对于一次压力测试中均只能用一次的资源应该怎么参数化呢? ...

  4. 笔记01 登录、常用配置参数、Action访问Servlet API 和设置Action中对象的值、命名空间和乱码处理、Action中包含多个方法如何调用

    Struts2登录 1. 需要注意:Struts2需要运行在JRE1.5及以上版本 2. 在web.xml配置文件中,配置StrutsPrepareAndExecuteFilter或FilterDis ...

  5. yii2 控制器里 action 大小写组合造成的路由问题

    yii1中, 若存在如下控制器 class BindController extends CController { public function actionGetMobilePhone () { ...

  6. 设计模式C++学习笔记之十五(Composite组合模式)

      15.1.解释 概念:将对象组合成树形结构以表示“部分-整体”的层次结构.Composite使得用户对单个对象和组合的使用具有一致性. main(),客户 CCorpNode,抽象基类,实现基本信 ...

  7. Struts2(五)Action二配置

    一.method参数 action package com.pb.web.action; public class HourseAction { public String add(){ System ...

  8. 系统学习 Java IO (五)----使用 SequenceInputStream 组合多个流

    目录:系统学习 Java IO---- 目录,概览 SequenceInputStream 可以将两个或多个其他 InputStream 合并为一个. 首先,SequenceInputStream 将 ...

  9. vuex 源码分析(五) action 详解

    action类似于mutation,不同的是Action提交的是mutation,而不是直接变更状态,而且action里可以包含任意异步操作,每个mutation的参数1是一个对象,可以包含如下六个属 ...

随机推荐

  1. sas单变量的特征分析

    sas单变量的特征分析 大炮,我有个烦恼,我领导最近老叫我单变量结合因变量分析,但是都是分段分析,我总是写proc sql然后group by ,但是这个过程好无聊啊,有木有什么新的代码,让我可以分析 ...

  2. Android Studio增加assets目录、raw目录

    assets与res/raw不同 assets目录是Android的一种特殊目录,用于放置APP所需的固定文件,且该文件被打包到APK中时,不会被编码到二进制文件. Android还存在一种放置在re ...

  3. Django--Cookie和Session组件

    什么是Cookie: cookie的工作原理是:由服务端产生内容,浏览器收到请求之后保存在本地:当浏览器再次访问的时候,浏览器会自动带上这个cookie,这样服务端就能去通过这个cookie来判断你是 ...

  4. 部分树形DP的优化

    ural1018. Binary Apple Tree 题目大意 有一棵n个节点的树,树上每个节点有一个值,选择m个节点使这些节点值的和最大 要求:如果选当前节点,则必须选它的父节点 解法: 我们设d ...

  5. Go之路一

    一.声明变量 var a int var b string var c []float32 var d func() bool var e struct{ x int } 第1行,声明一个整型类型的变 ...

  6. ngVerify - 更高效的 angular 表单验证

    ngVerify v1.5.0 a easy Angular Form Validation plugin.简洁高效的__angular表单验证插件__ See how powerful it.看看它 ...

  7. MySQL语句错误及解决方案

    1.group by查询错误 ERROR 1055 (42000): Expression #1 of SELECT list is not in GROUP BY clause and contai ...

  8. T2848 列车调度(二分或dp)

    题目背景 自行脑补, 题目描述 有N辆列车,标记为1,2,3,…,N.它们按照一定的次序进站,站台共有K个轨道,轨道遵从先进先出的原则.列车进入站台内的轨道后可以等待任意时间后出站,且所有列车不可后退 ...

  9. CodeVS3958 火车进站

    3958 火车进站 时间限制: 1 s 空间限制: 256000 KB 题目等级 : 大师 Master         题目描述 Description 火车站内往往设有一些主干线分叉出去的铁路支路 ...

  10. 洛谷P2258 子矩阵[2017年5月计划 清北学堂51精英班Day1]

    题目描述 给出如下定义: 子矩阵:从一个矩阵当中选取某些行和某些列交叉位置所组成的新矩阵(保持行与列的相对顺序)被称为原矩阵的一个子矩阵. 例如,下面左图中选取第2.4行和第2.4.5列交叉位置的元素 ...