httpclient Accept-Encoding 亂碼

來源:互聯網
上載者:User

標籤:

解決方案
 1 HttpEntity httpEntity = httpResponse.getEntity(); 2             if (httpEntity != null) { 3                 if (httpEntity.getContentEncoding() != null) { 4                     if ("gzip".equalsIgnoreCase(httpEntity.getContentEncoding().getValue())) { 5                         httpEntity = new GzipDecompressingEntity(httpEntity); 6                     } else if ("deflate".equalsIgnoreCase(httpEntity.getContentEncoding().getValue())) { 7                         httpEntity = new DeflateDecompressingEntity(httpEntity); 8                     } 9                 }10                 htmlByte = EntityUtils.toByteArray(httpEntity);11             }

 

來自:https://www.imququ.com/post/vary-header-in-http.html

 

經常抓包看 HTTP 要求的同學應該對 Vary 這個回應標頭欄位並不陌生,它有什麼用?用 PageSpeed 工具檢查頁面時,經常看到「Specify a Vary: Accept-Encoding header(請指定一個 Vary: Accept-Encoding 標題)」這樣的建議,為什麼要這樣做?本文記錄我對 Vary 的一些研究,其中就包含這些問題的答案。

HTTP 內容協商

要瞭解 Vary 的作用,先得瞭解 HTTP 的內容協商機制。有時候,同一個 URL 可以提供多份不同的文檔,這就要求服務端和用戶端之間有一個選擇最合適版本的機制,這就是內容協商。

協商方式有兩種,一種是服務端把文檔可用版本列表發給用戶端讓使用者選,這可以使用 300 Multiple Choices 狀態代碼來實現。這種方案有不少問題,首先多一次網路往返;其次服務端同一文檔的某些版本可能是為擁有某些技術特徵的用戶端準備的,而普通使用者不一定瞭解這些細節。舉個例子,服務端通常可以將靜態資源輸出為壓縮和未壓縮兩個版本,壓縮版顯然是為支援壓縮的用戶端而準備的,但如果讓普通使用者選,很可能選擇錯誤的版本。

所以 HTTP 的內容協商通常使用另外一種方案:服務端根據用戶端發送的要求標頭中某些欄位自動發送最合適的版本。可以用於這個機制的要求標頭欄位又分兩種:內容協商專用欄位(Accept 欄位)、其他欄位。

首先來看 Accept 欄位,詳見下表:

要求標頭欄位 說明 回應標頭欄位
Accept 告知伺服器發送何種媒體類型 Content-Type
Accept-Language 告知伺服器發送何種語言 Content-Language
Accept-Charset 告知伺服器發送何種字元集 Content-Type
Accept-Encoding 告知伺服器採用何種壓縮方式 Content-Encoding

