根据定义,http响应被分成3个parts,status-code -> headers -> body
,并且当执行akka客户端http请求时,在完全接收到前两个部分之后接收到http响应。
val responseFuture: Future[HttpResponse]
responseFuture.map {
case HttpResponse(statusCode:StatusCode, headers:Seq[HttpHeader], entity:ResponseEntity, protocol:HttpProtocol)
}
对于大多数用例来说这是完全正常的,但在我的特定情况下,我需要在收到所有标头之前访问标头(第三方服务器通过编写自定义进度标头返回进度,直到响应准备就绪)。有没有办法像访问正文一样访问标题?
val entity: ResponseEntity
val entitySource:Source[ByteString, Any] = entity.dataBytes
在完美的世界中,有一种方法可以访问标题作为源
HttpResponse(statusCode:StatusCode, headers:Source[HttpHeader, NotUsed], entity:ResponseEntity, protocol:HttpProtocol)
用akka-http
不可能
representation of HttpResponse
将标题视为Seq[HttpHeader]
而不是Iterator
或akka-stream
Source
。因此,正如问题中所解释的那样,如果没有首先获得所有标头值,则无法实例化HttpResponse对象。
我不知道这个设计决定背后的确切原因,但我怀疑这是因为支持标题的Source和身体的Source很难。如果没有首先使用标头Source,就无法使用正文Source,因此必须严格按顺序访问响应的成员变量。这会导致混淆和意外错误。
使用akka-stream
进行低级处理
hypertext transfer protocol只是一个应用层协议,通常在TCP之上。而且,这是一个相当simple message format:
响应消息包含以下内容:
- 状态行,包括状态代码和原因消息(例如,HTTP / 1.1 200 OK,表示客户端的请求成功)。
- 响应头字段(例如,Content-Type:text / html)。
- 空行。
- 可选的邮件正文。
因此,您可以使用Tcp
绑定来获取连接并自己解析消息ByteString
Source来获取标题:
val maximumFrameLength = 1024 * 1024
val endOfMessageLine : () => Byte => Boolean = () => {
var previousWasCarriage = false
(byte) =>
if(byte == '\r') {
previousWasCarriage = true
false
}
else if(byte == '\n' && previousWasCarriage) {
previousWasCarriage = false
true
}
else {
previousWasCarriage = false
false
}
}
def subFlow =
Flow[ByteString].flatMapConcat(str => Source.fromIterable(str))
.splitAfter(endOfMessageLine())
不幸的是,这可能要求您的请求也通过Tcp绑定作为原始ByteString发送。