HTTP/2 是一個二進制協議,這也就意味著它的可讀性幾乎為 0,但幸運的是,我們還是有很多工具,譬如 Wireshark, 能夠將其解析出來。
在了解 HTTP/2 之前,需要知道一些通用術語:
- Stream: 一個雙向流,一條連接可以有多個 streams
- Message: 也就是邏輯上面的 request,response
- Frame::數據傳輸的最小單位。每個 Frame 都屬于一個特定的 stream 或者整個連接。一個 message 可能有多個 frame 組成
Frame
是 HTTP/2 里面最小的數據傳輸單位,一個 Frame 定義如下
+-----------------------------------------------+
| Length (24) |
+---------------+---------------+---------------+
| Type (8) | Flags (8) |
+-+-------------+---------------+-------------------------------+
|R| Stream Identifier (31) |
+=+=============================================================+
| Frame Payload (0...) ...
+---------------------------------------------------------------+
- Length:也就是 Frame 的長度,默認最大長度是 16KB,如果要發送更大的 Frame,需要顯式的設置 max frame size
- Type:Frame 的類型,譬如有 DATA,HEADERS,PRIORITY 等
- Flag 和 R:保留位,可以先不管
- Stream Identifier:標識所屬的 stream,如果為 0,則表示這個 frame 屬于整條連接
- Frame Payload:根據不同 Type 有不同的格式
Multiplexing
HTTP/2 通過 stream 支持了連接的多路復用,提高了連接的利用率。Stream 有很多重要特性:
- 一條連接可以包含多個 streams,多個 streams 發送的數據互相不影響
- Stream 可以被 client 和 server 單方面或者共享使用
- Stream 可以被任意一段關閉
- Stream 會確定好發送 frame 的順序,另一端會按照接受到的順序來處理
- Stream 用一個唯一 ID 來標識
- 這里在說一下 Stream ID,如果是 client 創建的 stream,ID 就是奇數,如果是 server 創建的,ID 就是偶數。ID 0x00 和 0x01 都有特定的使用場景。
- Stream ID 不可能被重復使用,如果一條連接上面 ID 分配完了,client 會新建一條連接。而 server 則會給 client 發送一個 GOAWAY frame 強制讓 client 新建一條連接。
- 為了更大的提高一條連接上面的 stream 并發,可以考慮調大 SETTINGS_MAX_CONCURRENT_STREAMS
Priority
因為一條連接允許多個 streams 在上面發送 frame,那么在一些場景下面,我們還是希望 stream 有優先級,方便對端為不同的請求分配不同的資源。譬如對于一個 Web 站點來說,優先加載重要的資源,而對于一些不那么重要的圖片啥的,則使用低的優先級
我們還可以設置 Stream Dependencies,形成一棵 streams priority tree。假設 Stream A 是 parent,Stream B 和 C 都是它的孩子,B 的 weight 是 4,C 的 weight 是 12,假設現在 A 能分配到所有的資源,那么后面 B 能分配到的資源只有 C 的 1/3
Flow Control
HTTP/2 也支持流控,如果 sender 端發送數據太快,receiver 端可能因為太忙,或者壓力太大,或者只想給特定的 stream 分配資源,receiver 端就可能不想處理這些數據。譬如,如果 client 給 server 請求了一個視頻,但這時候用戶暫停觀看了,client 就可能告訴 server 別在發送數據了
雖然 TCP 也有 flow control,但它僅僅只對一個連接有效果。HTTP/2 在一條連接上面會有多個 streams,有時候,我們僅僅只想對一些 stream 進行控制,所以 HTTP/2 單獨提供了流控機制。Flow control 有如下特性:
- Flow control 是單向的。Receiver 可以選擇給 stream 或者整個連接設置 window size
- Flow control 是基于信任的。Receiver 只是會給 sender 建議它的初始連接和 stream 的 flow control window size
- Flow control 不可能被禁止掉。當 HTTP/2 連接建立起來之后,client 和 server 會交換 SETTINGS frames,用來設置 flow control window size
- Flow control 是 hop-by-hop,并不是 end-to-end 的,也就是我們可以用一個中間人來進行 flow control
這里需要注意,HTTP/2 默認的 window size 是 64 KB,實際這個值太小了,可以直接設置成 1 GB
HPACK
在一個 HTTP 請求里面,我們通常在 header 上面攜帶很多該請求的元信息,用來描述要傳輸的資源以及它的相關屬性。在 HTTP/1.x 時代,我們采用純文本協議,并且使用 \r\n 來分隔,如果我們要傳輸的元數據很多,就會導致 header 非常的龐大。另外,多數時候,在一條連接上面的多數請求,其實 header 差不了多少,譬如我們第一個請求可能 GET /a.txt,后面緊接著是 GET /b.txt,兩個請求唯一的區別就是 URL path 不一樣,但我們仍然要將其他所有的 fields 完全發一遍
HTTP/2 為了解決這個問題,使用了 HPACK。
HPACK 提供了一個靜態和動態的 table,靜態 table 定義了通用的 HTTP header fields,譬如 method,path 等。發送請求的時候,只要指定 field 在靜態 table 里面的索引,雙方就知道要發送的 field 是什么了
對于動態 table,初始化為空,如果兩邊交互之后,發現有新的 field,就添加到動態 table 上面,這樣后面的請求就可以跟靜態 table 一樣,只需要帶上相關的 index 就可以了