文章目錄
在過去的幾個月裡,我寫了很多有關 HTTP/2 的文章,也做過好幾場相關分享。我在向大家介紹 HTTP/2 的過程中,有一些問題經常會被問到。例如要部署 HTTP/2 一定要先升級到 HTTPS 麼?升級到 HTTP/2 之後,不支援 HTTP/2 的瀏覽器還能正常訪問麼?本文重點介紹 HTTP/2 的協商機制,明白了服務端和客戶端如何協商出最終使用的 HTTP 協議版本,這兩個問題就迎刃而解了。
HTTP Upgrade
為了更方便地部署新協議,HTTP/1.1 引入了 Upgrade 機制,它使得客戶端和服務端之間可以藉助已有的 HTTP 語法升級到其它協議。這個機制在 RFC7230 的「6.7 Upgrade」這一節中有詳細描述。
要發起 HTTP/1.1 協議升級,客戶端必須在請求頭部中指定這兩個欄位:
1 2 |
Connection: Upgrade Upgrade: protocol-name[/protocol-version] |
客戶端通過 Upgrade
頭部欄位列出所希望升級到的協議和版本,多個協議之間用 ,
(0x2C, 0x20)隔開。除了這兩個欄位之外,一般每種新協議還會要求客戶端傳送額外的新欄位。
如果服務端不同意升級或者不支援 Upgrade
所列出的協議,直接忽略即可(當成 HTTP/1.1 請求,以 HTTP/1.1 響應);如果服務端統一升級,那麼需要這樣響應:
1 2 3 4 5 |
HTTP/1.1 101 Switching Protocols Connection: upgrade Upgrade: protocol-name[/protocol-version] [... data defined by new protocol ...] |
可以看到,HTTP Upgrade 響應的狀態碼是 101
,並且響應正文可以使用新協議定義的資料格式。
如果大家之前使用過 WebSocket,應該已經對 HTTP Upgrade 機制有所瞭解。下面是建立 WebSocket 連線的 HTTP 請求:
1 2 3 4 5 6 7 |
GET ws://example.com/ HTTP/1.1 Connection: Upgrade Upgrade: websocket Origin: http://example.com Sec-WebSocket-Version: 13 Sec-WebSocket-Key: d4egt7snxxxxxx2WcaMQlA== Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits |
這是服務端同意升級的 HTTP 響應:
1 2 3 4 |
HTTP/1.1 101 Switching Protocols Connection: Upgrade Upgrade: websocket Sec-WebSocket-Accept: gczJQPmQ4Ixxxxxx6pZO8U7UbZs= |
在這之後,客戶端和服務端之間就可以使用 WebSocket 協議進行雙向資料通訊,跟 HTTP/1.1 沒關係了。可以看到,WebSocket 連線的建立就是典型的 HTTP Upgrade 機制。
顯然,這個機制也可以用做 HTTP/1.1 到 HTTP/2 的協議升級。例如:
1 2 3 4 5 |
GET / HTTP/1.1 Host: example.com Connection: Upgrade, HTTP2-Settings Upgrade: h2c HTTP2-Settings: |
在 HTTP Upgrade 機制中,HTTP/2 的協議名稱是 h2c
,代表 HTTP/2 ClearText。如果服務端不支援 HTTP/2,它會忽略 Upgrade
欄位,直接返回 HTTP/1.1 響應,例如:
1 2 3 4 5 |
HTTP/1.1 200 OK Content-Length: 243 Content-Type: text/html ... |
如果服務端支援 HTTP/2,那就可以回應 101
狀態碼及對應頭部,並且在響應正文中可以直接使用 HTTP/2 二進位制幀:
1 2 3 4 5 |
HTTP/1.1 101 Switching Protocols Connection: Upgrade Upgrade: h2c [ HTTP/2 connection ... ] |
以下是通過 HTTP Upgrade 機制將 HTTP/1.1 升級到 HTTP/2 的 Wireshark 抓包(兩張圖可以對照來看):
根據 HTTP/2 協議中的描述,額外補充幾點:
- 41 號包中,客戶端發起的協議升級請求中,必須通過
HTTP2-Settings
指定一個經過 Base64 編碼過的 HTTP/2 SETTINGS 幀; - 45 號包中,服務端同意協議升級,響應正文中必須包含 HTTP/2 SETTING 幀(二進位制格式,不需要 Base64 編碼);
- 62 號包中,客戶端可以開始傳送各種 HTTP/2 幀,但第一個幀必須是 Magic 幀(內容固定為 PRI * HTTP/2.0rnrnSMrnrn),做為協議升級的最終確認;
HTTP Upgrade 機制本身沒什麼問題,但很容易受網路中間環節影響。例如不能正確處理 Upgrade
頭部的代理節點,很可能造成最終升級失敗。之前我們統計過 WebSocket 的連通情況,發現大量明明支援 WebSocket 的瀏覽器卻無法升級,只能使用降級方案。
ALPN 擴充套件
HTTP/2 協議本身並沒有要求它必須基於 HTTPS(TLS)部署,但是出於以下三個原因,實際使用中,HTTP/2 和 HTTPS 幾乎都是捆綁在一起:
- HTTP 資料明文傳輸,資料很容易被中間節點窺視或篡改,HTTPS 可以保證資料傳輸的保密性、完整性和不被冒充;
- 正因為 HTTPS 傳輸的資料對中間節點保密,所以它具有更好的連通性。基於 HTTPS 部署的新協議具有更高的連線成功率;
- 當前主流瀏覽器,都只支援基於 HTTPS 部署的 HTTP/2;
如果前面兩個原因還不足以說服你,最後這個絕對有說服力,除非你的 HTTP/2 服務只打算給自己客戶端用。
下面介紹在 HTTPS 中,瀏覽器和服務端之間怎樣協商是否使用 HTTP/2。
基於 HTTPS 的協議協商非常簡單,多了 TLS 之後,雙方必須等到成功建立 TLS 連線之後才能傳送應用資料。而要建立 TLS 連線,本來就要進行 CipherSuite 等引數的協商。引入 HTTP/2 之後,需要做的只是在原本的協商機制中把對 HTTP 協議的協商加進去。
Google 在 SPDY 協議中開發了一個名為 NPN(Next Protocol Negotiation,下一代協議協商)的 TLS 擴充套件。隨著 SPDY 被 HTTP/2 取代,NPN 也被官方修訂為 ALPN(Application Layer Protocol Negotiation,應用層協議協商)。二者的目標和實現原理基本一致,這裡只介紹後者。如圖:
可以看到,客戶端在建立 TLS 連線的 Client Hello 握手中,通過 ALPN 擴充套件列出了自己支援的各種應用層協議。其中,HTTP/2 協議名稱是 h2
。
如果服務端支援 HTTP/2,在 Server Hello 中指定 ALPN 的結果為 h2
就可以了;如果服務端不支援 HTTP/2,從客戶端的 ALPN 列表中選一個自己支援的即可。
並不是所有 HTTP/2 客戶端都支援 ALPN,理論上建立 TLS 連線後,依然可以再通過 HTTP Upgrade 進行協議升級,只是這樣會額外引入一次往返。
小結
看到這裡,相信你一定可以很好地回答本文開頭提出的問題。
HTTP/2 需要基於 HTTPS 部署是當前主流瀏覽器的要求。如果你的 HTTP/2 服務要支援瀏覽器訪問,那就必須基於 HTTPS 部署;如果只給自己客戶端用,可以不部署 HTTPS(這個頁面列舉了很多支援 h2c
的 HTTP/2 服務端、客戶端實現)。
支援 HTTP/2 的 Web Server 基本都支援 HTTP/1.1。這樣,即使瀏覽器不支援 HTTP/2,雙方也可以協商出可用的 HTTP 版本,沒有相容性問題。如下表:
瀏覽器 | 伺服器 | 協商結果 |
---|---|---|
不支援 HTTP/2 | 不支援 HTTP/2 | 不協商,使用 HTTP/1.1 |
不支援 HTTP/2 | 支援 HTTP/2 | 不協商,使用 HTTP/1.1 |
支援 HTTP/2 | 不支援 HTTP/2 | 協商,使用 HTTP/1.1 |
支援 HTTP/2 | 支援 HTTP/2 | 協商,使用 HTTP/2 |
當然,本文討論的是通用情況。對於自己實現的客戶端和服務端,如果打算使用 HTTP/2 ClearText,由於 HTTP Upgrade 協商會增加一次往返,可以要求雙方必須支援 HTTP/2,直接傳送 HTTP/2 資料,不走協商。
打賞支援我寫出更多好文章,謝謝!
打賞作者
打賞支援我寫出更多好文章,謝謝!
任選一種支付方式