1. 程式人生 > >我眼中的 Nginx(二):HTTP/2 dynamic table size update

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

服務 bsp lin revert code 負責 ont mat expect

張超:又拍雲系統開發高級工程師,負責又拍雲 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 和位運算

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