我眼中的 Nginx(二):HTTP/2 dynamic table size update

又拍雲發表於2019-03-08
張超:又拍雲系統開發高階工程師,負責又拍雲 CDN 平臺相關元件的更新及維護。Github ID: tokers,活躍於 OpenResty 社群和 Nginx 郵件列表等開源社群,專注於服務端技術的研究;曾為 ngx_lua 貢獻原始碼,在 Nginx、ngx_lua、CDN 效能優化、日誌優化方面有較為深入的研究。

 

眾所周知,HTTP/2 使用了 HPACK 來壓縮頭部,通過使用索引替代原始的文字來減少傳輸的位元組數。HPACK 維護了兩張表,一張稱為靜態表,由 RFC/7541 給出定義,包含了許多 HTTP 協議裡最常見的頭部名和值;另外一張則是動態表,可以由客戶端、服務端控制新的頭部欄位。

dynamic table size update

出於控制單連線資源消耗的目的, 協議允許連線兩端控制這張動態表的大小。

第一種方式是通過 HTTP/2 的 SETTINGS 幀進行通告:

SETTINGS_HEADER_TABLE_SIZE (0x1): Allows the sender to inform the remote endpoint of the maximum size of the header compression table used to decode header blocks, in octets. The encoder can select any size equal to or less than this value by using signaling specific to the header compression format inside a header block (see [COMPRESSION]). The initial value is 4,096 octets.

 第二種是在第一個 HEADERS 幀資料裡進行控制。

A change in the maximum size of the dynamic table is signaled via a dynamic table size update (see Section 6.3). This dynamic table size update MUST occur at the beginning of the first header block following the change to the dynamic table size. In HTTP/2, this follows a settings acknowledgment (see Section 6.5.3 of [HTTP2]).
 

Nginx 的相關實現

Nginx/1.13.6 引入了一個新特性,在一條連線第一次向對端傳送 HEADERS 幀時,就會用到上述第二種方式,要求對端把動態表清空。

這個新特性導致了一些對 HTTP/2 實現不完整的客戶端無法正常工作,例如一些 okhttp 的舊版本就會發出這樣的錯誤:

ProtocolException: Expected ':status' header not present

這裡 :status header 等價於 HTTP/1.x 狀態行裡的狀態碼(因為 HTTP/2 將請求行、狀態行的欄位都用偽頭部進行表示了,因此也稱為 header)。

究其根本就是這些客戶端在解析 HEADERS 幀的時候,沒有把 dynamic table size update 這種特性考慮進來,於是導致了協議解析失敗。

筆者就曾在公司的測試機器上遇到過這個問題,Nginx 並沒有把這個特性做成可配置的(很大程度上是因為這是協議裡明確規定的),後來也只能 revert 了那個提交。

 

《我眼中的 Nginx》專欄:

我眼中的 Nginx(一):Nginx 和位運算

相關文章