談談 HTTP/2 的協議協商機制

JerryQu發表於2016-04-16

在過去的幾個月裡,我寫了很多有關 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 協議升級,客戶端必須在請求頭部中指定這兩個欄位:

客戶端通過 Upgrade 頭部欄位列出所希望升級到的協議和版本,多個協議之間用 ,(0x2C, 0x20)隔開。除了這兩個欄位之外,一般每種新協議還會要求客戶端傳送額外的新欄位。

如果服務端不同意升級或者不支援 Upgrade 所列出的協議,直接忽略即可(當成 HTTP/1.1 請求,以 HTTP/1.1 響應);如果服務端統一升級,那麼需要這樣響應:

可以看到,HTTP Upgrade 響應的狀態碼是 101,並且響應正文可以使用新協議定義的資料格式。

如果大家之前使用過 WebSocket,應該已經對 HTTP Upgrade 機制有所瞭解。下面是建立 WebSocket 連線的 HTTP 請求:

這是服務端同意升級的 HTTP 響應:

在這之後,客戶端和服務端之間就可以使用 WebSocket 協議進行雙向資料通訊,跟 HTTP/1.1 沒關係了。可以看到,WebSocket 連線的建立就是典型的 HTTP Upgrade 機制。

顯然,這個機制也可以用做 HTTP/1.1 到 HTTP/2 的協議升級。例如:

在 HTTP Upgrade 機制中,HTTP/2 的協議名稱是 h2c,代表 HTTP/2 ClearText。如果服務端不支援 HTTP/2,它會忽略 Upgrade 欄位,直接返回 HTTP/1.1 響應,例如:

如果服務端支援 HTTP/2,那就可以迴應 101 狀態碼及對應頭部,並且在響應正文中可以直接使用 HTTP/2 二進位制幀:

以下是通過 HTTP Upgrade 機制將 HTTP/1.1 升級到 HTTP/2 的 Wireshark 抓包(兩張圖可以對照來看):

h2c_upgrade_success_wireshark

h2c_upgrade_success

根據 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,應用層協議協商)。二者的目標和實現原理基本一致,這裡只介紹後者。如圖:

h2_tls_alpn_client

可以看到,客戶端在建立 TLS 連線的 Client Hello 握手中,通過 ALPN 擴充套件列出了自己支援的各種應用層協議。其中,HTTP/2 協議名稱是 h2

h2_tls_alpn_server

如果服務端支援 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 資料,不走協商。

打賞支援我寫出更多好文章,謝謝!

打賞作者

打賞支援我寫出更多好文章,謝謝!

任選一種支付方式

談談 HTTP/2 的協議協商機制 談談 HTTP/2 的協議協商機制

相關文章