OkHttp使用gzip時(shí)的坑

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 處理失敗的根本原因了屋讶。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末乐疆,一起剝皮案震驚了整個(gè)濱河市仰美,隨后出現(xiàn)的幾起案子壤圃,更是在濱河造成了極大的恐慌忍疾,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件谨朝,死亡現(xiàn)場離奇詭異卤妒,居然都是意外死亡甥绿,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進(jìn)店門则披,熙熙樓的掌柜王于貴愁眉苦臉地迎上來共缕,“玉大人,你說我怎么就攤上這事士复⊥脊龋” “怎么了?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵阱洪,是天一觀的道長便贵。 經(jīng)常有香客問我,道長冗荸,這世上最難降的妖魔是什么承璃? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮蚌本,結(jié)果婚禮上绸硕,老公的妹妹穿的比我還像新娘。我一直安慰自己魂毁,他們只是感情好玻佩,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著席楚,像睡著了一般咬崔。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上烦秩,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天垮斯,我揣著相機(jī)與錄音,去河邊找鬼只祠。 笑死兜蠕,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的抛寝。 我是一名探鬼主播熊杨,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼盗舰!你這毒婦竟也來了晶府?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤钻趋,失蹤者是張志新(化名)和其女友劉穎川陆,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蛮位,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡较沪,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年鳞绕,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片尸曼。...
    茶點(diǎn)故事閱讀 39,779評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡猾昆,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出骡苞,到底是詐尸還是另有隱情垂蜗,我是刑警寧澤,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布解幽,位于F島的核電站贴见,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏躲株。R本人自食惡果不足惜片部,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望霜定。 院中可真熱鬧档悠,春花似錦、人聲如沸望浩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽磨德。三九已至缘回,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間典挑,已是汗流浹背酥宴。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留您觉,地道東北人拙寡。 一個(gè)月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像琳水,于是被迫代替她去往敵國和親肆糕。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評論 2 354

推薦閱讀更多精彩內(nèi)容