使用wireshark抓取http2.0請求 html
點擊查看算法
method:get
、status:200
等等,隨着網頁增加到須要數十到數百個請求,這些請求中的冗餘標頭字段沒必要要地消耗帶寬,從而顯著增長了延遲,所以,Hpack技術應時而生。首先介紹下壓縮的概念(比較簡單,熟悉的能夠跳過):segmentfault
這個例子已經簡單介紹了壓縮的好處:能夠在傳輸的過程,簡化消息內容,從而下降消息的大小cookie
官方文檔裏的對Hpack的主要思想說明:工具
- 將header裏的字段列表視爲可包括重複對的name-value鍵值對的有序集合,分別使用8位字節表示name和value
- 當字段被編碼/解碼時,對應的字典會不斷擴充
- 在編碼形式中,header字段能夠直接表示,也可使用header field tables 中對應的引用。所以,可使用引用和文字值的混合來header字段列表。
- 文字值要麼直接編碼,要麼使用靜態huffman代碼
- 編碼器負責決定在標題字段表中插入哪些標題字段做爲新條目。解碼器執行對編碼器規定的報頭字段表的修改,重建處理中的報頭字段列表
以上摘自RFC 7541協議使用翻譯工具直接翻譯-_-,因此看起來有點艱澀,不要緊,先往下看編碼
因爲理論內容比較枯燥,因此先來幾張圖看一下效果,這裏使用wireshark來抓取對同一個頁面的兩次請求,查看對比。spa
以cookie
這個字段爲例,在上述兩次請求中:翻譯
因此經過簡單觀察,咱們能夠簡單得出如下結論:code
簡單描述一下Hpack算法的過程:htm
首先介紹一下前面在說明「壓縮過程」時,提到的字典。在Hpack
中,一共使用2個表來充當字典的角色:靜態表和動態表。
靜態表很簡單,只包含已知的header字段。點此查看完整的靜態表,分爲兩種:
:metho: GET
、:status: 200
:authority
、cookie
name
部分先用一個字符(好比cookie)來表示,同時,根據狀況判斷是否告知服務端,將 cookie: xxxxxxx
添加到動態表中(咱們這裏默認假定是從客戶端向服務端發送消息)cookie: xxxxxxx
就有可能被添加到動態表了,至因而否添加要根據後面提到的指令判斷)靜態表和動態表一塊兒組成一個索引地址空間。設靜態表長度爲s,動態表長度爲k,那麼最終的索引空間以下:
<---------- Index Address Space ----------> <-- Static Table --> <-- Dynamic Table --> +---+-----------+---+ +---+-----------+---+ | 1 | ... | s | |s+1| ... |s+k| +---+-----------+---+ +---+-----------+---+ ^ | | V Insertion Point Dropping Point
其中:
有了這個索引空間之後,header的字段一共有如下幾種表示方法:
header字段的表示法一共分2種,下面逐一說明。
數字主要用來表示上文中索引空間的索引值,具體的規則以下:
接下來看官方的一些例子幫助理解:
首先這裏限制位數爲5,因爲10小於2^5-1,能夠直接表示爲01010,結果爲:
0 1 2 3 4 5 6 7 +---+---+---+---+---+---+---+---+ | X | X | X | 0 | 1 | 0 | 1 | 0 | 10 stored on 5 bits +---+---+---+---+---+---+---+---
0 1 2 3 4 5 6 7 +---+---+---+---+---+---+---+---+ | X | X | X | 1 | 1 | 1 | 1 | 1 | Prefix = 31, I = 1306 | 1 | 0 | 0 | 1 | 1 | 0 | 1 | 0 | 1306>=128, encode(154), I=1306/128 | 0 | 0 | 0 | 0 | 1 | 0 | 1 | 0 | 10<128, encode(10), done +---+---+---+---+---+---+---+---+
直接從邊界開始,也就是使用8位前綴,42小於2^8-1=255 因此直接表示:
0 1 2 3 4 5 6 7 +---+---+---+---+---+---+---+---+ | 0 | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 42 stored on 8 bits +---+---+---+---+---+---+---+---+
header的字段能夠用字符串文原本表示,具體的規則以下:
0 1 2 3 4 5 6 7 +---+---+---+---+---+---+---+---+ | H | String Length (7+) | +---+---------------------------+ | String Data (Length octets) | +-------------------------------+
這種狀況下,第一個字節固定爲1,而後用7位前綴法表示索引的值
0 1 2 3 4 5 6 7 +---+---+---+---+---+---+---+---+ | 1 | Index (7+) | +---+---------------------------+ Figure 5: Indexed Header Field An indexed header field starts with the '1
例如10000010,表示索引值爲2,查找靜態表可知,對應的header字段是method:GET
注意咱們前面說索引空間的時候提到,索引空間地址是從1開始的,0的話會被視爲錯誤,也就是10000000解碼時會出錯。
這種狀況下,前兩位固定爲01,後面6位表示索引值,取到對應的name,例如01010000對應32,查靜態表可知name是cookie
,接下來使用字符串表示法表示對應的value字段,在解碼以後,這個字段就被加到動態表中,下次編碼的時候會直接使用狀況1,(這裏也就說明了爲何後續請求壓縮程度更大,由於動態表在不斷擴充,擴充的界限請看官方文檔這裏暫時不說明)
0 1 2 3 4 5 6 7 +---+---+---+---+---+---+---+---+ | 0 | 1 | Index (6+) | +---+---+-----------------------+ | H | Value Length (7+) | +---+---------------------------+ | Value String (Length octets) | +-------------------------------+
這種狀況和上面的很類似,只要補上name部分的字符串表示,而且把index值設置爲0便可。
0 1 2 3 4 5 6 7 +---+---+---+---+---+---+---+---+ | 0 | 1 | 0 | +---+---+-----------------------+ | H | Name Length (7+) | +---+---------------------------+ | Name String (Length octets) | +---+---------------------------+ | H | Value Length (7+) | +---+---------------------------+ | Value String (Length octets) | +-------------------------------+
觀察狀況2和狀況3可知,若是須要更新動態表,前兩位標誌位都是01
這種狀況,前四位固定爲0000,其餘和狀況2一致,
0 1 2 3 4 5 6 7 +---+---+---+---+---+---+---+---+ | 0 | 0 | 0 | 0 | Index (4+) | +---+---+-----------------------+ | H | Value Length (7+) | +---+---------------------------+ | Value String (Length octets) | +-------------------------------+
同理,前四位固定爲0000,其餘和狀況3一致,
0 1 2 3 4 5 6 7 +---+---+---+---+---+---+---+---+ | 0 | 0 | 0 | 0 | 0 | +---+---+-----------------------+ | H | Name Length (7+) | +---+---------------------------+ | Name String (Length octets) | +---+---------------------------+ | H | Value Length (7+) | +---+---------------------------+ | Value String (Length octets) | +-------------------------------+
觀察狀況2和狀況3可知,若是須要更新動態表,前兩位標誌位都是0000
這種狀況下和狀況4基本一致,只是前四位固定爲0001,區別在於:
0 1 2 3 4 5 6 7 +---+---+---+---+---+---+---+---+ | 0 | 0 | 0 | 0 | Index (4+) | +---+---+-----------------------+ | H | Value Length (7+) | +---+---------------------------+ | Value String (Length octets) | +-------------------------------+
0 1 2 3 4 5 6 7 +---+---+---+---+---+---+---+---+ | 0 | 0 | 0 | 1 | 0 | +---+---+-----------------------+ | H | Name Length (7+) | +---+---------------------------+ | Name String (Length octets) | +---+---------------------------+ | H | Value Length (7+) | +---+---------------------------+ | Value String (Length octets) | +-------------------------------+
和上面一種狀況同理,就略過了。
主要內容已經都說完了,接下來抓一些請求來看看具體的內容,好比直接抓取segment下的請求。
接下來看authority字段,咱們抓第一次和第二次請求的進行對比:
能夠看出,這個字段符合前面提到的狀況2:第一次編碼是01000001,表示name直接使用索引,索引值爲1,且value不在索引空間中,後面的部分表示具體的value值
第二次請求,發現已是直接使用索引空間的值(由於前一次請求已經要求更新到動態表),因此本次只要一個字符長度直接表示這個字段110001101,第一個1表示狀況1,後面1001101=64+8+4+1 =77 也就是此時對應的索引值
咱們前面提到動態表會隨請求增長不斷更新,可是動態表實際上是有大小限制的,所以動態表在增長條目時也可能會刪除條目,具體的更新規則等限於篇幅(沒錯,不是由於懶)不在本文更新。還有就是相關的huffman編碼等也不在此說明,本文主要仍是針對Hpack算法的過程和編碼規則作一些說明。主要參照RFC 7541協議
慣例:若是內容有錯誤的地方歡迎指出(以爲看着不理解不舒服想吐槽也徹底沒問題);若是對你有幫助,歡迎點贊和收藏,轉載請徵得贊成後著明出處,若是有問題也歡迎私信交流,主頁有郵箱地址