我正在使用Play's ActionBuilder来创建保护我的控制器的各种操作。例如,我实现了IsAuthenticated
以确保只有在用户登录时才能访问某些操作:
case class AuthRequest[A](user: String, request: Request[A]) extends WrappedRequest[A](request)
private[controllers] object IsAuthenticated extends ActionBuilder[AuthRequest] {
def invokeBlock[A](req: Request[A], block: (AuthRequest[A]) => Future[SimpleResult]) = {
req.session.get("user").map { user =>
block(new AuthRequest(user, req))
} getOrElse {
Future.successful(Results.Unauthorized("401 No user\n"))
}
}
}
使用IsAuthenticated
我可以(1)将操作限制为已登录的用户,以及(b)访问登录用户:
def auth = IsAuthenticated { implicit authRequest =>
val user = authRequest.user
Ok(user)
}
此外,我使用ActionBuilder HasToken
来确保在请求的标头中存在令牌时调用了一个操作(并且,我可以访问令牌值):
case class TokenRequest[A](token: String, request: Request[A]) extends WrappedRequest[A](request)
private[controllers] object HasToken extends ActionBuilder[TokenRequest] {
def invokeBlock[A](request: Request[A], block: (TokenRequest[A]) => Future[SimpleResult]) = {
request.headers.get("X-TOKEN") map { token =>
block(TokenRequest(token, request))
} getOrElse {
Future.successful(Results.Unauthorized("401 No Security Token\n"))
}
}
}
这样,我可以确保使用该令牌调用动作:
def token = HasToken { implicit tokeRequest =>
val token = tokeRequest.token
Ok(token)
}
到目前为止,非常好......
但是,我怎么能包装(或嵌套/组合)上面定义的那些动作?例如,我想确保(a)用户将登录和(b)该令牌将存在:
def tokenAndAuth = HasToken { implicit tokeRequest =>
IsAuthenticated { implicit authRequest =>
val token = tokeRequest.token
val user = authRequest.user
}
}
但是,上述操作无法编译。我尝试了许多不同的实现,但总是无法实现所需的行为。
一般而言:如何以任意顺序撰写使用Play Action
定义的ActionBuilder
?在上面的示例中,如果我将{在IsAuthenticated
或其他方面{1}} - 效果将是相同的:用户必须登录并且必须提供令牌。
答案 0 :(得分:8)
ActionBuilders不是用于临时组合,而是用于构建操作层次结构,因此您最终只能在整个控制器中使用几个操作。
因此,在您的示例中,您应该IsAuthenticated
在HasToken
之上构建case class HasToken[A](action: Action[A]) extends Action[A] {
def apply(request: Request[A]): Future[SimpleResult] = {
request.headers.get("X-TOKEN") map { token =>
action(TokenRequest(token, request))
} getOrElse {
Future.successful(Results.Unauthorized("401 No Security Token\n"))
}
}
lazy val parser = action.parser
}
case class IsAuthenticated[A](action: Action[A]) extends Action[A] {
def apply(request: Request[A]): Future[SimpleResult] = {
request.session.get("user").map { user =>
action(new AuthRequest(user, request))
} getOrElse {
Future.successful(Results.Unauthorized("401 No user\n"))
}
}
lazy val parser = action.parser
}
object ActionComposition extends Controller {
def myAction = HasToken {
Action.async(parse.empty) { case TokenRequest(token, request) =>
Future {
Ok(token)
}
}
}
def myOtherAction = IsAuthenticated {
Action(parse.json) { case AuthRequest(user, request) =>
Ok
}
}
def both = HasToken {
IsAuthenticated {
Action(parse.empty) { case AuthRequest(user, request: TokenRequest[_]) =>
Ok(request.token)
}
}
}
}
。
这是一个可行的解决方案,实际上可以简化您的代码。你真的需要经常在现场作曲吗?
使用EssentialActions可以实现特别的组合(仅仅因为他们没有从2.1改变),但他们有一些缺点,正如约翰所指出的那样。他们的API也不是真正用于临时使用,并且Iteratees对于控制器操作而言太低级且太麻烦。
所以最后你的最后一个选择就是直接编写Actions。默认情况下,操作不支持传递WrappedRequest(这就是ActionBuilder存在的原因)。但是,您仍然可以传递WrappedRequest并与其进行下一个Action处理。
以下是我迄今为止所提出的最好的,而且我认为它是相当脆弱的。
{{1}}
您还可以在结果级别进行撰写,仅使用内置操作。这在尝试分解错误处理和其他重复的东西时尤其有用。我有illustrated here。
我们仍然缺少Play 2.1的动作组合所提供的功能。到目前为止,似乎ActionBuilder + Result组合作为其继任者的胜利者。
答案 1 :(得分:4)
动作构建器的输出是动作,动作本质上是来自request =>的函数。未来的结果,所以你实际上可以这样称呼:
def tokenAndAuth = HasToken.async { implicit tokenRequest =>
IsAuthenticated { implicit authRequest =>
val token = tokenRequest
val user = authRequest.user
Ok("woho!")
}(tokenRequest) // <-- call the inner action yourself, returns Future[SimpleResult]
}
如果你想要解析一个正文,可能会有问题,我认为它会被外部请求的正文解析器解析但我不确定如果你指定一个内部解析器会做什么解析器。
你不能以更简单的方式做到这一点的原因是你不仅想要编写逻辑而且还要将数据传递到每个动作中,如果你有动作只是为了丢失auth或令牌而保释你绝对可以组成它们正如关于动作作品的游戏文档所描述的那样。
旁注:由于您只是查看标题而不是访问任何一个自定义操作装饰器中的主体,因此最好查看EssentialAction:s,因为那些可以让您在不首先解析主体的情况下拒绝请求当auth或令牌丢失时。