播放:二进制Web服务响应

时间:2012-10-20 20:25:14

标签: web-services scala playframework-2.0

我必须调用一个webservice来提供二进制文件的内容。我只想将相同的内容发回给我的控制器的调用者:

val blobPromise = WS.url("http://url/to/webservice/file.txt").get()
Async {
  blobPromise.map(f => Ok(f.body))
}

这适用于文本文件,但二进制文件将被破坏。我在这做错了什么? (也许是f.body将Web服务的二进制结果编码为String?但是如何获取原始数据呢?)

我知道,对于大文件来说这不是一个好方法 - 我已经在Play文档中阅读了Streaming HTTP responses,但对于我来说,作为Play框架的初学者,这似乎很复杂。

3 个答案:

答案 0 :(得分:8)

您可以使用f.ahcResponse.gerResponseBodyAsBytes获取原始数据。但我认为,这会将整个响应加载到内存中,这是低效的。

您可以使用播放的流媒体功能!提供很容易这样:

Async {
  WS.url("http://url/to/webservice/file.txt").get().map(response => {
    val asStream: InputStream = response.ahcResponse.getResponseBodyAsStream
    Ok.stream(Enumerator.fromStream(asStream))
  })
}

答案 1 :(得分:3)

如果您想要直播内容:

def streamFromWS = Action.async { request =>
  import play.api.libs.iteratee.Concurrent.joined

  val resultPromise = Promise[SimpleResult]

  val consumer = { rs: ResponseHeaders =>
    val (wsConsumer, stream) = joined[Array[Byte]]
    val contentLength = rs.headers.get("Content-Length").map(_.head).get
    val contentType = rs.headers.get("Content-Type").map(_.head).getOrElse("binary/octet-stream")
    resultPromise.success(
      SimpleResult(
        header = ResponseHeader(
          status = OK,
          headers = Map(
            CONTENT_LENGTH -> contentLength,
            CONTENT_DISPOSITION -> s"""attachment; filename="file.txt"""",
            CONTENT_TYPE -> contentType
          )),
        body = stream
      ))
    wsConsumer
  }

  WS.url("http://url/to/webservice/file.txt").get(consumer).map(_.run)

  resultPromise.future
}

答案 2 :(得分:1)

基于Yann Simon的回答,这是一个简单的CORS代理实现,它允许流式传输下载的远程文件并将它们流式传输到客户端。 它不会将所有文件加载到内存中。

  import play.api.libs.iteratee._

  private def getAndForwardStream(requestHolder: WSRequestHolder)(computeHeaders: ResponseHeaders => ResponseHeader): Future[SimpleResult] = {
    val resultPromise = scala.concurrent.Promise[SimpleResult]
    requestHolder.get { wsResponseHeaders: ResponseHeaders =>
      val (wsResponseIteratee, wsResponseEnumerator) = Concurrent.joined[Array[Byte]]
      val result = SimpleResult(
        header = computeHeaders(wsResponseHeaders),
        body = wsResponseEnumerator
      )
      resultPromise.success(result)
      wsResponseIteratee
    }
    resultPromise.future
  }

  def corsProxy(url: URL) = Action.async { implicit request =>
    val requestHolder = WS.url(url.toString).withRequestTimeout(10000)
    getAndForwardStream(requestHolder) { wsResponseHeaders: ResponseHeaders =>
      // We use the WS response headers and transmit them unchanged to the client, except we add the CORS header...
      val originToAllow = request.headers.get("Origin").getOrElse("*")
      val headers = wsResponseHeaders.headers.mapValues(_.head) + ("Access-Control-Allow-Origin" -> originToAllow)
      ResponseHeader(
        status = wsResponseHeaders.status,
        headers = headers
      )
    }
  }

这里的重要部分是使用play.api.libs.iteratee.Concurrent.joined[Array[Byte]]。 它允许创建一个Iteratee / Enumerator对,这样无论何时向Iteratee添加字节,这些字节都将是枚举器的枚举器。

这是缺失的部分,因为:

  • 您需要一个Iteratee来使用WS响应。
  • 您需要一个Enumerator来生成播放框架响应。