探索HTTP傳輸中gzip壓縮的祕密

x!!!發表於2019-02-16

為什麼要開啟gZip

圖片描述

我們給某人傳送郵件時,我們在傳輸之前把自己的檔案壓縮一下,接收方收到檔案後再去解壓獲取檔案。這中操作對於我們來說都已經司空見慣。我們壓縮檔案的目的就是為了把傳輸檔案的體積減小,加快傳輸速度。我們在 http 傳輸中開啟 gZip 的目的也是如此,但是一般文章介紹 gZip 時候總是結合一些服務端配置(nginx)或者構建工具外掛(webpack)來說,列出一大堆配置讓人看的雲裡霧裡,以至於到最後還沒搞懂 為什麼用怎麼用 這些問題。

http 與 gZip

我們下面去探討一下這些問題

gZip 檔案怎麼通訊

我們傳輸壓縮檔案給別人時候一般都帶著字尾名 .rar, .zip之類,對方在拿到檔案後根據相應的字尾名選擇不同的解壓方式然後去解壓檔案。我們在 http 傳輸時候解壓檔案的這個角色的扮演者就是我們使用的瀏覽器,但是瀏覽器怎麼分辨這個檔案是什麼格式,應該用什麼格式去解壓呢?

http/1.0 協議中關於服務端傳送的資料可以配置一個 Content-Encoding 欄位,這個欄位用於說明資料的壓縮方法

Content-Encoding: gzip
Content-Encoding: compress
Content-Encoding: deflate

客戶端在接受到返回的資料後去檢查對應欄位的資訊,然後根據對應的格式去做相應的解碼。客戶端在請求時,可以用 Accept-Encoding 欄位說明自己接受哪些壓縮方法。

Accept-Encoding: gzip, deflate


我們在瀏覽器的控制檯中可以看到請求的相關資訊

圖片描述

相容性

提到瀏覽器作為一個前端就不由自主的會想一個問題,會不會有瀏覽器不支援呢。HTTP/1.0 是1996年5月釋出的。好訊息是基本不用考慮相容性的問題,幾乎所有瀏覽器都支援它。值得一提的是 ie6的早起版本中存在一個會破壞 gZip的錯誤,後面 ie6本身在 WinXP SP2 中修復了這個問題,而且用這個版本的使用者數量也很少。

誰去壓縮檔案

這件事看起來貌似只能服務端來做,我們在網上看到最多的也是諸如 nginx 開啟 gZip 配置之類的文章,但是現在前端流行 spa 應用, 用 react, vue 之類的框架時候總伴隨這一套自己的腳手架,一般用 webpack 作為打包工具,其中可以配置外掛 如compression-webpack-plugin 可以讓我們把生成檔案進行 gZip 等壓縮並生成對應的壓縮檔案,而我們應用在構架時候有可能也會在服務區和前端檔案中放置一層 node 應用來進行介面鑑權和檔案轉發。nodejs中我們熟悉的express 框架中也有一個compression 中介軟體,可以開啟gZip,一時間看的人眼花繚亂,到底應該用誰怎麼用呢?

服務端響應請求時候壓縮

其實 nginx 壓縮和 node 框架中用中介軟體去壓縮都是一樣的,當我們點選網頁傳送一個請求時候,我們的服務端會找到對應的檔案,然後對檔案進行壓縮返回壓縮後的內容【當然可以利用快取減少壓縮次數】,並配置好我們上面提到的 Content-Encoding 資訊。對於一些應用在構架時候並沒有上游代理層,比如服務端就一層 node 就可以直接用自己本身的壓縮外掛對檔案進行壓縮,如果上游配有有 nginx 轉發處理層,最好交給 nginx 來處理這些,因為它們有專門為此構建的內容,可以更好的利用快取並減小開銷(很多使用c語言編寫的)。

我們看一些 nginx 中開啟 gZip 壓縮的一部分配置

# 開啟gzip
gzip on;
# 啟用gzip壓縮的最小檔案,小於設定值的檔案將不會壓縮
gzip_min_length 1k;
# gzip 壓縮級別,1-10,數字越大壓縮的越好,也越佔用CPU時間,後面會有詳細說明
gzip_comp_level 2;
# 進行壓縮的檔案型別。javascript有多種形式。其中的值可以在 mime.types 檔案中找到。
gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript;
應用構建時候壓縮

