如何以字节数组的形式访问Request [_]的主体

时间:2013-11-10 10:38:03

标签: scala playframework playframework-2.2

只要在定义Action时使用适当的主体解析器(例如request.body.asRaw...),访问请求主体的字节数组就很简单。

然而,我现在正在为HMAC安全的行动建立一个ActionBuilder,在这里,身体的访问是不可避免的。问题是ActionBuilders的定义在请求类型方面是通用的,因此也是主体解析器的定义:

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

由于A没有任何类型限制,似乎没有任何方法可以从Request[_]访问请求正文。

在我的具体案例中,它可以做类似的事情:

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

但对我来说这不是一个可以接受的解决方案。

我还尝试定义自定义正文解析器并将其应用于Request[_],但结果显示为空。

如何访问Request[_]的主体(字节数组表示就足够了)?


更新:如果我可以访问ActionBuilder中的请求主体,例如通过将整个处理包装在另一个执行自定义解析的操作中,它也是一个可接受的解决方案。但我不明白这是如何工作的...解决方案应该是可重用的,因为任意用户定义的动作可以与HMAC功能一起使用,而不会干扰任何用户逻辑。

2 个答案:

答案 0 :(得分:1)

我们解决这个问题的方法(在Play 2.3中)是构建一个BodyParser,它并行运行2个BodyParsers。使用此方法,您可以运行BodyParsers.parse.raw或除主要内容之外的任何内容。将原始解析器与验证(此处未显示)组合在一起,生成一个左[结果],其中包含您喜欢的任何错误消息和状态,以实现您想要的结果。

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的自定义变体,它将任何用户提供的BodyParser包装为DualBodyParser和我们自己的验证逻辑,并且只有在身份验证成功后才将第二个结果再次拼接到用户提供的代码块。请注意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)
}

答案 1 :(得分:0)

请求类只有一个body字段,当一个body解析器成功解析了请求体时,将导致创建一个Request [A]实例。通常,将原始字节与A实例一起使用并不重要,因为这会使每个请求的内存量增加一倍。

正文解析器可以为其提供的每个字节块继续消耗或提前返回。 也许您可以将Hmac验证内容实现为包装体解析器?

对于每个输入块(Array [Byte]),您将使用包装iteratee / enumeratee收集字节。当输入结束时,你会触发对这些字节的hmac签名计算/验证,如果它无效则可以返回BadRequest,或者将整个主体推送到实际的主体解析器。