Play Framework Scala:如何流请求正文

时间:2016-07-08 15:02:42

标签: scala playframework streaming

我使用Scala构建了一个使用Play Framework 2.3.x的微服务(我在这两者中都是初学者),但我无法找到一种流式传输请求体的方法。

问题在于:

我需要一个端点/transform,我可以在其中收到一个巨大的TSV文件,我将以另一种格式解析和渲染:简单转换。问题是我的控制器中的每个命令都运行得太晚了#34;它等待在启动代码之前接收完整文件。

示例:

  def transform = Action.async {
    Future {
      Logger.info("Too late")
      Ok("A response")
    }
  }

我希望能够在上传过程中逐行读取请求正文并处理请求,而不必等待文件被完全接收。

欢迎任何提示。

1 个答案:

答案 0 :(得分:11)

此答案适用于Play 2.5.x及更高版本,因为它使用的Akka流API取代了该版本中Play的基于Iteratee的流式传输。

基本上,您可以创建一个返回Source[T]的正文解析器,您可以将其传递给Ok.chunked(...)。一种方法是在body解析器中使用Accumulator.source[T]。例如,刚刚返回发送给它的数据的操作可能如下所示:

def verbatimBodyParser: BodyParser[Source[ByteString, _]] = BodyParser { _ =>
  // Return the source directly. We need to return
  // an Accumulator[Either[Result, T]], so if we were
  // handling any errors we could map to something like
  // a Left(BadRequest("error")). Since we're not
  // we just wrap the source in a Right(...)
  Accumulator.source[ByteString]
    .map(Right.apply)
}

def stream = Action(verbatimBodyParser) { implicit request =>
  Ok.chunked(request.body)
}

如果你想做一些转换TSV文件的事情,你可以使用Flow转换来源,例如:

val tsvToCsv: BodyParser[Source[ByteString, _]] = BodyParser { req =>

  val transformFlow: Flow[ByteString, ByteString, NotUsed] = Flow[ByteString]
    // Chunk incoming bytes by newlines, truncating them if the lines
    // are longer than 1000 bytes...
    .via(Framing.delimiter(ByteString("\n"), 1000, allowTruncation = true))
    // Replace tabs by commas. This is just a silly example and
    // you could obviously do something more clever here...
    .map(s => ByteString(s.utf8String.split('\t').mkString(",") + "\n"))

  Accumulator.source[ByteString]
    .map(_.via(transformFlow))
    .map(Right.apply)
}

def convert = Action(tsvToCsv) { implicit request =>
  Ok.chunked(request.body).as("text/csv")
}

Play文档的Directing the Body Elsewhere部分可能会有更多灵感。