我们解决这个问题的方法(在 Play 2.3 中)是构建一个 BodyParser,它并行运行 2 个 BodyParsers。使用它,您可以运行 BodyParsers.parse.raw 或除主程序之外的任何其他内容。将原始解析器与验证相结合(此处未显示),并生成一个 Left[Result] 以及您喜欢的任何错误消息和状态,以实现您想要的结果。
import scala.concurrent.ExecutionContext
import play.api.libs.concurrent.Execution.defaultContext
import play.api.libs.iteratee.Enumeratee
import play.api.libs.iteratee.Iteratee
import play.api.mvc.BodyParser
import play.api.mvc.RequestHeader
import play.api.mvc.Result
/**
* A BodyParser which executes any two provided BodyParsers in parallel.
*
* The results are combined in the following way:
* If any wrapped parser's Iteratee encounters an Error, that's the result.
* Else if the first parser's Iteratee finally yields a Left, this is used as the result.
* Else if the second parser's Iteratee yields a Left, this is used as the result.
* Else both Right results are combined in a Right[(A, B)].
*
* This can be used to e.g. provide the request's body both as a RawBuffer and a json-parsed
* custom model class, or to feed the body through a HMAC module in addition to parsing it.
*
* @author Jürgen Strobel <juergen@strobel.info>
*/
final case class DualBodyParser[+A, +B](
a: BodyParser[A],
b: BodyParser[B]
)(
implicit ec: ExecutionContext = defaultContext
)
extends BodyParser[(A, B)]
{
def apply(v1: RequestHeader): Iteratee[Array[Byte], Either[Result, (A, B)]] =
Enumeratee.zipWith(a(v1), b(v1)) {
case (Left(va), _) => Left(va)
case (_, Left(vb)) => Left(vb)
case (Right(va), Right(vb)) => Right((va, vb))
}
}
我们还创建了一个自定义的 ActionBuilder 变体,它使用 DualBodyParser 和我们自己的验证逻辑包装任何用户提供的 BodyParser,并且只有在身份验证成功后才将第二个的结果再次拼接到用户提供的代码块中。请注意,ActionBuilder 的这种变体复制并粘贴了大部分原始 ActionBuilder 代码,但需要一个参数来注入我们的身份验证逻辑,因此它是一个类而不是对象。将有不同的方法来解决这个问题。在 BodyParser 中封装完整的身份验证逻辑在 TODO 列表中,并且可能更容易和更好地组合。
/**
* An FooAuthenticatedAction does Foo authentication before invoking its action block.
*
* This replicates ActionBuilder and Action almost fully.
* It splices a parser.tolerantText BodyParser in, does Foo authentication with the
* body text, and then continues to call the provided block with the result of the
* provided body parser (if any).
*
* @param fooHelper An FooHelper configured to handle the incoming requests.
*
* @author Jürgen Strobel <juergen@strobel.info>
*/
case class FooAuthenticatedAction(fooHelper: FooHelper) extends ActionFunction[Request, Request] {
self =>
final def apply[A](bodyParser: BodyParser[A])(block: Request[A] => Result): Action[(String, A)] =
async(bodyParser) { req: Request[A] =>
Future.successful(block(req))
}
final def apply(block: Request[AnyContent] => Result): Action[(String, AnyContent)] =
apply(BodyParsers.parse.anyContent)(block)
final def apply(block: => Result): Action[(String, AnyContent)] =
apply(_ => block)
final def async(block: => Future[Result]): Action[(String, AnyContent)] =
async(_ => block)
final def async(block: Request[AnyContent] => Future[Result]): Action[(String, AnyContent)] =
async(BodyParsers.parse.anyContent)(block)
final def async[A](bodyParser: BodyParser[A])(block: Request[A] => Future[Result]): Action[(String, A)] =
composeAction(
new Action[(String, A)] {
def parser = DualBodyParser(parse.tolerantText, composeParser(bodyParser))
def apply(request: Request[(String, A)]) = try {
fooHelper.authenticate(request map (_._1)) match {
case Left(error) => failUnauthorized(error)
case Right(_key) => invokeBlock(request map (_._2), block)
}
} catch {
case e: NotImplementedError => throw new RuntimeException(e)
case e: LinkageError => throw new RuntimeException(e)
}
override def executionContext = self.executionContext
}
)
/**
* This produces the Result if authentication fails.
*/
def failUnauthorized(error: String): Future[Result] =
Future.successful( Unauthorized(error) )
protected def composeParser[A](bodyParser: BodyParser[A]): BodyParser[A] = bodyParser
protected def composeAction[A](action: Action[A]): Action[A] = action
// we don't use/support this atm
/**
override def andThen[Q[_]](other: ActionFunction[R, Q]): ActionBuilder[Q] = new ActionBuilder[Q]
**/
def invokeBlock[A](request: Request[A], block: (Request[A]) => Future[Result]) =
block(request)
}