2013-11-10 3 views
6

Доступ к массиву байтов тела запроса является простым, если при определении действия использовать подходящие синтаксические анализаторы тела, например request.body.asRaw....Как получить доступ к телу запроса [_] как массив байтов

Однако, сейчас я создаю ActionBuilder для HMAC-обеспеченных действий, где доступ к телу неизбежен. Проблема в том, что определение ActionBuilders является родовым по типу запроса и, таким образом, также парсера тела:

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

Как A не имеет каких-либо ограничений типа, как представляется, не будет каким-либо образом чтобы получить доступ к органу запроса с Request[_].

В моем конкретном случае, это будет работать, чтобы сделать что-то вроде:

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

, но это не является приемлемым решением для меня.

Я также попытался определить пользовательский парсер тела и применить его к Request[_], но результаты оказались пустыми.

Как получить доступ к телу Request[_] (достаточно представить представление массива байтов)?


Update: было бы также приемлемым решением, если я могу получить доступ к телу запроса внутри ActionBuilder, например, пути обертывания всей обработки в другом действии, что делает пользовательский разборе. Но я не вижу, как это будет работать ... Решение должно быть повторно использовано в том смысле, что любые пользовательские действия могут использоваться вместе с функциональностью HMAC, не мешая какой-либо логике пользователя.

ответ

0

Класс запроса имеет только одно поле для тела, когда парсер тела проанализировал тело запроса успешно, что приведет к созданию экземпляра Request [A]. Обычно не интересно иметь необработанные байты вместе с экземпляром A, так как для каждого запроса потребуется вдвое больше объема памяти.

Анализатор тела может либо продолжать употреблять, либо возвращать раннее для каждого блока байтов, которым он снабжен. Возможно, вы могли бы реализовать материал проверки Hmac в качестве анализатора тела для обертывания?

Для каждого блока ввода (массив [Byte]) вы собрали бы байты с обтеканием iteratee/enumeratee. Когда приходит конец ввода, вы запускаете вычисление/проверку подписи hmac на этих байтах и ​​можете вернуть BadRequest, если это недействительно, или направить весь тело на фактический парсер.

+0

Если мне нужно написать 'Action (parse.hmacParser) {...', это не было бы приемлемым решением для меня, поскольку это явно ограничивает повторное использование. Если бы было возможно иметь «ActionBuilder», который использует свой собственный «BodyParser» внутри себя и каким-то образом подвергает анализируемым данным, но вызывает завернутое «действие» с оригинальным парсером тела, что было бы приемлемым. Но я не понимаю, как это будет возможно. – Leo

+0

Если у вас есть hmacParser (parse.actualParser), я думаю, вы могли бы создать конструктор действий или вспомогательные методы, чтобы получить что-то вроде HmacProtected (parse.actualParser) {... – johanandren

+0

Кроме того, вы можете переопределить composeParser при создании ActionBuilder и обернуть фактический синтаксический анализатор с вашим синтаксическим анализатором hmac. – johanandren

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 <[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)) 
     } 
} 

Мы также создали собственный вариант 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 <[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) 
} 
Смежные вопросы