客戶端和服務器端一旦握手協商成功接創建鏈接,端點之間能夠基於HTTP/2協議傳遞交換幀數據了。html
下圖爲HTTP/2幀通用格式:幀頭+負載的比特位通用結構:java
+-----------------------------------------------+ | Length (24) | +---------------+---------------+---------------+ | Type (8) | Flags (8) | +-+-------------+---------------+-------------------------------+ |R| Stream Identifier (31) | +=+=============================================================+ | Frame Payload (0...) ... +---------------------------------------------------------------+
幀頭爲固定的9個字節((24+8+8+1+31)/8=9)呈現,變化的爲幀的負載(payload),負載內容是由幀類型(Type)定義。git
關於幀長度,須要稍加關注: - 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,須要快速發送出去,以避免延遲致使性能等問題github
和HTTP/1同樣,HTTP/2報頭字段包含一個或多個相關的鍵值對。報頭字段會在HTTP請求/響應報頭和服務器推送操做中使用。原先爲文本字段,如今須要使用HTTP報頭壓縮進行序列化成報頭分塊,做爲HEADERS 、 PUSH_PROMISE、CONTINUATION等幀的負載傳輸出去。安全
解壓縮採用的HPACK協議,具體可參考:http://http2.github.com/http2-spec/compression.html服務器
接收端合併接收到的幀組裝成報頭分塊,解壓縮還原報頭集合。異步
一個完整的報頭分塊包含: - 單個包含報頭終止標記END_HEADERS的HEADERS、PUSH_PROMISE幀,或者 - HEADERS、PUSH_PROMISE幀不包含的END_HEADERS標記,後續跟隨一個或多個CONTINUATION幀,最後一個CONTINUATION幀包含了END_HEADERS標記。性能
報頭壓縮是有狀態的,在一個完整的鏈接中,一方的壓縮上下文環境,另外一方的解壓的上下文環境,都是須要具有的。報頭解碼失敗須要做爲鏈接錯誤COMPRESSION_ERROR對待。測試
報頭塊彼此之間離散,做爲連續的同一類型幀序列存在,不存在交錯幀以及來自其餘類型幀或流。舉一個例子,一個連續的HEADERS/CONTINUATION/PUSH_PROMISE幀序列,最後一個幀包含了END_HEADERS標記,表示一個報頭完結。一個報頭塊邏輯上是一個幀,可是否完整取決於同類型連續的幀的最後一個包含END_HEADERS標記。.net
報頭塊做爲HEADERS/PUSH_PROMISE/CONTINUATION等幀負載被一端發向另外一端。接收端須要從HEADERS/PUSH_PROMISE/CONTINUATION等幀負載中進行組裝報頭塊,執行解壓還原報頭集合,無論幀須要不須要被丟棄。接收端在解壓時若不可以正常解壓報頭塊,須要迴應COMPRESSION_ERROR錯誤,而後終止鏈接。
規範定義了10個正式使用到幀類型,擴展實驗類型的ALTSVC、BLOCKED等不在介紹之列。下面按照優先使用順序從新排排序。
+-----------------------------------------------+ | Length (24) | +---------------+---------------+---------------+ | 0x4 (8) | 0000 000? (8) | +-+-------------+---------------+-------------------------------+ |R| Stream Identifier/0x0 (32) | +=+=============================+===============================+ | Identifier (16) | +-------------------------------+-------------------------------+ | Value (32) | +---------------------------------------------------------------+ | Identifier (16) | +-------------------------------+-------------------------------+ | Value (32) | +---------------------------------------------------------------+
設置幀,接收者向發送者通告己方設定,服務器端在鏈接成功後必須第一個發送的幀。
字段Identifier定義了以下參數: - SETTINGS_HEADER_TABLE_SIZE (0x1),通知接收者報頭表的字節數最大值,報頭塊解碼使用;初始值爲4096個字節,默承認不用設置 - SETTINGS_ENABLE_PUSH (0x2),0:禁止服務器推送,1:容許推送;其它值非法,PROTOCOL_ERROR錯誤 - SETTINGS_MAX_CONCURRENT_STREAMS (0x3),發送者容許可打開流的最大值,建議值100,默承認不用設置;0值爲禁止建立新流 - SETTINGS_INITIAL_WINDOW_SIZE (0x4),發送端流控窗口大小,默認值2^16-1 (65,535)個字節大小;最大值爲2^31-1個字節大小,若溢出須要報FLOW_CONTROL_ERROR錯誤 - SETTINGS_MAX_FRAME_SIZE (0x5),單幀負載最大值,默認爲2^14(16384)個字節,兩端所發送幀都會收到此設定影響;值區間爲2^14(16384)-2^24-1(16777215) - SETTINGS_MAX_HEADER_LIST_SIZE (0x6),發送端通告本身準備接收的報頭集合最大值,即字節數。此值依賴於未壓縮報頭字段,包含字段名稱、字段值以及每個報頭字段的32個字節的開銷等;文檔裏面雖然說默認值不受限制,由於受到報頭集合大小不限制的影響,我的認爲不要多於2 SETTINGS_MAX_FRAME_SIZE(即2^142=32768),不然包頭太大,隱患多多。
標誌位: * ACK (0x1),表示接收者已經接收到SETTING幀,做爲確認必須設置此標誌位,此時負載爲空,不然須要報FRAME_SIZE_ERROR錯誤
注意事項: - 在鏈接開始階段必須容許發送SETTINGS幀,但不必定要發送 - 在鏈接的生命週期內能夠容許任一端點發送 - 接收者不須要維護參數的狀態,只須要記錄當前值便可 - SETTINGS幀僅做用於當前鏈接,不針對單個流,所以流標識符爲0x0 - 不完整或不合規範的SETTINGS幀須要拋出PROTOCOL_ERROR類型鏈接錯誤 - 負載字節數爲6個字節的倍數,6*N (N>=0)
處理流程: - 發送端發送須要兩端都須要攜帶有遵照的SETTINGS設置幀,不可以帶有ACK標誌位 - 接收端接收到無ACK標誌位的SETTINGS幀,必須按照幀內字段出現順序一一進行處理,中間不可以處理其它幀 - 接收端處理時,針對不受支持的參數需忽略 - 接收端處理完畢以後,必須響應一個包含有ACK確認標誌位、無負載的SETTINGS幀 - 發送端接收到確認的SETTINGS幀,表示兩端設置已生效 - 發送端等待確認若超時,報SETTINGS_TIMEOUT類型鏈接錯誤
+-----------------------------------------------+ | Length (24) | +---------------+---------------+---------------+ | 0x1 (8) | 00?0 ??0? (8) | +-+-------------+---------------+-------------------------------+ |R| Stream Identifier (31) | +=+=============+===============================================+ |Pad Length? (8)| +-+-------------+-----------------------------------------------+ |E| Stream Dependency? (31) | +-+-------------+-----------------------------------------------+ | Weight? (8) | +-+-------------+-----------------------------------------------+ | Header Block Fragment (*) ... +---------------------------------------------------------------+ | Padding (*) ... +---------------------------------------------------------------+
報頭主要載體,請求頭或響應頭,同時呢也用於打開一個流,在流處於打開"open"或者遠程半關閉"half closed (remote)"狀態均可以發送。
字段列表: - Pad Length:受制於PADDED標誌控制是否顯示,8個比特表示填充的字節數。 - E:一個比特表示流依賴是否專用,可選項,只在流優先級PRIORITY被設置時有效 - Stream Dependency:31個比特表示流依賴,只在流優先級PRIORITY被設置時有效 Weight:8個比特(一個字節)表示無符號的天然數流優先級,值範圍天然是(1~256),或稱之爲權重。只在流優先級PRIORITY被設置時有效 - Header Block Fragment:報頭塊分片 - Padding:填充的字節,受制於PADDED標誌控制是否顯示,長度由Pad Length字段決定
所需標誌位: END_STREAM (0x1): 報頭塊爲最後一個,意味着流的結束。後續可緊接着CONTINUATION幀在當前的流中,須要把CONTINUATION幀做爲HEADERS幀的一部分對待END_HEADERS (0x4): 此報頭幀不需分片,完整的一個幀。後續再也不須要CONTINUATION幀幫忙湊齊。若沒有此標誌的HEADER幀,後續幀必須是以CONTINUATION幀傳遞在當前的流中,不然接收者須要響應PROTOCOL_ERROR類型的鏈接錯誤。 PADDED (0x8): 須要填充的標誌 PRIORITY (0x20): 優先級標誌位,控制獨立標誌位E,流依賴,和流權重。
注意事項: - 其負載爲報頭塊分片,若內容過大,須要藉助於CONTINUATION幀繼續傳輸。若流標識符爲0x0,結束段須要返回PROTOCOL_ERROR鏈接異常。HEADERS幀包含優先級信息是爲了不潛在的不一樣流之間優先級順序的干擾。 - 其實通常來說,報文頭部不大的狀況下,一個HEADERS就能夠完成了,特殊狀況就是Cookie字段超過16KiB大小,不常見。
+-----------------------------------------------+ | Length (24) | +---------------+---------------+---------------+ | 0x9 (8) | 0x0/0x4 (8) | +-+-------------+---------------+-------------------------------+ |R| Stream Identifier (32) | +=+=============================================================+ | Header Block Fragment (*) | +---------------------------------------------------------------+
字段列表: - 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幀。
一個或多個DATA幀做爲請求、響應內容載體,較爲完整的結構以下:
+-----------------------------------------------+ | Length (24) | +---------------+---------------+---------------+ | 0x0 (8) | 0000 ?00? (8) | +-+-------------+---------------+-------------------------------+ |R| Stream Identifier (31) | +=+=============+===============================================+ |Pad Length? (8)| +---------------+-----------------------------------------------+ | Data (*) ... +---------------------------------------------------------------+ | Padding? (*) ... +---------------------------------------------------------------+
字段: 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鏈接錯誤。
+-----------------------------------------------+ | Length (24) | +---------------+---------------+---------------+ | 0x5 (8) | 0000 ??00 (8) | +-+-------------+---------------+-------------------------------+ |R| Stream Identifier (32) | +=+=============================================================+ |Pad Length? (8)| +-+-------------+-----------------------------------------------+ |R| Promised Stream ID (31) | +-+-------------------------------------------------------------+ | Header Block Fragment (*) . . . +---------------------------------------------------------------+ | Padding (*) . . . +---------------------------------------------------------------+
服務器端通知對端初始化一個新的推送流準備稍後推送數據: - 要求推送流爲打開或遠端半關閉(half closed (remote))狀態,不然報PROTOCOL_ERROR錯誤: - 承諾的流不必定要按照其流打開順序進行使用,僅用做稍後使用 - 受對端所設置SETTINGS_ENABLE_PUSH標誌位決定是否發送,不然做爲PROTOCOL_ERROR錯誤對待 - 接收端一旦拒絕接收推送,會發送RST_STREAM幀告知對方推送無效
字段列表: - Promised Stream ID,31個比特表示無符號的天然數,爲推送保留的流標識符,後續適用於發送推送數據 - Header Block Fragment,請求頭部字段值,可看作是服務器端模擬客戶端發起一次資源請求
標誌位: END_HEADERS(0x4/00000010),此幀包含完整的報頭塊,不用後面跟隨CONTINUATION幀了 PADDED(0x8/00000100),填充開關,決定了下面的Pad Length和Padding是否要填充,具體和HEADERS幀內容一致,很少說
優先級幀,類型值爲0x6,8個字節表示。發送者測量最小往返時間,心跳機制用於檢測空閒鏈接是否有效。
+-----------------------------------------------+ | 0x8 (24) | +---------------+---------------+---------------+ | 0x6 (8) | 0000 000? (8) | +-+-------------+---------------+-------------------------------+ |R| 0x0 (32) | +=+=============================================================+ | Opaque Data (64) | +---------------------------------------------------------------+
字段列表: - Opaque Data:8個字節負載,值隨意填寫。
標誌位: * ACK(0x1):一旦設置,表示此PING幀爲接收者響應的PING幀,非發送者。
注意事項: - PING幀發送方ACK標誌位爲0x0,接收方響應的PING幀ACK標誌位爲0x1。不然直接丟棄。其優先級要高於其它類型幀。 - PING幀不和具體流相關聯,若流標識符爲0x0,接收方須要響應PROTOCOL_ERROR類型鏈接錯誤。 - 超過負載長度,接收者須要響應FRAME_SIZE_ERROR類型鏈接錯誤。
優先級幀,類型值爲0x2,5個字節表示。表達了發送方對流優先級權重的建議值,在流的任何狀態下均可以發送,包括空閒或關閉的流。
+-----------------------------------------------+ | 0x5 (24) | +---------------+---------------+---------------+ | 0x2 (8) | 0x0 (8) | +-+-------------+---------------+-------------------------------+ |R| Stream Identifier (31) | +=+=============================================================+ |E| Stream Dependency (31) | +-+-------------+-----------------------------------------------+ | Weight (8) | +---------------+
字段列表: - E:流是否獨立 - Stream Dependency:流依賴,值爲流的標識符,天然也是31個比特表示。 - Weight:權重/優先級,一個字節表示天然數,範圍1~256
注意事項: - PRIORITY幀其流標識符爲0x0,接收方須要響應PROTOCOL_ERROR類型的鏈接錯誤。 - PRIORITY幀可在流的任何狀態下發送,但限制是不可以在一個包含有報頭塊連續的幀裏面出現,其發送時刻須要,若流已經結束,雖然能夠發送,但已經沒有什麼效果。 - 超過5個字節PRIORITY幀接收方響應FRAME_SIZE_ERROR類型流錯誤。
+-----------------------------------------------+ | 0x4 (24) | +---------------+---------------+---------------+ | 0x8 (8) | 0x0 (8) | +-+-------------+---------------+-------------------------------+ |R| Stream Identifier (31) | +=+=============================================================+ |R| Window Size Increment (31) | +-+-------------------------------------------------------------+
流量控制幀,做用於單個流以及整個鏈接,但只能影響兩個端點之間傳輸的DATA數據幀。但需注意,中介不轉發此幀。
字段列表: - Window Size Increment,31個比特位無符號天然數,範圍爲1-2^31-1(2,147,483,647)個字節數,代表發送者能夠發送的最大字節數,以及接收者能夠接收到的最大字節數。
注意事項: - 目前流控只會影響到DATA數據幀 - 流標識符爲0,影響整個鏈接,非單個流 - 流標識符不爲空,具體流的標識符,將只可以影響到具體流 - WINDOW_UPDATE在某個攜帶有END_STREAM幀的後面被髮送(當前流處於關閉或遠程關閉狀態),接收端可忽略,但不能做爲錯誤對待 - 發送者不能發送一個窗口值大於接收者已持有(接收端已經擁有一個流控窗口值)可用空間大小的WINDOW_UPDATE幀 - 當流控窗口所設置可用空間已耗盡時,對端發送一個零負載帶有END_STREAM標誌位的DATA數據幀,這是容許的行爲 - 流量控制不會計算幀頭所佔用的9個字節空間 - 若窗口值溢出,針對單獨流,響應RST_STREAM(錯誤碼FLOW_CONTROL_ERROR)幀;針對整個鏈接的,響應GOAWAY(錯誤碼FLOW_CONTROL_ERROR)幀 - DATA數據幀的接收方在接收到數據幀以後,須要計算已消耗的流控窗口可用空間,同時要把最新可用窗口空間發送給對端 - DATA數據幀發送方接收到WINDOW_UPDATE幀以後,獲取最新可用窗口值 - 接收方異步更新發送方窗口值,避免流停頓/失速 - 默認狀況下流量控制窗口值爲65535,除非接收到SETTINGS幀SETTINGS_INITIAL_WINDOW_SIZE參數,或者WINDOWS_UPDATE幀攜帶的窗口值大小,不然不會改變 - SETTINGS_INITIAL_WINDOW_SIZE值的改變會致使窗口可用空間不明晰,易出問題,發送者必須中止受流控影響的DATA數據幀的發送直到接收到WINDOW_UPDATE幀得到新的窗口值,纔會繼續發送。eg:客戶端在鏈接創建的瞬間一口氣發送了60KB的數據,但來自服務器SETTINGS設置幀的初始窗口值爲16KB,客戶端只可以等到WINDOW_UPDATE幀告知新的窗口值,而後繼續發送傳送剩下的44KB數據 - SETTINGS幀沒法修改針對整個鏈接的流量控制窗口值 - 任一端點在處理SETTINGS_INITIAL_WINDOW_SIZE值時一旦致使流控窗口值超出最大值,都須要做爲一個FLOW_CONTROL_ERROR類型鏈接錯誤對待
優先級幀,類型值爲0x3,4個字節表示。表達了發送方對流優先級權重的建議值,任什麼時候間任何流均可以發送,包括空閒或關閉的流。
+-----------------------------------------------+ | 0x4 (24) | +---------------+---------------+---------------+ | 0x3 (8) | 0x0 (8) | +-+-------------+---------------+-------------------------------+ |R| Stream Identifier (31) | +=+=============================================================+ | Error Code (32) | +---------------------------------------------------------------+
字段列表: - Error Code:錯誤代碼,32位無符號的天然數表示流被關閉的錯誤緣由。
注意事項: - 接收到RST_STREAM幀,須要關閉對應流,所以流也要處於關閉狀態。 - 接收者不可以在此流上發送任何幀。 - 發送端須要作好準備接收接收端接收到RST_STREAM幀以前發送的幀,這個空隙的幀須要處理。 - 若流標識符爲0x0,接收方須要響應PROTOCOL_ERROR類型鏈接錯誤。 - 當流處於空閒狀態idle狀態時是不可以發送RST_STREAM幀,不然接收方會報以PROOTOCOL_ERROR類型鏈接錯誤。
+-----------------------------------------------+ | Length (24) | +---------------+---------------+---------------+ | 0x7 (8) | 0x0 (8) | +-+-------------+---------------+-------------------------------+ |R| Stream Identifier (32) | +=+=============================================================+ |R| Last-Stream-ID (31) | +-+-------------------------------------------------------------+ | Error Code (32) | +---------------------------------------------------------------+ | Additional Debug Data (*) | +---------------------------------------------------------------+
一端通知對端較爲優雅的方式中止建立流,同時還要完成以前已創建流的任務。
HTTP/2協議的擴展是容許存在的,在於提供額外服務。擴展包括: - 新類型幀,須要遵照通用幀格式 - 新的設置參數,用於設置新幀相關屬性 - 新的錯誤代碼,約定幀可能觸發的錯誤
當定義一個新幀,須要注意 1. 規範建議新的擴展須要通過雙方協商後才能使用 1. 在SETTINGS幀添加新的參數項,可在鏈接序言時發送給對端,或者適當機會發送 1. 雙方協商成功,可使用新的擴展
已知ALTSVC、BLOCKED屬於擴展幀。
服務器提供給客戶端當前可用的替代服務,相似於CNAME,客戶端不支持可用選擇忽略
+-----------------------------------------------+ | Length (24) | +---------------+---------------+---------------+ | 0xa (8) | 0x0 (8) | +-+-------------+---------------+-------------------------------+ |R| Stream Identifier (32) | +=+=============================+===============================+ | Origin-Len (16) | Origin? (*) ... +-------------------------------+-------------------------------+ | Alt-Svc-Field-Value (*) ... +---------------------------------------------------------------+
字段列表: - Origin-Len: 16比特位整數,說明了Origin字段字節數 - Origin: ASCII字符串表示替代服務 - Alt-Svc-Field-Value: 包含了Alt-Svc HTTP Header Field,長度=Length (24) - Origin-Len (16)
須要注意: - 中介設備不能轉發給客戶端,緣由就是中介自身替換處理,轉發正常的業務數據給客戶端就行
具體可參考:https://tools.ietf.org/html/draft-ietf-httpbis-alt-svc-06
一端告訴另外一端由於受到流量控制的做用有數據但沒法發送。
+-----------------------------------------------+ | Length (24) | +---------------+---------------+---------------+ | 0xb (8) | 0x0 (8) | +-+-------------+---------------+-------------------------------+ |R| Stream Identifier/0x0 (32) | +=+=============================================================+
以上記錄了HTTP/2幀基本結構,10個文檔定義的正式幀,以及額外的兩個擴展幀。
原文 http://www.blogjava.net/yongboy/archive/2015/03/20/423655.html