PLAY2.6-SCALA(四) Action组合

一、自定义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,这对于全局交叉问题很有用。

猜你喜欢

转载自www.cnblogs.com/feiyumo/p/9141017.html