既然服務端都可以做了為什麼 webpack 在打包前端應用時候還有這樣一個壓縮外掛呢,我們可以在上面 nginx 配置中看到 gzip_comp_level 2 這個配置項,上面也有註釋寫道 1-10 數字越大壓縮效果越好,但是會耗費更多的CPU和時間,我們壓縮檔案除了減少檔案體積大小外,也是為了減少傳輸時間,如果我們把壓縮等級配置的很高,每次請求服務端都要壓縮很久才回返回資訊回來,不僅伺服器開銷會增大很多,請求方也會等的不耐煩。但是現在的 spa 應用既然檔案都是打包生成的,那如果我們在打包時候就直接生成高壓縮等級的檔案,作為靜態資源放在伺服器上,接收到請求後直接把壓縮的檔案內容返回回去會怎麼樣呢?

webpackcompression-webpack-plugin 就是做這個事情的,配置起來也很簡單隻需要在裝置中加入對應外掛,簡單配置如下

const CompressionWebpackPlugin = require('compression-webpack-plugin');

webpackConfig.plugins.push(
    new CompressionWebpackPlugin({
      asset: '[path].gz[query]',
      algorithm: 'gzip',
      test: new RegExp('\\.(js|css)$'),
      threshold: 10240,
      minRatio: 0.8
    })
)

webpack 打包完成後生成打包檔案外還會額外生成 .gz 字尾的壓縮檔案

圖片描述

那麼這個外掛的壓縮等級是多少呢,我們可以在原始碼中看到預設的 level9

...
const zlib = require('zlib');
this.options.algorithm = zlib[this.options.algorithm];
...
this.options.compressionOptions = {
    level: options.level || 9,
    flush: options.flush
    ...
}

可以看到壓縮使用的是 zlib 庫,而 zlib 分級來說,預設是 6 ,最高的級別就是9 Best compression (also zlib.Z_BEST_COMPRESSION),因為我們只有在上線專案時候才回去打包構建一次,所以我們在構建時候使用最高階的壓縮方式壓縮多耗費一些時間對我們來說根本沒任何損耗,而我們在伺服器上也不用再去壓縮檔案,只需要找到相應已經壓縮過的檔案直接返回就可以了。

服務端怎麼找到這些檔案

在應用層面解決這個問題還是比較簡單的,比如上述壓縮檔案會產生index.css, index.js的壓縮檔案,在服務端簡單處理可以判斷這兩個請求然後給予相對應的壓縮檔案。以 nodeexpress 為例

...
app.get(['/index.js','/index.css'], function (req, res, next) {
  req.url = req.url + '.gz'
  res.set('Content-Encoding', 'gzip')
  res.setHeader("Content-Type", generateType(req.path)) // 這裡要根據請求檔案設定content-type
  next()
})

上面我們可以給請求返回 gZip 壓縮後的資料了,當然上面的侷限性太強也不可取,但是對於處理這個方面需求也已經有很多庫存在,expressexpress-static-gzip 外掛 koakoa-static 則預設自帶對 gZip 檔案的檢測,基本原理就是對請求先檢測 .gz字尾的檔案是否存在,再去根據結果返回不同的內容。

哪些檔案可以被 gZip 壓縮

gZip 可以壓縮所有的檔案,但是這不代表我們要對所有檔案進行壓縮,我們寫的程式碼(css,js)之類的檔案會有很好的壓縮效果,但是圖片之類檔案則不會被 gzip 壓縮太多,因為它們已經內建了一些壓縮,一些檔案(比如一些已經被壓縮的像.zip檔案那種)再去壓縮可能會讓生成的檔案體積更大一些。當然已經很小的檔案也沒有去壓縮的必要了。

實踐

能開啟 gZip 肯定是要開啟的,具體使用在請求時候實時壓縮還是在構建時候去生成壓縮檔案,就要看自己具體業務情況。

參考資料

相關文章