Scala 使用Play'时如何包装动作(以任何顺序);谁是ActionBuilder?

Scala 使用Play'时如何包装动作(以任何顺序);谁是ActionBuilder?,scala,authentication,playframework,playframework-2.0,composition,Scala,Authentication,Playframework,Playframework 2.0,Composition,我正在使用创建各种操作来保护我的控制器。例如,我实施了IsAuthenticated,以确保只有在用户登录时才能访问某些操作: case class AuthRequest[A](user: String, request: Request[A]) extends WrappedRequest[A](request) private[controllers] object IsAuthenticated extends ActionBuilder[AuthRequest] { def in

我正在使用创建各种操作来保护我的控制器。例如,我实施了
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"))
    }
  }
}
def auth = IsAuthenticated { implicit authRequest =>
  val user = authRequest.user
  Ok(user)
}
使用
IsAuthenticated
我可以(1)将操作限制为登录的用户,以及(b)访问正在登录的用户:

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"))
    }
  }
}
def auth = IsAuthenticated { implicit authRequest =>
  val user = authRequest.user
  Ok(user)
}
此外,我使用ActionBuilder
HasToken
确保在请求头中存在令牌的情况下调用操作(并且,我可以访问令牌值):

通过这种方式,我可以确保在该令牌存在的情况下调用了操作:

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的
ActionBuilder定义的
Action
s?
在上述示例中,无论我是否将
IsAuthenticated
包装在
HasToken
中,或者以其他方式包装,效果都是一样的:用户必须登录并出示令牌


注意:我创建了一个。

动作生成器的输出是一个动作,一个动作本质上是一个来自request=>future result的函数,因此您实际上可以这样调用它:

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]
}
def tokenAndAuth=HasToken.async{implicit tokenRequest=>
IsAuthenticated{隐式authRequest=>
val token=tokenRequest
val user=authRequest.user
好的(“哇哦!”)
}(tokenRequest)//ActionBuilder
ActionBuilder并不是为临时合成而设计的,而是为了构建一个动作层次结构,这样您就可以在整个控制器中只使用几个动作

因此,在您的示例中,您应该将
IsAuthenticated
构建在
HasToken
之上,作为I

这是一个可行的解决方案,实际上可以简化您的代码

本质化 特别合成可以通过EssentialActions实现(仅仅是因为它们没有从2.1版更改),但正如Johan指出的那样,它们有一些缺点。它们的API也不是真正用于特别用途的,迭代对象的级别太低,对于控制器操作来说太麻烦

行动 最后,您的最后一个选择是直接编写操作。默认情况下,操作不支持传递WrappedRequest(这就是ActionBuilder存在的原因)。但是,您仍然可以传递WrappedRequest并让下一个操作处理它

以下是到目前为止我想到的最好的,我想它相当脆弱

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)
      }
    }
  }
}
结果 您也可以在结果级别进行合成,并且只使用内置操作。这在尝试排除错误处理和其他重复内容时特别有用。我有一个

结论
我们仍然缺少Play 2.1提供的动作合成功能。到目前为止,在我看来,ActionBuilder+Result composition作为其继任者是赢家。

好吧,如果我使用
EssentialAction
,我将无法再将数据传递到每个动作(如令牌和用户),对吗?据我所知,这是
ActionBuilder
WrappedRequest
的独特功能。你不能修饰请求,但这并不意味着你不能传递数据。EssentialAction本质上是一个RequestHeader=>BodyParser,因此你可以做同样的合成but单独传递额外提取的数据。虽然只需要很少的时间(和注释中的空间)来编写示例。啊-明白了。我认为上面的方法在这里得到了演示: