使用Play的ActionBuilder时如何包装动作(以任何顺序)?

Mar*_*tin 9 authentication scala composition playframework playframework-2.0

我正在使用Play的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"))
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

使用IsAuthenticated我可以(1)将操作限制为已登录的用户,以及(b)访问登录的用户:

def auth = IsAuthenticated { implicit authRequest =>
  val user = authRequest.user
  Ok(user)
}
Run Code Online (Sandbox Code Playgroud)

此外,我使用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"))
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

这样,我可以确保使用该令牌调用操作:

def token = HasToken { implicit tokeRequest =>
  val token = tokeRequest.token
  Ok(token)
}
Run Code Online (Sandbox Code Playgroud)

到现在为止还挺好...

但是,我怎么能包装(或嵌套/组合)上面定义的那些动作呢?例如,我想确保(a)用户将登录并且(b)该令牌将存在:

def tokenAndAuth = HasToken { implicit tokeRequest =>
  IsAuthenticated { implicit authRequest =>
    val token = tokeRequest.token
    val user = authRequest.user
  }
}
Run Code Online (Sandbox Code Playgroud)

但是,上述操作无法编译.我尝试了许多不同的实现,但总是无法实现所需的行为.

一般而言:我怎样才能以任意顺序Action使用Play定义ActionBuilder在上面的例子中,它不会有问题,如果我想包IsAuthenticatedHasToken或周围其他方式-的影响将是相同的:用户必须先登录,并必须出示令牌.

注意:我创建了一个提供完整源代码Gist.

Mar*_*ier 8

ActionBuilder

ActionBuilders不是用于临时组合,而是用于构建操作层次结构,因此您最终只能在整个控制器中使用几个操作.

所以在你的例子中,你应该建立IsAuthenticatedHasToken在这里说明的顶部.

这是一个可行的解决方案,实际上可以简化您的代码.你真的需要经常在现场作曲吗?

EssentialAction

使用EssentialActions可以实现特别的组合(仅仅因为他们没有从2.1改变),但他们有一些缺点,正如Johan指出的那样.它们的API也不是真正用于临时使用,并且Iteratees对于控制器操作来说太低级且太麻烦.

操作

所以最后你的最后一个选择就是直接编写Actions.默认情况下,操作不支持传递WrappedRequest(这就是ActionBuilder存在的原因).但是,您仍然可以传递WrappedRequest并与其进行下一个Action处理.

以下是迄今为止我提出的最好的,我猜是相当脆弱的.

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)
      }
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

结果

您还可以在结果级别进行撰写,仅使用内置操作.这在尝试分解错误处理和其他重复的东西时尤其有用.我在这里有一个例子.

结论

我们仍然缺少Play 2.1的动作组合提供的功能.到目前为止,似乎ActionBuilder + Result组合作为其继任者的赢家.