Retrofit 是現(xiàn)在最流行的網(wǎng)絡(luò)開發(fā)框架之一帅容,功能十分強(qiáng)大并徘,但是最近確遇到一個(gè)十分坑的問題饮亏,現(xiàn)在記錄下來路幸,希望看到的人能注意下简肴。
眾所周知砰识,在 HTTP 傳輸時(shí)是支持 gzip 壓縮的辫狼,客戶端發(fā)起請求時(shí)在請求頭里增加 Accept-Encoding: gzip膨处,服務(wù)端響應(yīng)時(shí)在返回的頭信息里增加 Content-Encoding: gzip,這表示傳輸?shù)臄?shù)據(jù)是采用 gzip 壓縮的鹃答。默認(rèn)情況下测摔,傳輸內(nèi)容是不壓縮的锋八,采用 gzip 壓縮后可以大幅減少傳輸內(nèi)容大小挟纱,這樣可以提高傳輸速度,減少流量的使用黄琼。
本來 OkHttp 是默認(rèn)支持 gzip 解壓縮的,不需要額外配置的整慎。但是我在攔截器里統(tǒng)一添加了很多請求頭信息脏款,大概代碼如下:
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);
}
}
以前服務(wù)端沒有開啟 gzip 壓縮,一直都沒有問題裤园,某天突然運(yùn)維加了 gzip 壓縮撤师,說是為了要省流量帶寬,結(jié)果就悲劇了拧揽,我們 Android APP 里所有的接口都報(bào)錯(cuò)了剃盾,明明前一秒都是OK的,后一秒就都不能訪問了淤袜,但是 iOS 里卻能正常訪問痒谴,這是最令人崩潰的事情。
立即進(jìn)行代碼調(diào)試积蔚,發(fā)現(xiàn) Android 里的 http 請求返回的都是亂碼字符串了读慎,其實(shí)這些都是 gzip 壓縮的數(shù)據(jù),不是說 OkHttp 是自動支持 gzip 解壓縮的嗎钻注?為什么我們的返回?cái)?shù)據(jù)沒有進(jìn)行 gzip 解壓泵肄?還有一個(gè)奇怪的現(xiàn)象是,當(dāng)我把這段代碼 addHeader("Accept-Encoding", "gzip") 去掉之后肉瓦,一切又恢復(fù)正常了鲫趁。
這是一個(gè)很費(fèi)解的問題糠惫,當(dāng)我手動加上這個(gè)頭信息時(shí),OkHttp 不會自動解壓 gzip 流,當(dāng)我去掉時(shí) OkHttp 又會自動解壓 gzip 流了,秉著刨根究底的精神我翻看了源碼法瑟,終于找到了原因。原來 OkHttp 在最終構(gòu)建請求信息以及處理返回信息時(shí)脊奋,內(nèi)部使用了一個(gè)叫做 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) {
//設(shè)置頭信息 Content-Length
requestBuilder.header("Content-Length", Long.toString(contentLength));
requestBuilder.removeHeader("Transfer-Encoding");
} else {
//如果傳輸長度為-1,則表示分塊傳輸敬尺,自動設(shè)置頭信息
requestBuilder.header("Transfer-Encoding", "chunked");
requestBuilder.removeHeader("Content-Length");
}
}
if (userRequest.header("Host") == null) {
requestBuilder.header("Host", hostHeader(userRequest.url(), false));
}
//如果沒有設(shè)置頭信息 Connection盯质,則自動設(shè)置為 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,在這里會自動設(shè)置頭信息 Accept-Encoding = gzip
transparentGzip = true;
requestBuilder.header("Accept-Encoding", "gzip");
}
List<Cookie> 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,并且我們沒有手動在請求頭信息里設(shè)置 Accept-Encoding = gzip,則會進(jìn)行 gzip 解壓數(shù)據(jù)流
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();
}
}
上面代碼關(guān)鍵地方我做了注釋臂拓,OkHttp 會額外的增加很多請求頭信息孵滞,如果我們在代碼里沒有手動設(shè)置 Accept-Encoding = gzip 匿级,那么 OkHttp 會自動處理 gzip 的解壓縮;反之散庶,你需要手動對返回的數(shù)據(jù)流進(jìn)行 gzip 解壓縮蕉堰。
以上就是我的代碼里 gzip 處理失敗的根本原因了屋讶。