How to access the request body [_] as an array of bytes

Accessing the byte array of the request body is simple if you use the appropriate body parsers when defining the action, for example request.body.asRaw...

However, I am creating an ActionBuilder for HMAC-protected actions right now when access to the body is inevitable. The problem is that the definition of ActionBuilders is common in terms of the type of request and, therefore, the parser of the body:

 def invokeBlock[A](request: Request[A], block: HmacRequest[A] => Future[SimpleResult]) 

Since A does not have any type restrictions, there seems to be no way to access the request body from Request[_] .

In my specific case, this will work to do something like:

 request.body.asInstanceOf[AnyContentAsJson].json.toString()... 

but this is not an acceptable solution for me.

I also tried to define my own body analyzer and apply it to Request[_] , but the results were empty.

How to access the body of Request[_] (sufficient representation of an array of bytes)?


Update: this would also be an acceptable solution if I can access the request body in ActionBuilder , for example, by transferring all the processing to another action that performs individual parsing. But I don’t see how this will work ... The solution should be reused in the sense that arbitrary user actions can be used together with the HMAC functionality without interfering with any user logic.

+6
source share
2 answers

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) } 
+1
source

The request class has only one field for the body when the body parser processed the request body successfully, which will lead to the creation of an instance of Request [A]. It is usually not interesting to have raw bytes with instance A, since each request will require twice as much memory.

The body analyzer can either continue to consume or return earlier for each block of bytes with which it is supplied. Maybe you can implement the Hmac verification material as a body analyzer for wrapping?

For each input fragment (Array [Byte]) you will collect bytes with iteratee / enumeratee wrap. When the end of the input comes, you call the hmac signature calculation / verification on these bytes and can return BadRequest if it is not valid, or click the whole body on the actual body parser.

0
source

Source: https://habr.com/ru/post/957768/


All Articles