背景描述
阿裡雲CDN提供了
頁面優化功能,當開啟頁面優化功能時,CDN可以自動清除HTML頁面備援的注釋和重複的空白符,縮小檔案體積,提升頁面可閱讀性。本案例遇到的一個問題是,按照文檔開啟了CDN的頁面優化功能,但是通路HTML頁面實際并沒有優化效果。
Gzip壓縮引發
在排查測試的過程中,發現直接用curl URL的方式檢視響應結果,是已經被頁面優化了,但是直接在浏覽器上通路HTML卻沒有被優化。進一步對比curl請求以及浏覽器Network下的Request Header以及Response Header,發現浏覽器的Request Header帶了Accept-Encoding: gzip, deflate,Response Header傳回了Content-Encoding: gzip,如下圖所示

我們知道HTTP協定上的GZIP編碼是一種用來改進WEB應用程式性能的技術,大流量的WEB站點常常使用GZIP壓縮技術來讓使用者感受更快的速度。從測試來看,浏覽器請求頭帶了Accept-Encoding: gzip表示浏覽器支援解碼gzip壓縮後的檔案,如果伺服器支援gzip壓縮,那麼在收到這個請求頭以後,就會傳回gzip壓縮以後的檔案,在Response Header裡以Content-Encoding: gzip的形式展現。直接用如下curl指令帶Accept-Encoding請求頭測試,發現傳回結果也是帶了gzip的,且頁面也是沒有優化的。
curl -I 'http://測試URL' -H 'Accept-Encoding: gzip'
綜上可以暫時得出結論,傳回結果帶了Accept-Encoding: gzip以後,CDN的頁面壓縮不生效,傳回結果不帶Accept-Encoding: gzip,CDN的頁面壓縮生效。
Gzip壓縮是在哪裡發生的
由于請求的鍊路是:用戶端Client -->CDN -->源站
這個Gzip壓縮,可能是CDN産生的,也可能是源站産生的。這個比較容易驗證,直接用curl指令綁定到源站IP去測試即可得出結論
curl -I 'http://測試URL' -H 'Accept-Encoding: gzip' -x '源站IP:80'
測試來看,傳回結果果然帶了Accept-Encoding: gzip,說明是源站gzip壓縮導緻的。産生這個現象的原因逐漸浮出水面,整個過程如下:
當使用者在浏覽器發起請求時,浏覽器預設帶了Accept-Encoding: gzip這個請求頭,CDN作為一個代理伺服器,在回源請求源伺服器的時候轉發了這個來自真實用戶端(浏覽器)的請求頭,源伺服器由于開了Gzip壓縮,是以在收到這個請求頭以後傳回了Gzip壓縮以後的内容給CDN,由于CDN不具備Gunzip功能,是以無法對Gzip壓縮以後的内容去做頁面優化,是以導緻了頁面優化功能不生效。
Gzip配置參數
以上基本定位問題,但是為了更明确,我搭建了一個基于Nginx的Web伺服器用做CDN的源站,并在Nginx的配置檔案nginx.conf裡開啟了Gzip壓縮,配置如下圖
但是測試發現,通過CDN通路,并且發了Accept-Encoding: gzip請求頭以後,CDN依然可以完成頁面壓縮,這就比較奇怪。為了定位問題,直接在Web伺服器上抓了包,看一下CDN和Web伺服器的互動請求,發現一個很奇怪的現象:CDN請求Web伺服器的時候轉發了Accept-Encoding: gzip,但是Web伺服器并沒有響應Content-Encoding: gzip,封包如下圖:
根據這個現象,去查了一下Nginx官網對于
ngx_http_gzip_module子產品的配置說明,可以看到該子產品有如下配置參數
其中有一個gzip_proxied參數引起了注意。這個參數的含義,可以解釋如下
文法: gzip_proxied [off|expired|no-cache|no-store|private|no_last_modified|no_etag|auth|any] …
預設值: gzip_proxied off
作用域: http, server, location
Nginx作為反向代理的時候啟用,開啟或者關閉後端伺服器傳回的結果,比對的前提是後端伺服器必須要傳回包含”Via”的 header頭。
off – 對于所有來自代理伺服器的請求,都關閉壓縮
expired – 如果響應header頭中包含 “Expires” 頭資訊則啟用壓縮
no-cache – 如果響應header頭中包含 “Cache-Control:no-cache” 頭資訊則啟用壓縮
no-store – 如果響應header頭中包含 “Cache-Control:no-store” 頭資訊則啟用壓縮
private – 如果響應header頭中包含 “Cache-Control:private” 頭資訊則啟用壓縮
no_last_modified – 如果響應header頭中不包含 “Last-Modified” 頭資訊則啟用壓縮
no_etag – 如果響應header頭中不包含 “ETag” 頭資訊則啟用壓縮
auth – 如果響應header頭中包含 “Authorization” 頭資訊則啟用壓縮
any – 無條件啟用壓縮,也就是對任何來自代理伺服器的請求,都傳回壓縮的内容
由于Nginx配置裡配置了 gzip_proxied expired no-cache no-store private auth
是以相當于啟用了gzip_proxied參數,當Web伺服器發現來自代理伺服器的請求時(在這裡就是來自CDN的請求),Web伺服器會去校驗gzip_proxied參數,當發現伺服器的Response Header裡沒有傳回Expires、"Cache-Control:no-cache"等類似響應頭時,伺服器就傳回了不帶Gzip壓縮的資料。如果Gzip配置子產品是按照如下配置的話,那麼任何來自代理伺服器的請求,伺服器都會傳回Gzip壓縮的内容。
gzip_proxied any
如何判斷請求是來自代理伺服器的
那麼問題來了,伺服器是如何判斷這個請求是來自代理伺服器的,而不是真實用戶端呢。這裡就涉及到Via這個HTTP Header了。關于Via的介紹,可以參考HTTP協定關于
Via的文檔。Via 是一個通用首部,是由代理伺服器添加的,适用于正向和反向代理,在請求和響應首部中均可出現。這個消息首部可以用來追蹤消息轉發情況,防止循環請求,以及識别在請求或響應傳遞鍊中消息發送者對于協定的支援能力。在這裡,CDN作為代理伺服器,去請求源伺服器的時候,請求頭裡會帶上Via頭(這點在上面的抓包截圖裡也可以看到),而伺服器就是根據請求頭裡的Via得知該請求是來自上遊代理伺服器的。
HTTP伺服器的問題是知道代理本身是否能夠處理壓縮響應。傳入請求中的接受編碼頭(也就是Accept-Encoding: gzip)很可能是由原始客戶機請求提供的,但這并不能表明它所經過的代理或網關的能力,也就是說,伺服器并不知道上遊代理伺服器能否處理Gzip壓縮以後的内容。是以,在此場景中,伺服器采用最安全的選項,并選擇不壓縮它發回的響應,這也是合理的。關于Via這個Header對于Gzip壓縮的影響,可以參考
這篇Akamai的文章,有詳細的介紹。
一個新的想法
既然源站響應了gzip内容會導緻CDN的頁面優化不生效,那隻能源站響應未壓縮過的内容,但是這樣的話,最終對于用戶端來說,請求到的檔案沒有經過有效壓縮,還是會消耗用戶端帶寬,進而影響Web頁面的通路性能。而CDN除了提供頁面優化的功能外,還提供了Gzip的功能,那能不能在CDN層面去做頁面優化和Gzip壓縮呢?
通過測試發現,在CDN開啟頁面優化的同時,無論是開啟Gzip壓縮還是Br壓縮,隻要請求頭帶了accept-encoding: gzip, deflate, br頭,則頁面優化不生效。由此可見,在CDN層面,智能壓縮的優先級比較高,壓縮以後的内容無法頁面優化,看來這個方案暫時也不可行。當然,這部分的政策有待産品層面去改良。
總結和解決方案
綜上,該問題可以總結如下
(1)如果源站響應了Gzip壓縮的内容,CDN會因為無法Gunzip導緻頁面優化功能不生效
(2)如果希望與CDN去智能壓縮和頁面優化,是以CDN層面智能壓縮的優先級比頁面優化的優先級高,也會導緻頁面優化不生效
(3)如果期望使用CDN的頁面優化,那麼需要確定源站伺服器關閉Gzip壓縮。如果源站伺服器是Nginx,通過修改Nginx配置檔案裡ngx_http_gzip_module子產品的gzip_proxied參數,設定來自代理伺服器的請求,不傳回Gzip壓縮的内容來實作。
(4)另外還有一種比較實作方案,是可以在CDN層面配置删除Accept-Encoding這個回源HTTP請求頭。