天天看點

HttpUrlConnection之gzip相關

進入主題之前,我們先來看一下用戶端與伺服器通信過程中,如果伺服器支援,HTTP gzip壓縮是如何實作的?

如圖所示:

HttpUrlConnection之gzip相關

​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.java

// 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");
}      

​Accept-Encoding​

​,預設自動添加 ,且标記變量​

​transparentGzip​

​為​

​true​

​。

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

針對傳回結果,如果同時滿足以下三個條件:

​transparentGzip​

​true​

  • ,即之前自動添加了

​Accept-Encoding​

  • header中标明了

​Content-Encoding​

  • 為gzip
  • 有body

​Content-Encoding​

​、​

​Content-Length​

​,并對結果進行解壓縮。

可以看到以上邏輯完成了:

  1. 開發者沒有添加

​Accept-Encoding​

  1. 時,自動添加

​Accept-Encoding: gzip​

  1. 自動添加的request,response支援自動解壓
  2. 手動添加不負責解壓縮
  3. 自動解壓時移除

​Content-Length​

  1. ,是以上層Java代碼想要contentLength時為-1
  2. 自動解壓時移除

​Content-Encoding​

  1. 自動解壓時,如果是分塊傳輸編碼,

​Transfer-Encoding: chunked​

  1. 不受影響。

以上6點是我們通過OkHttp源碼得出的結論,我們以此來繼續看下其他架構。

HttpUrlConnection

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.

​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()​

​​

​Content-Length​

​移除,contentLength在Java層擷取為-1,而HttpURLConnection 在Android 4.4以後底層是由OkHttp實作的,那文檔中提到的​

​getContentLength()​

​是compressed size是否還繼續成立呢?

實測後發現 :

  • 4.4之後的版本,

​Content-Length​

  • 被移除,

​getContentLength()​

  • 2.3- 4.3之間,

​Content-Length​

  • 沒有移除,

​getContentLength()​

5. 自動解壓時的Content-Encoding

與Content-Length對應:

  • 4.4之後的版本,

​Content-Encoding​

  • 被移除
  • 2.3- 4.3之間,

​Content-Encoding​

  • 存在,無變化。

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

​Transfer-Encoding: chunked​

​不受影響。

Apache

這裡不再贅述,僅闡述結論:

無自動添加、解壓機制。

總結

​Accept-Encoding​

​與資料自動解壓?

name transparent response compression
OkHttp yes
HttpUrlConnection yes
Apache no

2、支援自動後,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 ~ ?) 被移除

參考資料

  • ​​Android’s HTTP Clients​​
  • ​​HttpURLConnection​​
  • ​​HTTP 協定中的 Transfer-Encoding​​