HTTP2 是一個超文字傳輸協定,它是 HTTP 協定的第二個版本。HTTP2 主要是基於 google 的 SPDY 協定,SPDY 的關鍵技術被 HTTP2 採納了,因此 SPDY 的成員全程參與了 HTTP2 協定製定過程。
HTTP2 是由網際網路工程任務組 (IETF) 的 httpbis(Hypertext Transfer Protocol Bis) 工作小組開發。
多數瀏覽器在 2015 年底支援了該協定。
HTTP 1.1 包含很多的細節設計,也預留了很多未來擴充套件選項。所以沒有軟體實現了協定中提及的全部細節。
比如 Head 頭協定內容也很多,有的相似性還比較高。
隨著網際網路發展,有一些不常用的功能後來慢慢被用上。比如 HTTP Pipelining,可以提高傳輸速度。
HTTP1.1 很難充分利用 TCP 提供的效能。HTTP使用者端和瀏覽器就要找其它方法來解決頁面載入時間。
隨著網際網路的發展,傳輸資源的數量和 size 大小都增加了很多。比如很多網站首頁需要下載的資源數量明顯增加了很多,下載慢的話,渲染頁面時間就會增加,導致使用者體驗下降。HTTP 1.1 也提出了一種解決方案 - HTTP Pipelining 技術。
HTTP Pipelining 把多個 HTTP 請求放到一個 TCP 連線中來傳送,傳送過程中不需要伺服器對前一個情趣的響應。但是在使用者端,還是會按照傳送的順序來接收響應請求。這就導致一個問題,就是 Head-of-line blocking。
在 2.3 小結中簡單介紹了 HTTP Pipelining,這個技術也導致了一個問題 Head-of-line blocking。
HTTP 1.1 利用 pipelining 可以同時在一個 TCP 中傳送多個 HTTP 請求,但是使用者端接收伺服器端響應資訊時,還是按照傳送時的順序來接收響應資訊。
這會導致一個什麼問題呢?
使用者端接收時,如果第一個響應資訊慢,會導致後面的響應資訊阻塞。因為 http1.1 以前協定規定是一發一收這種模式,相當於一個先進先出的序列佇列。伺服器端為了按序返回響應資訊也會佔用很多伺服器資源。
比如一些資源傳送到伺服器:js、css,伺服器不能對他們進行分塊解析,需要解析完 js 後,才會去解析 css 資源,這中解析方式肯定是一種低效率的解析方式,需要改進。
上面就是 http1.1 協定遇到的一些主要問題。
HTTP 頭欄位多,所以採用壓縮演演算法來進行壓縮大小傳輸。
這就與 HTTP2 協定設計有關,主要涉及 3 個概念,流、訊息、幀。
流:stream,存在於 TCP 連線中的一個虛擬連線通道。每個流通道都有一個整數 ID 標識,流還可以承載雙向訊息。
訊息:message,它是由資料框構成。
幀:data frame,HTTP2 中構成訊息的最小單位。訊息有一個或多個幀構成。資料框中有 1 個關鍵資料,這個幀屬於哪個資源。也就是解析後可以直接組成資源。
下面章節內容會詳細介紹 HTTP2 協定內容。
也是跟 HTTP2 協定設計相關。
HTTP2 的一些其他特性:
演示地址:https://http2.akamai.com/demo
HTTP2 是基於 HTTP 語意,它提供了一種優化傳輸機制。HTTP2 支援 HTTP1.1 所有核心特性,HTTP2 從以下幾個方面進行了改進:
HTTP2 協定有兩個識別符號:
因為 HTTP1.1 協定會存在很長時間,所以怎麼把 HTTP1.1 協定升級到 HTTP2 協定就很關鍵了。
使用者端發起一個 http URI 請求時,如果事前不知道下一跳是否支援 HTTP2,需要使用 HTTP Upgrade 機制。使用者端發起一個 HTTP1.1 請求,其中包含 "h2c" 的 Upgrade 首部欄位,該請求還必須包含一個 HTTP2-Settings 首部欄位。
例如:
GET / HTTP/1.1
Host: server.example.com
Connection: Upgrade, HTTP2-Settings
Upgrade: h2c
HTTP2-Settings: <base64url encoding of HTTP/2 SETTINGS payload>
如果伺服器不同意升級或者不支援 Upgrade 升級,可以直接忽略,當成是 HTTP1.1 請求和響應就好了。
如果伺服器同意升級,響應格式為:
HTTP/1.1 101 Switching Protocols
Connection: Upgrade
Upgrade: h2c
[ HTTP/2 connection ...
HTTP 響應升級的狀態碼是 101(Switching Protocols)。在結束 101 響應的空行後,伺服器端可以開始傳送 HTTP2 資料框了。
更多詳細內容請檢視 RFC7540: https://www.rfc-editor.org/rfc/rfc7540
以上都是沒有涉及到 HTTPS ,現在 HTTP2 幾乎都是用到了 HTTPS(TLS),這個又怎麼升級呢?
前面講到,執行在 TLS 之上的 HTTP2 協定標識為 「h2」,使用者端不能傳送 「h2c" 協定標識,伺服器端自然也不能選擇 "h2c" 協定響應。
多了 TLS 之後,使用者端和伺服器端雙方需等到成功建立 TLS 連線之後才能傳送應用資料。而建立 TLS 連線,雙方本來就要進行協商,引入 HTTP2 之後,要做的就是在原來協商機制中把 HTTP2 的協商機制加進去。
HTTP2 是基於 Google 的 SPDY 協定開發的,在 SPDY 中開發了一個名為 NPN (Next Protocol Negotiation,下一代協定協商)的 TLS 擴充套件協定。而在 HTTP2 中,NPN 被 HTTP2 修改為 ALPN(Application Layer Protocol Negotiation,應用層協定協商),它也是 TLS 的擴充套件協定。也就是說構築在 TLS 上的 HTTP2,使用 ALPN 擴充套件協定進行協商。
Client Server
ClientHello -------->
ServerHello
Certificate*
ServerKeyExchange*
CertificateRequest*
<-------- ServerHelloDone
Certificate*
ClientKeyExchange
CertificateVerify*
[ChangeCipherSpec]
Finished -------->
[ChangeCipherSpec]
<-------- Finished
Application Data <-------> Application Data
(Message flow for a full handshake,https://www.rfc-editor.org/rfc/rfc5246#section-7.3,打 * 號都是可選項)
在 RFC5246 中 7.4.1.2 小結的 TLS 的 Client Hello 結構:
struct {
ProtocolVersion client_version;
Random random;
SessionID session_id;
CipherSuite cipher_suites<2..2^16-2>;
CompressionMethod compression_methods<1..2^8-1>;
select (extensions_present) {
case false:
struct {};
case true:
Extension extensions<0..2^16-1>;
};
} ClientHello;
上面的 Extension 在 7.4.1.4 Hello Extensions 中有定義:
struct {
ExtensionType extension_type;
opaque extension_data<0..2^16-1>;
} Extension;
enum {
signature_algorithms(13), (65535)
} ExtensionType;
Here:
- "extension_type" identifies the particular extension type.
- "extension_data" contains information specific to the particular
extension type.
在 HTTP2 的 APNE 擴充套件結構在 rfc7301 中第 3 小結也有定義:
A new extension type ("application_layer_protocol_negotiation(16)")
is defined and MAY be included by the client in its "ClientHello"
message.
enum {
application_layer_protocol_negotiation(16), (65535)
} ExtensionType;
The "extension_data" field of the
("application_layer_protocol_negotiation(16)") extension SHALL
contain a "ProtocolNameList" value.
opaque ProtocolName<1..2^8-1>;
struct {
ProtocolName protocol_name_list<2..2^16-1>
} ProtocolNameList;
"ProtocolNameList" contains the list of protocols advertised by the
client, in descending order of preference. Protocols are named by
IANA-registered,opaque, non-empty byte strings, as described further
in Section 6 ("IANA Considerations") of this document
更多資訊請檢視這裡:https://www.rfc-editor.org/rfc/rfc5246
HTTP2 協定是由 HTTP1.1 升級而來,HTTP 的語意不變,提供的功能沒有變化,HTTP方法、狀態碼、URI和Header欄位這些都沒有變化。在 HTTP2 中,傳輸資料時編碼是不同的,與換行符分隔文字的 HTTP1.1 協定不同,HTTP2 中資料交換都被拆分為更小的訊息和幀,而每個訊息和幀都是用二進位制格式來編碼。幀是 HTTP2 中最小資料單元。
幀 frame:HTTP2 中最小通訊資料單元,每個幀至少包含了一個標識(stream identifier,簡稱stream id)該幀所屬的流。
訊息 message:訊息由一個或多個幀組成。例如請求的訊息和響應的訊息。
流 stream:存在於 HTTP2 連線中的一個「虛擬連線通道「,它是一個邏輯概念。流可以承載雙向位元組流,及是使用者端和伺服器端可以進行雙向通訊的位元組序列。每個流都有一個唯一的整數 ID(stream identifier) 標識,由發起流的一端分配給流。
單個 HTTP2 連線可以包含多個同時開啟的流,任何一個端點(使用者端和伺服器端)都可以將多個流的訊息進行傳輸。這也是多路複用關鍵所在。一個 TCP 連線(HTTP2 連線建立在 TCP 連線之上)裡可以傳送若干個流(stream),每個流中可以傳輸若干條訊息(message),每條訊息由若干二進位制幀(frame)組成。
任何一端都可以關閉流。在流上傳送訊息的順序很重要,最後接收端會把 Stream Identifier (同一個流) 相同的幀重新組裝成完整的訊息報文。特別是 HEADERS 幀和 DATA 幀的順序在語意上非常重要。
HTTP2 中連線Connection、流Stream、訊息Message、幀Frame的關係示意圖如下:
(來自:https://hpbn.co/http2)
(來自:https://hpbn.co/http2)
從上圖可以看出,HTTP1.1 是明文文字,而 HTTP2.0 首部(HEADERS)和資料訊息主體(DATA)都是幀(frame)。frame 是 HTTP2 協定中最小資料傳輸單元。
一旦建立了 HTTP2 連線,端點(endpoints)間就可以開始交換幀資料。
所有的幀資料都是以一個固定的 9 位元組開頭(Frame Payload之前),後面跟一個可變長度的有效負載Frame Payload,這個可變長度的長度值由欄位 Length 來表示。
幀的格式:
+-----------------------------------------------+
| Length (24) |
+---------------+---------------+---------------+
| Type (8) | Flags (8) |
+-+-------------+---------------+-------------------------------+
|R| Stream Identifier (31) |
+=+=============================================================+
| Frame Payload (0...) ...
+---------------------------------------------------------------+
(from:https://www.rfc-editor.org/rfc/rfc7540.html#section-4.1)
另外的一種表示法,這種表示方法感覺跟編碼形式比較接近:
HTTP Frame {
Length (24),
Type (8),
Flags (8),
Reserved (1),
Stream Identifier (31),
Frame Payload (..),
}
(來自:https://httpwg.org/specs/rfc9113.html)
Length:24 個 bit 的無符號整數,用來表示 Frame Payload 的長度佔用位元組數。這個 Length 值的範圍為 0 - 16384(2^14)。觸發接收方設定了一個更大的值 SETTINGS_MAX_FRAME_SIZE 。
幀頭的 9 位元組不包含在這個 Length 值中。
Type:定義 Frame 的型別,8 bits 表示。幀型別決定了幀的格式和語意。實現的話必須忽略或拋棄未知型別的幀。
Flags:為幀Frame型別保留的 8 bit 的布林值,這個標誌用於特定的幀Frame型別語意。如果這個欄位沒有被標識為特定幀型別語意,那麼接收時必須被忽略,並且傳送時不設定(0x0)。
R(Reserved):一個保留的 1 bit 欄位,這個欄位語意未定義。傳送時必須保持未設定(0x0),接收時忽略。
Stream Identifier:流標識,31 bit 的無符號整數。值 0x0 保留給與整個連線相關聯的幀,而不是單個流。
Frame Payload:內容主體,由幀的型別決定。
上一小節的 Type 欄位,即幀型別,在 HTTP2 中共分為 10 種型別:
每一種型別幀都由一個 8 位型別程式碼來識別。每種幀型別在建立和管理整個連線或單個資料流時都有不同的作用,
Type | Code(Type 值) | 說明 |
---|---|---|
DATA | 0x00 | 資料框(type=0x00),內容主題資訊Frame Payload。比如一個或多個 DATA 幀可用於傳輸請求或響應的資訊內容 |
HEADERS | 0x01 | 頭幀(type=0x01),用於開啟一個流,另外還攜帶一個首部的塊片段 |
PRIORITY | 0x02 | 優先順序幀(type=0x02),在 rfc9113 中這個欄位已棄用。PRIORITY 幀可以在任何流狀態下傳送,包括空閒或關閉的流 |
RST_STREAM | 0x03 | 流終止幀(type=0x03),允許立即終止一個流。傳送 RST_STREAM 表示請求取消流或表明發生錯誤的情況 |
SETTINGS | 0x04 | 設定幀(type=0x04),設定兩端連線方式的設定引數 |
PUSH_PROMISE | 0x05 | 推播幀(type=0x05),伺服器端的推播,告訴對端打算推播資料給你了 |
PING | 0x06 | PING幀(type=0x06),確定一個空閒連線是否仍然可用,也可以測量端點間往返時間(RTT)。PING 幀可以從任意端點發出 |
GOAWAY | 0x07 | GOAWAY幀(type=0x07),用於發起關閉連線的請求,或發出嚴重錯誤的訊號。GOAWAY 允許端點優雅的停止接收新流,同時仍然完成對先前建立的流的處理。 |
WINDOW_UPDATE | 0x08 | WINDOW_UPDATE幀(type=0x08),用於實現流控。可以作用在單獨的某個流上(指定具體的 Stream Identifier ),也可以作用在整個連線上(Stream Identifier 為 0x0)。只有 DATA 幀會受到流控的影響 |
CONTINUATION | 0x08 | 延續幀 (type=0x9),用於繼續傳送首部塊片段位元組序列 |
(from:https://httpwg.org/specs/rfc9113.html)
說明:圖表中 x 符號表示該型別的幀的 flags 可以取的值
+---------------+
|Pad Length? (8)|
+---------------+-----------------------------------------------+
| Data (*) ...
+---------------------------------------------------------------+
| Padding (*) ...
+---------------------------------------------------------------+
(DATA Frame Payload,https://www.rfc-editor.org/rfc/rfc7540.html)
下面是來自 https://httpwg.org/specs/rfc9113.html 完整幀定義:
DATA Frame {
Length (24),
Type (8) = 0x00,
Unused Flags (4),
PADDED Flag (1),
Unused Flags (2),
END_STREAM Flag (1),
Reserved (1),
Stream Identifier (31),
[Pad Length (8)],
Data (..),
Padding (..2040),
}
這個 DATA Frame 裡欄位,Length,Type,Unused Flags,Reserved 和 Stream Identifier 在 5.4.3 小節有介紹。下面介紹 DATA Frame 裡的額外欄位:
DATA 幀的標識有:
+---------------+
|Pad Length? (8)|
+-+-------------+-----------------------------------------------+
|E| Stream Dependency? (31) |
+-+-------------+-----------------------------------------------+
| Weight? (8) |
+-+-------------+-----------------------------------------------+
| Header Block Fragment (*) ...
+---------------------------------------------------------------+
| Padding (*) ...
+---------------------------------------------------------------+
(HEADERS Frame Payload, https://www.rfc-editor.org/rfc/rfc7540.html)
來自 https://httpwg.org/specs/rfc9113.html HEADER 幀完整定義:
HEADERS Frame {
Length (24),
Type (8) = 0x01,
Unused Flags (2),
PRIORITY Flag (1),
Unused Flag (1),
PADDED Flag (1),
END_HEADERS Flag (1),
Unused Flag (1),
END_STREAM Flag (1),
Reserved (1),
Stream Identifier (31),
[Pad Length (8)],
[Exclusive (1)],
[Stream Dependency (31)],
[Weight (8)],
Field Block Fragment (..),
Padding (..2040),
}
主要看 HEADERS Frame Payload 這部分欄位,其它部分欄位在 5.4.3 小節有介紹。
HEADERS 幀的標識有:
除了上面介紹的 2 種幀型別格式結構,還有 8 種其他的幀型別格式,更多內容請檢視下面的 RFC:
在第四節概要性的介紹了 HTTP2 的一些特性,這一節詳細介紹下。
在 HTTP1.1 中,一個 HTTP 的資料傳輸需要建立一個 TCP 連線,雖然有 Pipleing 特性,但是又有對頭阻塞的問題。
在 HTTP2 中,在一個 TCP 連線中,可以發起多個 HTTP2 連線請求,而每個 HTTP2 連線中又可以發起多個流來傳輸資料。這都得益於 HTTP2 中資料格式的設計,最重要的流概念,在上面 5.3.1 小節中有介紹這個流的概念。
來看一看網路上的一張圖,HTTP1.1 和 HTTP2 的連線傳輸對比圖,原始出處我沒有找到:
從圖中可以看到在 HTTP1.1 中,請求 index.html 資源,響應完畢後就關閉連線了。
而在 HTTP2 中,請求完資源後,連線仍然是開啟的,後面還可以繼續使用這個連線通道傳輸資料。
上面是從資源傳輸的角度對比了 2 者的特性。
下面從 HTTP2 中 stream(流)角度來看看多路複用:
(來自:https://hpbn.co/http2/)
上圖可以看到在一個 HTTP2 connection 中,使用者端和伺服器端雙方都能夠向對方傳送多個流資料(stream 1、stream 3、stram 5),在 HTTP2 中用這個 stream ID 來標識幀和流的對應關係。
這也就是為什麼說流是多路複用中一個很重要的概念原因。
更多關於 stream流 和 multiplexing多路複用的內容,請檢視連結:https://httpwg.org/specs/rfc9113.html
HTTP 1.1 請求頭的協定內容很多,而且大部分都是重複的。在 HTTP1.1 中每次請求都會大量攜帶這種冗餘的頭資訊,浪費流量。
在 HTTP2 中,設計了 HPACK 壓縮演演算法對頭部協定內容進行壓縮傳輸,這樣不僅資料傳輸速度加快,也能節省網路流量。
HPACK 原理:
(來自 HTTP/2 is here, let's optimize)
更多內容檢視 RFC:https://www.rfc-editor.org/rfc/rfc7541.txt
伺服器端推播是一種在使用者端請求之前傳送資料的機制。在 HTTP2 中,伺服器可以對使用者端的一個請求傳送多個響應。除了對原始請求響應外,還可以向用戶端推播額外的資料。
伺服器端推播的目的是讓伺服器通過預測它收到請求後有哪些相關資源需要返回,從而減少資源請求往返次數。
比如在 HTML 頁面的請求後,通常是對該頁面應用的樣式表和指令碼的請求,當這些資源被伺服器端直接推播給使用者端時,使用者端就不需要單獨給伺服器傳送請求來獲取這些資源了。
在 https://hpbn.co/http2/ 中有一幅圖可以用來說明伺服器端推播機制:
(來自 https://hpbn.co/http2/)
在 page.html 檔案中包含資原始檔 script.js 和 style.css。使用者端向伺服器端請求 page.html 檔案,伺服器端發現 page.html 檔案中包含了這兩種資原始檔,就會把這兩種資原始檔推播給使用者端,以此來減少使用者端的請求次數。如上圖。
所有伺服器端推播資料流都由 PUSH_PROMISE 幀發起。
在實踐中,伺服器端推播很難有效使用。因為需要伺服器端正確預測使用者端發出的額外請求,預測必須同時考慮快取、內容協商和使用者行為等因素。預測錯誤又可能導致效能下降,因為伺服器端傳送了額外的資料。特別是推播了大量資料時可能與重要的響應資料發生線路爭用等問題。
使用者端可以請求禁用伺服器端推播,SETTINGS_ENABLE_PUSH 設定為 0 就可以了。
在 HTTP2 中,使用流來實現多路複用,這種會對 TCP 連線使用產生競爭,從而導致流傳輸被阻塞。流量控制是確保在同一連線上的流不會相互干擾。流量控制既能用於單個流也能用於整個連線。
HTTP2 使用 WINDOW_UPDATE 幀提供流量控制功能。
HTTP2 流控協定旨在不修改協定基礎上使用各種流控演演算法,流量控控的一些特點:
1、流量控制作用於連線。所有型別流控都是作用於兩個單跳的端點之間,而不是整個端到端的鏈路。
2、流量控制基於 WINDOW_UPDATE 幀實現。接收端公佈自己打算在每個流及整個連線上接收多少個八位位元組。這是一個基於信用的方案。
3、流量控制具有方向性,總體控制是由接收端提供的。接收端可以為每個流和整個連線設定它所需要的任何視窗大小。傳送端必需遵循接收端設定的流量控制限制。使用者端、伺服器端和中間設施,作為接收端均獨立宣告其流控視窗,並在傳送時同樣遵循其對等端設定的流控限制。
4、對於所有的流和連線,流量控制視窗的初始值大小為 65535 位元組。
5、幀型別也決定了是否要在這個幀上應用流控。對於在 rfc9113 檔案中提到的幀型別,只有 DATA 幀是流控的作用物件。其他所有幀均不佔用流控視窗空間,這確保了重要的控制幀不會被流控阻塞。
6、端點可以選擇禁用自己的流量控制,但端點不能忽略來自其對等端點的流量控制訊號。
7、HTTP2 中僅定義了 WINDOW_UPDATE 幀的格式和語意。在這篇檔案 rfc9113 中沒有規定接收端如何決定何時傳送此幀或傳送的值,也沒有規定傳送端如何選擇傳送封包。協定的實現者可以選擇任意一種符合其需求的演演算法。
更多內容請檢視這篇檔案:https://httpwg.org/specs/rfc9113.html.2
在像 HTTP2 這樣的使用了多路複用的協定中,為流分配頻寬和計算資源的優先次序對於實現良好的效能至關重要。糟糕的優先順序方案可能導致 HTTP2 協定糟糕的效能。
在 HTTP2 中,一個訊息可以拆分為多個單獨的幀,並且允許來自多個流的幀被多路複用,使用者端和伺服器端幀傳輸可能是亂序傳輸,所以優先順序順序就變成了一個關鍵效能考慮因素。還有一種重要情況是,當傳送受到限制情況下,可以通過優先順序順序選擇那個流傳輸幀。顯示設定過優先順序的流將被優先安排。但這種優先並不能保證一個優先順序高的流能得到優先順序處理或優先順序傳輸。所以說,優先順序僅僅作為一種建議存在。
HTTP2 允許每個流具有關聯的權重和依賴性:
更多流優先順序資訊,比如流的依賴關係、依賴權重、優先順序(依賴)重排、優先順序狀態管理等內容,請檢視:https://httpwg.org/specs/rfc9113.html.3
感覺有幫助的可以點個推薦
https://httpwg.org/specs/rfc9113.html HTTP/2 RFC,HTTP-WG(HTTP Working Group)
https://www.rfc-editor.org/rfc/rfc9113.html HTTP/2 RFC
https://hpbn.co/http2 High Performance Browser Networking
https://www.rfc-editor.org/rfc/rfc7540 Hypertext Transfer Protocol Version 2
https://httpwg.org/specs/rfc7540.htm Hypertext Transfer Protocol Version 2,Updated by: 9113
https://github.com/httpwg/http2-spec HTTP/2 spec
https://www.rfc-editor.org/info/rfc5246 The Transport Layer Security (TLS) Protocol Version 1.2,這個是 August 2008 編寫早期版本(https://www.rfc-editor.org/rfc/rfc5246),下面還有好多 Updated 的升級版本