Web services 播放:二进制Web服务响应
我必须调用一个Web服务,它为我提供二进制文件的内容。我只想将相同的内容返回给控制器的调用者:Web services 播放:二进制Web服务响应,web-services,scala,playframework-2.0,Web Services,Scala,Playframework 2.0,我必须调用一个Web服务,它为我提供二进制文件的内容。我只想将相同的内容返回给控制器的调用者: val blobPromise = WS.url("http://url/to/webservice/file.txt").get() Async { blobPromise.map(f => Ok(f.body)) } 这适用于文本文件,但二进制文件会损坏。我做错了什么?(可能是f.body将来自Web服务的二进制结果编码为字符串?但如何获取原始数据?) 我知道,对于大文件来说,这不是一
val blobPromise = WS.url("http://url/to/webservice/file.txt").get()
Async {
blobPromise.map(f => Ok(f.body))
}
这适用于文本文件,但二进制文件会损坏。我做错了什么?(可能是f.body
将来自Web服务的二进制结果编码为字符串?但如何获取原始数据?)
我知道,对于大文件来说,这不是一个好方法——我在Play文档中读到过,但对于我这个Play框架的初学者来说,这似乎很复杂。您可以使用
f.ahresponse.gerResponseBodyAsBytes
获取原始数据。但我认为,这将把整个响应加载到内存中,这是低效的
您可以使用播放的流媒体功能!很容易提供如下内容:
Async {
WS.url("http://url/to/webservice/file.txt").get().map(response => {
val asStream: InputStream = response.ahcResponse.getResponseBodyAsStream
Ok.stream(Enumerator.fromStream(asStream))
})
}
如果要流式传输内容,请执行以下操作:
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
}
基于Yann Simon answer,这里有一个简单的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来使用WS响应
- 您需要一个枚举器来生成play framework响应
f.ahresponse.getResponseBodyAsBytes的解决方案现在也可以用于二进制数据。流媒体功能似乎比我想象的要简单…:-)事实上,当你读它的时候,它不会阻塞。。。但这只是因为它是先读入内存的。为了避免这种情况,您必须使用重载形式的get()
,它接受一个consumer函数参数:get[a](consumer:(ResponseHeaders)⇒ Iteratee[Array[Byte],A])
此代码读取内存中的全部内容。如果您需要流式处理,请查看一个示例应用程序:在“处理大型响应”标题下有一个将WS与迭代对象一起使用的示例。请注意,play 2.3附带了新的API stream():使用它,您不再需要加入[]