Accept-Encoding 和 Content-Encoding 是 HTTP 中用來對「採用何種編碼格式傳輸正文」進行協定的一對頭部欄位。它的工作原理是這樣:瀏覽器傳送請求時,通過 Accept-Encoding 帶上自己支援的內容編碼格式列表;服務端從中挑選一種用來對正文進行編碼,並通過 Content-Encoding 響應頭指明選定的格式;瀏覽器拿到響應正文後,依據 Content-Encoding 進行解壓。當然,服務端也可以返回未壓縮的正文,但這種情況不允許返回 Content-Encoding。這個過程就是 HTTP 的內容編碼機制。
內容編碼目的是優化傳輸內容大小,通俗地講就是進行壓縮。一般經過 gzip 壓縮過的文字響應,只有原始大小的 1/4。對於文字類響應是否開啟了內容壓縮,是我們做效能優化時首先要檢查的重要專案;而對於 JPG / PNG 這類本身已經高度壓縮過的二進位制檔案,不推薦開啟內容壓縮,效果微乎其微還浪費 CPU。
內容編碼針對的只是傳輸正文。在 HTTP/1 中,頭部始終是以 ASCII 文字傳輸,沒有經過任何壓縮。這個問題在 HTTP/2 中得以解決,詳見:HTTP/2 頭部壓縮技術介紹。
內容編碼使用特別廣泛,理解起來也很簡單,隨手開啟一個網頁抓包看下請求響應就能明白。唯一要注意的是不要把它與 HTTP 中的另外一個概念:傳輸編碼(Transfer-Encoding)搞混即可。
有關 HTTP 內容編碼機制我打算只介紹這麼多,下面重點介紹兩種具體的內容編碼格式:gzip 和 deflate,具體會涉及到兩個問題:1)gzip 和 deflate 分別是什麼編碼?2)為什麼很少見到 Content-Encoding: deflate?
開始之前,先來介紹三種資料壓縮格式:
- DEFLATE,是一種使用 Lempel-Ziv 壓縮演算法(LZ77)和哈夫曼編碼的資料壓縮格式,定義於 RFC 1951 : DEFLATE Compressed Data Format Specification;
- ZLIB,是一種使用 DEFLATE 的資料壓縮格式,定義於 RFC 1950 : ZLIB Compressed Data Format Specification;
- GZIP,是一種使用 DEFLATE 的檔案格式,定義於 RFC 1952 : GZIP file format specification;
這三個名詞有太多的含義,很容易讓人暈菜。所以本文有如下約定:
- DEFLATE、ZLIB、GZIP 這種大寫字元,表示資料壓縮格式;
- deflate、gzip 這種小寫字元,表示 HTTP 中 Content-Encoding 的取值;
- Gzip 特指 GUN zip 檔案壓縮程式,Zlib 特指 Zlib 庫;
在 HTTP/1.1 的初始規範 RFC 2616 的「3.5 Content Codings」這一節中,這樣定義了 Content-Encoding 中的 gzip 和 deflate:
- gzip,一種由檔案壓縮程式「Gzip,GUN zip」產生的編碼格式,描述於 RFC 1952。這種編碼格式是一種具有 32 位 CRC 的 Lempel-Ziv 編碼(LZ77);
- deflate,由定義於 RFC 1950 的「ZLIB」編碼格式與 RFC 1951 中描述的「DEFLATE」壓縮機制組合而成的產物;
RFC 2616 對 Content-Encoding 中的 gzip 的定義很清晰,它就是指在 RFC 1952 中定義的 GZIP 編碼格式;但對 deflate 的定義含糊不清,實際上它指的是 RFC 1950 中定義的 ZLIB 編碼格式,但 deflate 這個名字特別容易產生誤會。
在 Zlib 庫的官方網站,有這麼一條 FAQ:What’s the difference between the “gzip” and “deflate” HTTP 1.1 encodings? 就是在討論 HTTP/1.1 對 deflate 的錯誤命名:
Q:在 HTTP/1.1 的 Content-Encoding 中,gzip 和 deflate 的區別是什麼?
A:gzip 是指 GZIP 格式,deflate 是指 ZLIB 格式。HTTP/1.1 的作者或許應該將後者稱之為
zlib
,從而避免與原始的 DEFLATE 資料格式產生混淆。雖然 HTTP/1.1 RFC 2016 正確指出,Content-Encoding 中的 deflate 就是由 RFC 1950 描述的 ZLIB,但有報告顯示仍然有部分伺服器和瀏覽器錯誤地產生或期望收到原始的 DEFLATE 格式,特別是微軟。所以雖然使用 ZLIB 更為高效(實際上這正是 ZLIB 的設計目標),但使用 GZIP 格式可能更為可靠,這一切都是因為 HTTP/1.1 的作者不幸地選擇了錯誤的命名。結論:在 HTTP/1.1 的 Content-Encoding 中,請使用 gzip。
在 HTTP/1.1 的修訂版 RFC 7230 的 4.2 Compression Codings 這一節中,徹底明確了 deflate 的含義,對 gzip 也做了補充:
- deflate,包含「使用 Lempel-Ziv 壓縮演算法(LZ77)和哈夫曼編碼的 DEFLATE 壓縮資料流(RFC 1951)」的 ZLIB 資料格式(RFC 1950)。注:一些不符合規範的實現會傳送沒有經過 ZLIB 包裝的 DEFLATE 壓縮資料;
- gzip,具有 32 位迴圈冗餘檢查(CRC)的 LZ77 編碼,通常由 Gzip 檔案壓縮程式(RFC 1952)產生。接受方應該將 x-gzip 視為 gzip;
總結一下,HTTP 標準中定義的 Content-Encoding: deflate,實際上指的是 ZLIB 編碼(RFC 1950)。但由於 RFC 2616 中含糊不清的定義,導致 IE 錯誤地實現為只接受原始 DEFLATE(RFC 1951)。為了相容 IE,我們只能用 Content-Encoding: gzip 進行內容編碼,它指的是 GZIP 編碼(RFC 1952)。
其實上,ZLIB 和 DEFLATE 的差別很小:ZLIB 資料去掉 2 位元組的 ZLIB 頭,再忽略最後 4 位元組的校驗和,就變成了 DEFLATE 資料。在 Fiddler 增加以下處理,就可以讓 IE 支援標準的 Content-Encoding: deflate(ZLIB 編碼),很好奇為啥微軟一直不改。
1 2 3 4 5 6 7 8 |
if ((compressedData.Length > 2) & ((compressedData[0] & 0xF) == 0x8) && // Low 4-bits must be 8 ((compressedData[0] & 0x80) == 0) && // High-bit must be clear ((((compressedData[0] 8) + compressedData[1]) % 31) == 0)) // Validate checksum { Debug.Write("Fiddler: Ignoring RFC1950 Header bytes for DEFLATE"); iStartOffset = 2; } |
由於其它瀏覽器也能解析原始 DEFLATE,所以有些 WEB 應用乾脆為了遷就 IE 直接輸出原始 DEFLATE,個人覺得這種不遵守標準的做法不值得推薦,還是推薦直接用 GZIP 編碼來獲得更好的相容性。
另外 Google 提出的 sdch 這種內容編碼方式,我之前關注過一段時間,不過只停留在理論階段,所以本文沒有提及,感興趣的同學可以自己去研究。
最後預告一下:今天這篇 Content-Encoding 以及 GZIP、ZLIB、DEFLATE 編碼格式的科普文,是為下一篇講《如何壓縮 HTTP 請求正文》做準備,敬請期待!
打賞支援我寫出更多好文章,謝謝!
打賞作者
打賞支援我寫出更多好文章,謝謝!
任選一種支付方式