Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to access the body of a Request[_] as byte array

Accessing the byte array of a request body is straightforward as long as one uses the appropriate body parsers when defining an Action, like request.body.asRaw....

However, I'm building an ActionBuilder for HMAC-secured Actions right now, where access to the body is inevitable. The Problem is that the definition of ActionBuilders is generic in terms of the request type and thus also of the body parser:

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

As A doesn't have any type constraints, there doesn't seem to be any way to get access to the request body from a Request[_].

In my specific case, it would work to do something like:

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

but that isn't an acceptable solution for me.

I also tried defining a custom body parser and applying it to the Request[_], but the results turned out empty.

How do I get access to the body of a Request[_] (a byte array representation would suffice)?


Update: it would also be an acceptable solution if I can get access to the request body within the ActionBuilder, for instance through wrapping the whole processing in another action that does custom parsing. But I don't see how that would work... The solution should be reusable in the sense that arbitrary user-defined actions can be used along with the HMAC-functionality, without interfering with any of the user logic.

like image 617
Leo Avatar asked Nov 10 '13 10:11

Leo


People also ask

How to write get response to byte array in Java?

How to write GET response to byte array? To send a HTTP GET request using Java can be done with the URL class. The openStream () method will send the GET request and return an input stream that can be used to read the HTTP response. Wrapping the stream in a BufferedInputStream can be done to improve the I/O performance.

How do I send binary data via HTTP request?

Binary data works the same way but with a different signature and content type for the HTTP request. and this HTTP request data with 'binary' content: POST http://localhost:5000/api/BodyTypes/RawBytesFormatter HTTP/1.1 Accept-Encoding: gzip,deflate Content-type: application/octet-stream Raw Wind and Water make the world go 'round.

How to read raw data from a request body?

You can capture the raw Request.Body and read the raw buffer out of that which is pretty straight forward. The easiest and least intrusive, but not so obvious way to do this is to have a method that accepts POST or PUT data without parameters and then read the raw data from Request.Body:

How to extract the data sent as JSON in request body?

Here is how you can extract the data that was sent as JSON in the request body. If you are using Express, that's quite simple: use the express.json () middleware which is available in Express v4.16.0 onwards.


1 Answers

The way we solved this (in Play 2.3) is to construct a BodyParser which runs 2 BodyParsers in parallel. Using this you can run a BodyParsers.parse.raw or whatever in addition to your main one. Combine the raw parser with validation (not shown here), and produce a Left[Result] with any error message and status you like, to achieve your 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'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 <[email protected]>
 */
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 variant of ActionBuilder which wraps any user provided BodyParser with a DualBodyParser and our own validation logic, and splices out the result from the second one again to the user-provided code block only after authentication has succeeded. Note this variation on ActionBuilder copy&pastes much of the original ActionBuilder code, but takes a parameter to inject our authentication logic, so it's a class and not an object. There would be different ways to solve this problem. Encapsulating the full authentication logic in a BodyParser is on the TODO list, and probably easier and better composable.

/**
 * 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 <[email protected]>
 */
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)
}
like image 105
Jürgen Strobel Avatar answered Sep 22 '22 23:09

Jürgen Strobel