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

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

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 как неблокирующей потоковой среды (потому что я буду создавать полный массив байтов в памяти, прежде чем вызывать для него toString и, наконец, регистрировать его)?

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

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


person Cokorda Raka    schedule 23.12.2014    source источник
comment
Вы также должны регистрировать ответ в потоковом режиме. Например. System.out — это OutputStream, вы можете войти туда в потоковом режиме (хотя вы можете получить два чередующихся ответа). Если вы входите в базу данных, вы можете транслировать ее. И так далее.   -  person lmm    schedule 23.12.2014
comment
Я думаю, что нашел ответ здесь: stackoverflow.com/questions/17752151/   -  person Cokorda Raka    schedule 23.12.2014


Ответы (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).

person Cokorda Raka    schedule 24.12.2014

Как мне кажется, если вы ведете журнал внутри result.body &> Enumeratee.map (как это предлагается в https://stackoverflow.com/a/27630208/1781549) и тело результата представлено более чем в одном фрагменте, тогда каждый фрагмент будет регистрироваться независимо. Вы, вероятно, не хотите этого.

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

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 Enumerator перед записью в журнал (и возвращать новый Enumerator). Итак, если ответ большой или вы полагаетесь на потоковую передачу, то, вероятно, это тоже то, что вам не нужно.

person Ivan Yurchenko    schedule 27.04.2016

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

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)
person Kyle Winter    schedule 30.03.2016