现象
最近使用OkHttp替换原Http请求方案,但是发现原先可用的第三方接口请求的出现相同的请求结构,请求内容不同的时候,返回可能为空的情况。
第三方接口分析
在第三方平台上测试接口,发现系统中有异常的接口,在平台上是正常的,进而对平台上的接口响应进行分析,比较后发现Response Headers中存在如下差异:
系统中获取到结果的Response Headers:
Content-Length: 390
Content-Type: application/json; charset=utf-8
系统中获取结果为空的Response Headers:
Content-Encoding: gzip
Content-Type: application/json; charset=utf-8
再对比返回的body,获取结果为空的请求的消息内容会更长些,考虑是第三方接口对于返回内容长的数据进行了gzip处理。
分析和解决
查看源码后发现,OkHttp在最终构建请求信息以及处理返回信息时,内部使用了一个叫做 BridgeInterceptor的拦截器okhttp3.internal.http.BridgeInterceptor#intercept
if (transparentGzip && "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding")) && HttpHeaders.hasBody(networkResponse)) {
GzipSource responseBody = new GzipSource(networkResponse.body().source());
Headers strippedHeaders = networkResponse.headers().newBuilder()
.removeAll("Content-Encoding")
.removeAll("Content-Length")
.build();
responseBuilder.headers(strippedHeaders);
String contentType = networkResponse.header("Content-Type");
responseBuilder.body(new RealResponseBody(contentType, -1L, Okio.buffer(responseBody)));
}
意味着如果返回的头信息里Content-Encoding = gzip,并且我们没有手动在请求头信息里设置 Accept-Encoding = gzip,则会进行 gzip 解压数据流;反之,我们需要手动对返回的数据流进行 gzip 解压缩。
再查看处理返回的代码
if(responseBody.contentLength() > 0) {
try {
result = responseBody.string();
} catch (IOException e) {
log.error("获取请求返回异常,{}", e.getMessage());
}
} else {
log.error("获取请求返回异常");
}
可以看到只认为responseBody中contentLength大于0的数据才是返回正常的,但是看okhttp的源码,存在gzip自动解压流数据的情况,返回的contentLength将会是-1
(responseBuilder.body(new RealResponseBody(contentType, -1L, Okio.buffer(responseBody)));)
因此这里要修改为
try {
result = responseBody.string();
} catch (IOException e) {
log.error("获取请求返回异常,{}", e.getMessage());
}
gzip
上面讲到了gzip,顺带了解一下使用gzip。
gzip是一种数据压缩格式方式。内容编码目的是优化传输内容大小,通俗地讲就是进行压缩。一般经过 gzip压缩过的文本响应,只有原始大小的1/4。对于文本类响应是否开启了内容压缩,是我们做性能优化时首先要检查的重要项目。
对于Content-Encoding中的其他知识可参考
HTTP 协议中的 Content-Encoding
参考
OkHttp使用gzip时的坑HTTP 协议中的 Content-Encoding