Воспроизведение / ведение журнала / печать тела ответа / запуск по перечислителю / буферизация тела

Я ищу способ напечатать тело ответа в Play Framework, у меня есть код, подобный этому:

object AccessLoggingAction extends ActionBuilder[Request] {
  def invokeBlock[A](request: Request[A], block: (Request[A]) => Future[Result]) = {
    Logger.info(s"""Request:
      id=${request.id} 
      method=${request.method} 
      uri=${request.uri} 
      remote-address=${request.remoteAddress} 
      body=${request.body}
    """)
    val ret = block(request)
    /*
    ret.map {result =>
      Logger.info(s"""Response:
      id=${request.id} 
      body=${result.body}
      """)
    }
    */ //TODO: find out how to print result.body (be careful not to consume the enumerator)
    ret
  }
}

В настоящее время закомментированный код работает не так, как я хотел, я имею в виду, что он напечатает:

Response:
id=1
body=play.api.libs.iteratee.Enumerator$$anon$18@39e6c1a2

Итак, мне нужно найти способ получить строку из Enumerator[Array[Byte]]. Я попытался понять концепцию Enumerator, прочитав это: http://mandubian.com/2012/08/27/understanding-play2-iteratees-for-normal-humans/

Итак... если я правильно понимаю

  1. Я не должен высушивать перечислитель в процессе преобразования его в String. В противном случае клиент ничего не получит.

  2. Предположим, я выяснил, как реализовать механизм T / filter. Но тогда... разве это не противоречит цели Play Framework как неблокирующей потоковой инфраструктуры (потому что я собирался бы собрать полный массив байтов в памяти, прежде чем вызывать to String для него, и, наконец, зарегистрировать его)?

Итак, как правильно записать ответ?

Заранее спасибо, рака

3 ответа

Решение

Этот код работает:

object AccessLoggingAction extends ActionBuilder[Request] {
  def invokeBlock[A](request: Request[A], block: (Request[A]) => Future[Result]) = {
    val start = System.currentTimeMillis

    Logger.info(s"""Request:
      id=${request.id} 
      method=${request.method} 
      uri=${request.uri} 
      remote-address=${request.remoteAddress} 
      body=${request.body}
    """)

    val resultFut = block(request)

    resultFut.map {result =>
      val time = System.currentTimeMillis - start
      Result(result.header, result.body &> Enumeratee.map(arrOfBytes => {
        val body = new String(arrOfBytes.map(_.toChar))
        Logger.info(s"""Response:
      id=${request.id} 
      method=${request.method}
      uri=${request.uri}
      delay=${time}ms 
      status=${result.header.status}
      body=${body}""")
        arrOfBytes
      }), result.connection)
    }
  }
}

Я частично узнал об этом здесь (о том, как получить байтовый массив из перечислителя): Scala Play 2.1: доступ к телам запросов и ответов в фильтре.

Я использую Play 2.3.7, в то время как ссылка, которую я дал, использует 2.1 (и все еще использует PlainResult, которого больше нет в 2.3).

Как мне кажется, если вы делаете вход внутри result.body &> Enumeratee.map (как предложено в /questions/820078/vosproizvedenie-vedenie-zhurnala-pechat-tela-otveta-zapusk-po-perechislitelyu-buferizatsiya-tela/820092#820092), а тело результата представлено более чем в одном чанке, тогда каждый чанк будет регистрироваться независимо. Вы, вероятно, не хотите этого.

Я бы реализовал это так:

val ret = block(request).flatMap { result =>
  val consume = Iteratee.consume[Array[Byte]]()
  val bodyF = Iteratee.flatten(result.body(consume)).run
  bodyF.map { bodyBytes: Array[Byte] =>
    //
    // Log the body
    //

    result.copy(body = Enumerator(bodyBytes))
  }
}

Но будьте осторожны: вся идея этого состоит в том, чтобы потреблять все данные из result.body Перечислитель перед входом в систему (и возвращением нового Перечислителя). Так что, если ответ большой, или вы полагаетесь на потоковую передачу, то это, вероятно, также то, что вы не хотите.

Я использовал приведенный выше ответ в качестве отправной точки, но заметил, что он будет регистрировать ответы только при наличии тела. Мы адаптировали это к этому:

var responseBody = None:Option[String]  
val captureBody = Enumeratee.map[Array[Byte]](arrOfBytes => {
    val body = new String(arrOfBytes.map(_.toChar))
    responseBody = Some(body)
    arrOfBytes
})
val withLogging = (result.body &> captureBody).onDoneEnumerating({
    logger.debug(.. create message here ..)
})
result.copy(body=withLogging)
Другие вопросы по тегам