聊聊HTTP gzip壓縮與常見的Android網絡框架

版權聲明:html

歡迎轉載,但請保留文章原始出處java

做者:GavinCTandroid

出處:http://www.cnblogs.com/ct2011/p/5835990.htmlgit

進入主題以前,咱們先來看一下客戶端與服務器通訊過程當中,若是服務器支持,HTTP gzip壓縮是如何實現的?github

如圖所示:apache

HTTP compression:gzip

request header中聲明Accept-Encoding: gzip,告知服務器客戶端接受gzip的數據。
服務器支持的狀況下,返回gzip後的response body,同時加入如下header:服務器

  • Content-Encoding: gzip:代表body是gzip過的數據
  • Content-Length:117:表示body gzip壓縮後的數據大小,便於客戶端使用。

    Transfer-Encoding: chunked分塊傳輸編碼

OK,HTTP gzip壓縮的基本流程咱們理清楚了,來看在Android各網絡框架中表現有什麼差別。網絡

OkHttp

OkHttp做爲目前Android最火的網絡庫,應用範圍較廣,相比於Android自帶的HttpUrlConnection、Apache坑也少不少。
咱們首先來看這個庫的實現:
(注:如下代碼基於OkHttp 3.4.1, 以前的版本邏輯也是同樣的,但3.4.0開始將這些邏輯抽離到了內置的interceptor中,看起來較爲方便)框架

BridgeInterceptor.javaide

// 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) {
  transparentGzip = true;
  requestBuilder.header("Accept-Encoding", "gzip");
}

若是header中沒有Accept-Encoding,默認自動添加 ,且標記變量transparentGziptrue

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);
  responseBuilder.body(new RealResponseBody(strippedHeaders, Okio.buffer(responseBody)));
}

針對返回結果,若是同時知足如下三個條件:

  • transparentGziptrue,即以前自動添加了Accept-Encoding
  • header中標明瞭Content-Encoding爲gzip
  • 有body

移除 Content-EncodingContent-Length,並對結果進行解壓縮。

能夠看到以上邏輯完成了:

  1. 開發者沒有添加Accept-Encoding時,自動添加Accept-Encoding: gzip
  2. 自動添加的request,response支持自動解壓
  3. 手動添加不負責解壓縮
  4. 自動解壓時移除Content-Length,因此上層Java代碼想要contentLength時爲-1
  5. 自動解壓時移除 Content-Encoding
  6. 自動解壓時,若是是分塊傳輸編碼,Transfer-Encoding: chunked不受影響。

以上6點是咱們經過OkHttp源碼得出的結論,咱們以此來繼續看下其餘框架。

HttpUrlConnection

1. 是否自動添加Accept-Encoding: gzip

官網有過說明:

In Gingerbread, we added transparent response compression. HttpURLConnection will automatically add this header to outgoing requests, and handle the corresponding response:
Accept-Encoding: gzip
Take advantage of this by configuring your Web server to compress responses for clients that can support it. If response compression is problematic, the class documentation shows how to disable it.

即:2.3後默認是gzip,不加Accept-Encoding會被自動添加上Accept-Encoding: gzip

2. 自動添加的request,response是否支持自動解壓

By default, this implementation of HttpURLConnection requests that servers use gzip compression and it automatically decompresses the data for callers of getInputStream().

即返回的數據是已經自動解壓縮的。

3. 手動添加是否負責解壓縮

By default, this implementation of HttpURLConnection requests that servers use gzip compression and it automatically decompresses the data for callers of getInputStream(). The Content-Encoding and Content-Length response headers are cleared in this case. Gzip compression can be disabled by setting the acceptable encodings in the request header:
urlConnection.setRequestProperty("Accept-Encoding", "identity");
Setting the Accept-Encoding request header explicitly disables automatic decompression and leaves the response headers intact; callers must handle decompression as needed, according to the Content-Encoding header of the response.

例子中只提到設置爲identity時能夠禁止gzip壓縮。
可是請注意最後一段提到,顯式聲明會禁止自動解壓,同時保留header完整性,須要根據Content-Encoding來本身處理response。

實測4.1 - 6.0 版本以後發現,並非非要指定identity才能屏蔽,指定gzip同樣也不會解壓縮。so,只要是顯式聲明過,都不會再處理,即:手動添加不會負責解壓縮。

4. 自動解壓時Content-Length問題

Since HTTP’s Content-Length header returns the compressed size, it is an error to use getContentLength() to size buffers for the uncompressed data. Instead, read bytes from the response until InputStream.read() returns -1.

即:getContentLength() 值爲gzip壓縮時的數據大小。

以前提到OkHttp在處理gzip壓縮時會把Content-Length移除,contentLength在Java層獲取爲-1,而HttpURLConnection 在Android 4.4之後底層是由OkHttp實現的,那文檔中提到的getContentLength()是compressed size是否還繼續成立呢?

實測後發現 :

  • 4.4以後的版本,Content-Length被移除,getContentLength() = -1
  • 2.3- 4.3之間,Content-Length 沒有移除,getContentLength() = compressed size

5. 自動解壓時的Content-Encoding

與Content-Length對應:

  • 4.4以後的版本,Content-Encoding被移除
  • 2.3- 4.3之間,Content-Encoding存在,無變化。

6. 自動解壓時的分塊編碼傳輸

與OkHttp相同,Transfer-Encoding: chunked不受影響。

Apache

這裏再也不贅述,僅闡述結論:
無自動添加、解壓機制。

總結

一、是否支持自動添加Accept-Encoding與數據自動解壓?

name transparent response compression
OkHttp yes
HttpUrlConnection yes
Apache no

二、支持自動後,response header的表現如何?

name Content-Encoding: gzip Header : Content-Length Java : ContentLength
OkHttp 被移除 被移除 -1
HttpUrlConnection(2.3 ~ 4.3) 不變 不變 compressed size
HttpUrlConnection(4.4 ~ ?) 被移除 被移除 -1

name Content-Encoding: gzip Transfer-Encoding: chunked
OkHttp 被移除 不變
HttpUrlConnection(2.3 ~ 4.3) 不變 不變
HttpUrlConnection(4.4 ~ ?) 被移除 不變

三、自動模式啓動後,在Java中獲取contentLength不管是哪一個版本的HttpUrlConnection仍是OkHttp都是不可信的,都不是解壓縮以後的值(可能爲-1或compressed size),所以最好不要經過contentLength來作什麼操做

四、HttpUrlConnection、OkHttp均是手動添加不自動解壓縮,Apache沒有自動添加自動解壓功能。三者在手動添加Accept-Encoding後,表現一致(利用這個特色,能夠作一個在三者之上的網絡框架,隨意切換三種通道)。

參考資料

相關文章
相關標籤/搜索