android okhttp3 gzip,OkHttp使用gzip时的坑

Retrofit 是现在最流行的网络开发框架之一,功能十分强大,但是最近确遇到一个十分坑的问题,现在记录下来,希望看到的人能注意下。

众所周知,在 HTTP 传输时是支持 gzip 压缩的,客户端发起请求时在请求头里增加 Accept-Encoding: gzip,服务端响应时在返回的头信息里增加 Content-Encoding: gzip,这表示传输的数据是采用 gzip 压缩的。默认情况下,传输内容是不压缩的,采用 gzip 压缩后可以大幅减少传输内容大小,这样可以提高传输速度,减少流量的使用。

本来 OkHttp 是默认支持 gzip 解压缩的,不需要额外配置的。但是我在拦截器里统一添加了很多请求头信息,大概代码如下:

public class RequestInterceptor implements Interceptor {

public RequestInterceptor() {

}

@Override

public Response intercept(Chain chain) throws IOException {

Request.Builder builder = chain.request()

.newBuilder()

.addHeader("Accept", "application/json")

.addHeader("Accept-Encoding", "gzip");

Request request = builder.build();

return chain.proceed(request);

}

}

以前服务端没有开启 gzip 压缩,一直都没有问题,某天突然运维加了 gzip 压缩,说是为了要省流量带宽,结果就悲剧了,我们 Android APP 里所有的接口都报错了,明明前一秒都是OK的,后一秒就都不能访问了,但是 iOS 里却能正常访问,这是最令人崩溃的事情。

立即进行代码调试,发现 Android 里的 http 请求返回的都是乱码字符串了,其实这些都是 gzip 压缩的数据,不是说 OkHttp 是自动支持 gzip 解压缩的吗?为什么我们的返回数据没有进行 gzip 解压?还有一个奇怪的现象是,当我把这段代码 addHeader("Accept-Encoding", "gzip") 去掉之后,一切又恢复正常了。

这是一个很费解的问题,当我手动加上这个头信息时,OkHttp 不会自动解压 gzip 流,当我去掉时 OkHttp 又会自动解压 gzip 流了,秉着刨根究底的精神我翻看了源码,终于找到了原因。原来 OkHttp 在最终构建请求信息以及处理返回信息时,内部使用了一个叫做 BridgeInterceptor 的拦截器,该类的代码如下:

public final class BridgeInterceptor implements Interceptor {

private final CookieJar cookieJar;

public BridgeInterceptor(CookieJar cookieJar) {

this.cookieJar = cookieJar;

}

@Override public Response intercept(Chain chain) throws IOException {

Request userRequest = chain.request();

Request.Builder requestBuilder = userRequest.newBuilder();

RequestBody body = userRequest.body();

if (body != null) {

MediaType contentType = body.contentType();

//自动增添加请求头 Content-Type

if (contentType != null) {

requestBuilder.header("Content-Type", contentType.toString());

}

long contentLength = body.contentLength();

//如果传输长度不为-1,则表示完整传输

if (contentLength != -1) {

//设置头信息 Content-Length

requestBuilder.header("Content-Length", Long.toString(contentLength));

requestBuilder.removeHeader("Transfer-Encoding");

} else {

//如果传输长度为-1,则表示分块传输,自动设置头信息

requestBuilder.header("Transfer-Encoding", "chunked");

requestBuilder.removeHeader("Content-Length");

}

}

if (userRequest.header("Host") == null) {

requestBuilder.header("Host", hostHeader(userRequest.url(), false));

}

//如果没有设置头信息 Connection,则自动设置为 Keep-Alive

if (userRequest.header("Connection") == null) {

requestBuilder.header("Connection", "Keep-Alive");

}

// If we add an "Accept-Encoding: gzip" header field we're responsible for also decompressing

// the transfer stream.

boolean transparentGzip = false;

if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {

//如果我们没有在请求头信息里增加Accept-Encoding,在这里会自动设置头信息 Accept-Encoding = gzip

transparentGzip = true;

requestBuilder.header("Accept-Encoding", "gzip");

}

List cookies = cookieJar.loadForRequest(userRequest.url());

if (!cookies.isEmpty()) {

requestBuilder.header("Cookie", cookieHeader(cookies));

}

if (userRequest.header("User-Agent") == null) {

requestBuilder.header("User-Agent", Version.userAgent());

}

Response networkResponse = chain.proceed(requestBuilder.build());

HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());

Response.Builder responseBuilder = networkResponse.newBuilder()

.request(userRequest);

//如果返回的头信息里Content-Encoding = gzip,并且我们没有手动在请求头信息里设置 Accept-Encoding = gzip,则会进行 gzip 解压数据流

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)));

}

return responseBuilder.build();

}

}

上面代码关键地方我做了注释,OkHttp 会额外的增加很多请求头信息,如果我们在代码里没有手动设置 Accept-Encoding = gzip ,那么 OkHttp 会自动处理 gzip 的解压缩;反之,你需要手动对返回的数据流进行 gzip 解压缩。

以上就是我的代码里 gzip 处理失败的根本原因了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值