在说问题前,先确认一个http响应头的一个字段Content-Length的解释。
对于http的请求返回结果要进行内容的长度校验主要有两种方式,二者互斥使用
- 客户端在http头(head)加Connection:keep-alive时,服务器的response是Transfer-Encoding:chunked的形式,通知页面数据是否接收完毕,例如长连接或者程序运行中可以动态的输出内容,例如一些运算比较复杂且需要用户及时的得到最新结果,那就采用chunked编码将内容分块输出。
- 除了如1所述之外的情况一般都是可以获取到Content-Length的。在HTTP协议中,Content-Length用于描述HTTP消息实体的传输长度the transfer-length of the message-body。在HTTP协议中,消息实体长度和消息实体的传输长度是有区别,比如说gzip压缩下,消息实体长度是压缩前的长度,消息实体的传输长度是gzip压缩后的长度。
OkHttp处理Gzip流
一般下载过程中需要用到Content-Length来做断点续传以及长度校验。但是OkHttp在用户请求中未带Content-Encoding会默认加上gzip的方式,同时在返回流解压完后,会剔除掉这个字段,导致Content-Length获取不到,同时因为后面取到的流已经是解压后的流了,所以这个其实也没啥意义了。
可以参考下BridgeInterceptor的源码
// If we add an "Accept-Encoding: gzip" header field we're responsible for also decompressing
// the transfer stream.
var transparentGzip = false
if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
transparentGzip = true
requestBuilder.header("Accept-Encoding", "gzip")
}
val networkResponse = chain.proceed(requestBuilder.build())
HttpHeaders.receiveH
eaders(cookieJar, userRequest.url(), networkResponse.headers())
val responseBuilder = networkResponse.newBuilder()
.request(userRequest)
if (transparentGzip &&
"gzip".equals(networkResponse.header("Content-Encoding"), ignoreCase = true) &&
HttpHeaders.hasBody(networkResponse)) {
val responseBody = networkResponse.body()
if (responseBody != null) {
val gzipSource = GzipSource(responseBody.source())
val strippedHeaders = networkResponse.headers().newBuilder()
.removeAll("Content-Encoding")
.removeAll("Content-Length")
.build()
responseBuilder.headers(strippedHeaders)
val contentType = networkResponse.header("Content-Type")
responseBuilder.body(RealResponseBody(contentType, -1L, gzipSource.buffer()))
}
}
return responseBuilder.build()
}
不得不说okhttp在此处的处理略显粗暴,但也不是完全不能理解。
所以如果想使用OkHttp作为下载框架,必须只能自己对请求添加Content-Encoding,然后自己处理Gzip流。