现象

最近使用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