例如用戶端發送以下要求標頭:

 
Accept:*/*Accept-Encoding:gzip,deflate,sdchAccept-Language:zh-CN,en-US;q=0.8,en;q=0.6

表示它可以接受任何 MIME 類型的資源;支援採用 gzip、deflate 或 sdch 壓縮過的資源;可以接受 zh-CN、en-US 和 en 三種語言,並且 zh-CN 的權重最高(q 取值 0 - 1,最高為 1,最低為 0,預設為 1),服務端應該優先返回語言等於 zh-CN 的版本。

瀏覽器的回應標頭可能是這樣的:

 
Content-Type: text/javascriptContent-Encoding: gzip

表示這個文檔確切的 MIME 類型是 text/javascript;文檔內容進行了 gzip 壓縮;回應標頭沒有 Content-Language 欄位,通常說明返回版本的語言正好是要求標頭 Accept-Language 中權重最高的那個。

有時候,上面四個 Accept 欄位並不夠用,例如要針對特定瀏覽器如 IE6 輸出不一樣的內容,就需要用到要求標頭中的 User-Agent 欄位。類似的,要求標頭中的 Cookie 也可能被服務端用做輸出差異化內容的依據。

由於用戶端和服務端之間可能存在一個或多個中間實體(如快取服務器),而快取服務最基本的要求是給使用者返回正確的文檔。如果服務端根據不同 User-Agent 返回不同內容,而快取服務器把 IE6 使用者的響應緩衝下來,並返回給使用其他瀏覽器的使用者,肯定會出問題 。

所以 HTTP 協議規定,如果服務端提供的內容取決於 User-Agent 這樣「常規 Accept 協商欄位之外」的要求標頭欄位,那麼回應標頭中必須包含 Vary 欄位,且 Vary 的內容必須包含 User-Agent。同理,如果服務端同時使用要求標頭中 User-Agent 和 Cookie 這兩個欄位來產生內容,那麼響應中的 Vary 欄位看上去應該是這樣的:

 
Vary: User-Agent, Cookie

也就是說 Vary 欄位用於列出一個響應欄位列表,告訴快取服務器遇到同一個 URL 對應著不同版本文檔的情況時,如何緩衝和篩選合適的版本。

有 BUG 的快取服務

再來看 PageSpeed 的「Specify a Vary: Accept-Encoding header」這個提示,按照上面的說明,Accept-Encoding 屬於內容協商專用欄位,服務端只需要在回應標頭中增加 Content-Encoding 欄位,用來指明內容壓縮格式;或者不輸出 Content-Encoding 表明內容未經過壓縮就可以了。而快取服務器,應該針對不同的 Content-Encoding 緩衝不同內容,再根據具體請求中的 Accept-Encoding 欄位返回最合適的版本。

但是有些實現得有 BUG 的快取服務器,會忽略回應標頭中的 Content-Encoding,從而可能給不支援壓縮的用戶端返回緩衝的壓縮版本。有兩個方案可以避免這種情況發生:

  1. 將回應標頭中的 Cache-Control 欄位設為 private,告訴中間實體不要緩衝它;
  2. 增加 Vary: Accept-Encoding 回應標頭,明確告知快取服務器按照 Accept-Encoding 欄位的內容,分別緩衝不同的版本;

通常為了更好的利用中間實體的緩衝功能,我們都用第二種方案。

對於 css、js 這樣的靜態資源,只要用戶端支援 gzip,服務端應該總是啟用它;同時為了避免有 BUG 的快取服務器給使用者返回錯誤的版本,還應該輸出 Vary: Accept-Encoding。

Nginx 和 SPDY

通常,上面說的這些工作,Web Server 都可以幫我們搞定。對於 Nginx 來說,下面這個配置可以自動給啟用了 gzip 的響應加上 Vary: Accept-Encoding:

 
gzip_vary on;

用 curl 驗證我部落格的 js 檔案,回應標頭如下:

 
[email protected]:~$ curl --head https://www.imququ.com/.../xx.js HTTP/1.1 200 OKServer: nginxDate: Tue, 31 Dec 2013 16:34:48 GMTContent-Type: application/x-javascriptContent-Length: 66748Last-Modified: Tue, 31 Dec 2013 14:30:52 GMTConnection: keep-aliveVary: Accept-EncodingETag: "52c2d51c-104bc"Expires: Fri, 29 Dec 2023 16:34:48 GMTCache-Control: max-age=315360000Strict-Transport-Security: max-age=31536000Accept-Ranges: bytes

可以看到,服務端正確輸出了「Vary: Accept-Encoding」,一切正常。

但是用 Chrome 內建抓包工具看下,這個回應標頭卻是這樣:

 
HTTP/1.1 200 OKcache-control: max-age=315360000content-encoding: gzipcontent-type: application/x-javascriptdate: Tue, 31 Dec 2013 16:35:27 GMTexpires: Fri, 29 Dec 2023 16:35:27 GMTlast-modified: Tue, 31 Dec 2013 14:30:52 GMTserver: nginxstatus: 200strict-transport-security: max-age=31536000version: HTTP/1.1

我的部落格支援 SPDY/2 協議,用 Chrome 訪問我部落格會走 SPDY,所以上面的回應標頭看上有點不同尋常,例如欄位名都變成了小寫;多了 status、version 等欄位,這些變化下次專門介紹(註:見「SPDY 3.1 中的請求 / 回應標頭」)。神奇的是儘管服務端沒任何變化,但響應中的 Vary: Accept-Encoding 卻不見了。

SPDY 規定用戶端必須支援壓縮,這意味著 SPDY 伺服器可以直接啟用壓縮而不用關心要求標頭中的 Accept-Encoding 欄位。下面這段來自 Nginx 支援的 SPDY/2 協議:

User-agents are expected to support gzip and deflate compression. Regardless of the Accept-Encoding sent by the user-agent, the server may select gzip or deflate encoding at any time. [via]

於是,對於支援 SPDY 的用戶端來說,Vary: Accept-Encoding 沒有用途,Nginx 選擇直接去掉它,可以節省一點流量。curl 或其他不支援 SPDY 協議的用戶端還是走 HTTP 協議,所以看到的回應標頭是常規的。

Nginx 的這個做法是否合適一直有爭論,實際上並不是所有支援 SPDY 的 Web Server 都會這麼做。例如即使通過 SPDY 協議訪問 Google 首頁的 js 檔案,依然可以看到 vary: Accept-Encoding:

 
HTTP/1.1 200 OKstatus: 200 OKversion: HTTP/1.1age: 25762alternate-protocol: 443:quiccache-control: public, max-age=31536000content-encoding: gzipcontent-length: 154614content-type: text/javascript; charset=UTF-8date: Tue, 31 Dec 2013 23:23:51 GMTexpires: Wed, 31 Dec 2014 23:23:51 GMTlast-modified: Mon, 16 Dec 2013 21:54:35 GMTserver: sffevary: Accept-Encodingx-content-type-options: nosniffx-xss-protection: 1; mode=block

另外,現階段 Chrome 和 Firefox 都支援 SPDY 協議,但 PageSpeed Chrome 版和 Firefox 版都沒有針對 SPDY 協議做特別處理,所以用它們測試我的部落格,還是會提示「Specify a Vary: Accept-Encoding header」,這有點讓人哭笑不得。不過PageSpeed 線上版 已經更新規則,估計擴充版也快了。

PS:Vary 在 IE 下有很多坑,使用時要格外小心。網上這部分文章比較多,例如 hax 早年寫的 IE 與 Vary 頭,可以點過去瞭解下。

httpclient Accept-Encoding 亂碼

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.