HTTP2於2015年2月28日正式通過IETF組織批准發布,正式定稿。有關它的內容可以參考: HTTP2 概述 http://www.cnblogs.com/ghj1976/p/4552583.html 。
在HTTP2 的網絡通訊中, Frame 是 通訊中的最小傳輸單位,至少含有一個 Frame header,能夠表示它屬於哪一個 Stream。一個具體的請求類似如下:
HTTP/2 幀通用格式:
幀頭+負載的比特位通用結構:
幀頭為固定的9個字節((24+8+8+1+31)/8=9)呈現,變化的為幀的負載(Frame Payload),負載內容是由幀類型(Type)定義。
- 幀長度Length:無符號的自然數,24個比特表示,僅表示幀負載(Frame Payload)所占用字節數,不包括幀頭所占用的9個字節。
默認大小區間為為0~16,384(2^14),一旦超過默認最大值2^14(16384),發送方將不再允許發送,除非接收到接收方定義的SETTINGS_MAX_FRAME_SIZE(一般此值區間為2^14 ~ 2^24)值的通知。 - 幀類型Type:8個比特表示,定義了幀負載的具體格式和幀的語義,HTTP/2規范定義了10個幀類型,這里不包括實驗類型幀和擴展類型幀
- 幀的標志位Flags:8個比特表示,服務於具體幀類型,默認值為0x0。
有一個小技巧需要注意,一般來講,8個比特可以容納8個不同的標志,比如,PADDED值為0x8,二進制表示為00001000;END_HEADERS值為0x4,二進制表示為00000100;END_STREAM值為0X1,二進制為00000001。可以同時在一個字節中傳達三種標志位,二進制表示為00001101,即0x13。因此,后面的幀結構中,標志位一般會使用8個比特表示,若某位不確定,使用問號?替代,表示此處可能會被設置標志位 - 幀保留比特為R:在HTTP/2語境下為保留的比特位,固定值為0X0
- 流標識符Stream Identifier:無符號的31比特表示無符號自然數。0x0值表示為幀僅作用於連接,不隸屬於單獨的流。
關於幀長度,需要稍加關注: - 0 ~ 2^14(16384)為默認約定長度,所有端點都需要遵守 - 2^14 (16,384) ~ 2^24-1(16,777,215)此區間數值,需要接收方設置SETTINGS_MAX_FRAME_SIZE參數單獨賦值 - 一端接收到的幀長度超過設定上限或幀太小,需要發送FRAME_SIZE_ERR錯誤 - 當幀長錯誤會影響到整個連接狀態時,須以連接錯誤對待之;比如HEADERS,PUSH_PROMISE,CONTINUATION,SETTINGS,以及幀標識符不該為0的幀等,都需要如此處理 - 任一端都沒有義務必須使用完一個幀的所有可用空間 - 大幀可能會導致延遲,針對時間敏感的幀,比如RST_STREAM, WINDOW_UPDATE, PRIORITY,需要快速發送出去,以免延遲導致性能等問題
HTTP2 的幀包含下面幾種類型,對應上圖的Type區域定義。
Frame Type Code
DATA 0x0
HEADERS 0x1
PRIORITY 0x2
RST_STREAM 0x3
SETTINGS 0x4
PUSH_PROMISE 0x5
PING 0x6
GOAWAY 0x7
WINDOW_UPDATE 0x8
CONTINUATION 0x9
參考:
HTTP/2 frame format
http://segmentfault.com/a/1190000002586816
幀的標志位(Flags)含義如下圖:
圖來自: http://search.cpan.org/~crux/Protocol-HTTP2-0.14/lib/Protocol/HTTP2/Frame.pm
案例:
假設我們要發送 0x12345678,流編號為 10 ,類型為DATA,那么這個Frame的16進制表達就是:
'000004' + '00' + '00' + '0000000A' + '12345678'
HTTP2 的 Header 幀
HTTP2的 HEADER幀的格式如下:
對應的字段列表說明如下:
- Pad Length:受制於PADDED標志控制是否顯示,8個比特表示填充的字節數。 可選。Flags:PADDED 設置后要求有此字段
- E:一個比特表示流依賴是否專用,可選項,只在流優先級PRIORITY被設置時有效 可選。Flags:PRIORITY 設置后要有此字段
- Stream Dependency:31個比特表示流依賴,只在流優先級PRIORITY被設置時有效 可選。Flags:PRIORITY 設置后要有此字段
- Weight:8個比特(一個字節)表示無符號的自然數流優先級,值范圍自然是(1~256),或稱之為權重。只在流優先級PRIORITY被設置時有效 這個字段是可選的,並且只在優先級標記設置的情況下才呈現。
- Header Block Fragment:報頭塊分片
- Padding:填充的字節,受制於PADDED標志控制是否顯示,長度由Pad Length字段決定
注意, 只有 Header Block Fragment 是必須的, 其他都看 幀的標志位Flags 是否設置要有。
所需標志位:
- END_STREAM (0x1): 報頭塊為最后一個,意味着流的結束。
END_HEADERS (0x4): 此報頭幀不需分片,完整的一個幀。后續不再需要CONTINUATION幀幫忙湊齊。若沒有此標志的HEADER幀,后續幀必須是以CONTINUATION幀傳遞在當前的流中,否則接收者需要響應PROTOCOL_ERROR類型的連接錯誤。 - PADDED (0x8): 需要填充的標志
- PRIORITY (0x20): 優先級標志位,控制獨立標志位E,流依賴,和流權重。
注意事項:
- 其負載為報頭塊分片,若內容過大,需要借助於CONTINUATION幀繼續傳輸。若流標識符為0x0,結束段需要返回PROTOCOL_ERROR連接異常。HEADERS幀包含優先級信息是為了避免潛在的不同流之間優先級順序的干擾。
- 其實一般來講,報文頭部不大的情況下,一個HEADERS就可以完成了,特殊情況就是Cookie字段超過16KiB大小,不常見。
HTTP2的 CONTINUATION 幀
HTTP2的 CONTINUATION 幀的格式如下:
字段列表:
- Header Block Fragment,用於協助HEADERS/PUSH_PROMISE等單幀無法包含完整的報頭剩余部分數據。
注意事項:
- 一個HEADERS/PUSH_PROMISE幀后面會跟隨零個或多個CONTINUATION,只要上一個幀沒有設置END_HEADERS標志位,就不算一個幀完整數據的結束。
- 接收端處理此種情況,從開始的HEADERS/PUSH_PROMISE幀到最后一個包含有END_HEADERS標志位幀結束,合並的數據才算是一份完整數據拷貝
- 在HEADERS/PUSH_PROMISE(沒有END_HEADERS標志位)和CONTINUATION幀中間,是不能夠摻雜其它幀的,否則需要報PROTOCOL_ERROR錯誤
標志位: * END_HEADERS(0X4):表示報頭塊的最后一個幀,否則后面還會跟隨CONTINUATION幀。
HTTP2的 Data幀
一個或多個DATA幀作為請求、響應內容載體,較為完整的結構如下:
字段:
- Pad Length: 一個字節表示填充的字節長度。取決於PADDED標志是否被設置.
- Data: 這里是應用數據,真正大小需要減去其他字段(比如填充長度和填充內容)長度。
- * Padding: 填充內容為若干個0x0字節,受PADDED標志控制是否顯示。接收端處理時可忽略驗證填充內容。若驗證,可以對非0x0內容填充回應PROTOCOL_ERROR類型連接異常。
標志位:
- END_STREAM (0x1): 標志此幀為對應標志流最后一個幀,流進入了半關閉/關閉狀態。
- PADDED (0x8): 負載需要填充,Padding Length + Data + Padding組成。
注意事項:
- 若流標識符為0x0,接收者需要響應PROTOCOL_ERROR連接錯誤
- DATA幀只能在流處於"open" or "half closed (remote)"狀態時被發送出去,否則接收端必須響應一個STREAM_CLOSED的連接錯誤。若填充長度不小於負載長度,接收端必須響應一個PROTOCOL_ERROR連接錯誤。
例子
以gRPC的 HelloWorld 項目為例, 有關這個項目的搭建請參考: http://www.cnblogs.com/ghj1976/p/4549602.html
其中一個網絡請求包的內容如下圖截圖:
這是來自 CommView 的 Loopback 網絡請求監控的包, 如何使用 CommView 請參考: http://www.cnblogs.com/ghj1976/p/4554982.html
這里黑色加量的部分是 RPC 特有的部分內容。
這部分包含2個幀,他們的分別數據如下:
00 00-0E 01 04 00 00 00 01 88 5F 8B 1D 75 D0 62 0D 26-3D 4C 4D 65 64
00 00 12 00 00 00 00 00 01 00 00-00 00 0D 0A 0B 48 65 6C 6C 6F 20 77 6F 72 6C 64
Header 幀
00 00 0E 01 04 00 00 00 01 88 5F 8B 1D 75 D0 62 0D 26 3D 4C 4D 65 64
幀長度Length 為 00 00 0E ,即 14 + 9 長度共23 。
幀類型Type 為 01 標示是 Header 幀
幀的標志位Flags 為 04, 標示 END_HEADERS, 即這個Header幀不需要分片。
流標識符Stream Identifier 為 00 00 00 01 即,編號為1 。
Header 幀特有的串(Header Block Fragment): 88 5F 8B 1D 75 D0 62 0D 26 3D 4C 4D 65 64
這里做了壓縮,就不展開了, 相關知識請參考: http://http2.github.com/http2-spec/compression.html
Data幀
00 00 12 00 00 00 00 00 01 00 00 00 00 0D 0A 0B 48 65 6C 6C 6F 20 77 6F 72 6C 64
幀長度Length 為 00 00 12 即 18+9 = 27
幀類型Type 為 00 標示是 Data 幀
幀的標志位Flags 00
流標識符Stream Identifier 為 00 00 00 01 ,及 編號1, 對應上面的 Header 幀。
Data幀特有的串(Data 區域): 00 00 00 00 0D 0A 0B 48 65 6C 6C 6F 20 77 6F 72 6C 64
參考資料:
HTTP2協議中報文頭可以采用Haffman編碼,我們看到的報文頭信息都是二進制信息。
HTTP2的報文格式請參考: http://www.blogjava.net/yongboy/archive/2015/03/20/423655.html
HTTP2報文頭及數據幀格式解析實例分析
http://blog.csdn.net/jiayanhui2877/article/details/45074315
Haffman 壓縮算法請參考: http://coolshell.cn/articles/7459.html