The way we solved this (in Play 2.3) is to build a BodyParser that runs 2 BodyParsers in parallel. Using this, you can run BodyParsers.parse.raw or something else in addition to your main one. Combine the raw parser with validation (not shown here) and create a left [result] with any error message and status that you like to achieve the desired 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 Iteratee encounters an Error, that the result. * Else if the first parser Iteratee finally yields a Left, this is used as the result. * Else if the second parser 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 eg provide the request 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)) } }
We also created a custom version of ActionBuilder that wraps any user provided by BodyParser using DualBodyParser and our own validation logic and reconnects the result to the second with the user-provided code block only after the authentication is successful. Note that this version of the ActionBuilder copies will copy most of the ActionBuilder source code, but accepts a parameter to enter our authentication logic, so this is a class, not an object. There would be different ways to solve this problem. Encapsulation of the full authentication logic in BodyParser is included in the TODO list and is probably simpler and better.
/** * 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) }