[譯] HTTP/2 常見問題解答

僱個城管打天下發表於2019-02-06

以下是有關 HTTP/2 的常見問題解答。

一般問題

為什麼要修訂 HTTP?

HTTP/1.1 已經在 Web 上服役了十五年以上,但其劣勢也開始顯現。

載入一個網頁比以往更加耗費資源(詳見 HTTP Archive’s page size statistics)。與此同時,有效地載入所有這些靜態資源變得非常困難,因為事實上,HTTP 只允許每個 TCP 連線有一個未完成的請求。

在過去,瀏覽器使用多個 TCP 連線來發出並行請求。然而這種做法是有限制的。如果使用了太多的連線,就會產生相反的效果(TCP 擁塞控制將被無效化,導致的用塞事件將會損害效能和網路)。而且從根本上講這對其他程式來說也是不公平的(因為瀏覽器會佔用許多本不該屬於他的資源)。

同時,大量的請求意味著“線上上”有大量重複的資料。

這兩個因素都意味著 HTTP/1.1 請求有很多與之相關的開銷;如果請求太多,則會影響效能。

這使得業界在有哪些是最好的實踐上達成共識,它們包括,比如,Spriting(圖片合併)、data: inlining(資料內嵌)、Domain Sharding(域名分片)和 Concatenation(檔案合併)等。這些不規範的解決方案說明了協議本身存在一些潛在問題,並且在使用的時候會出現很多問題。

誰制定了 HTTP/2?

HTTP/2 是由 IETFHTTP 工作組開發的,該組織負責維護 HTTP 協議。該組織由眾多 HTTP 實現者、使用者、網路運營商和 HTTP 專家組成。

值得注意的是,雖然工作組的郵件列表託管在 W3C 網站上,不過這並不是 W3C 的功勞。但是, Tim Berners-Lee 和 W3C TAG與 WG 的進度保持一致。

許多人為這項工作做出了自己的貢獻,尤其是一些來自“大”專案的工程師,例如 Firefox、Chrome、Twitter、Microsoft 的 HTTP stack、Curl 和 Akamai。以及若干 Python、Ruby 和 NodeJS 的 HTTP 實現者。

為了更好的瞭解有關 IETF 的資訊,你可以訪問 Tao of the IETF;你也可以在 Github 的貢獻者圖表上檢視有哪些人為該專案做出了貢獻,同樣的,你也可以在 implementation list 上檢視誰正在參與該專案。

HTTP/2 與 SPDY 的關係是什麼?

HTTP/2 第一次出現並被討論的時候,SPDY 正得到廠商 (像 Mozilla 和 nginx)的青睞和支援,並被看成是 HTTP/1.x 基礎上的重大改善。

在不斷的徵求建議以及投票選擇之後,SPDY/2 被選為 HTTP/2 的基礎。從那時起,根據工作組的討論和使用者的反饋,它已經有了很多變化。

在整個過程中,SPDY 的核心開發人員參與了 HTTP/2 的開發,其中包括 Mike Belshe 和 Roberto Peon。

2015 年 2 月,谷歌宣佈計劃取消對 SPDY 的支援,轉而支援 HTTP/2。

究竟是 HTTP/2.0 還是 HTTP/2?

工作組決定刪除次要版本(“.0”),因為它在 HTTP/1.x 中造成了很多混亂。也就是說,HTTP 的版本僅代表它的相容性,不表示它的特性和“亮點”。

和 HTTP/1.x 相比 HTTP/2 的關鍵區別是什麼?

在高版本 HTTP/2 中:

  • 是二進位制的,代替原有的文字
  • 是多路複用的,代替原來的序列和阻塞機制
  • 所以可以在一個連線中並行處理
  • 壓縮頭部資訊減小開銷
  • 允許伺服器主動推送應答到客戶端的快取中

為什麼 HTTP/2 是二進位制的?

和 HTTP/1.x 這樣的文字協議相比,二進位制協議解析起來更高效、“線上”更緊湊,更重要的是錯誤更少。因為它們對如空白字元的處理、大小寫、行尾、空連結等的處理很有幫助。

舉個例子 ?,HTTP/1.1 定義了四種不同的方法來解析一條訊息;而在HTTP/2中,僅需一個程式碼路徑即可。

HTTP/2 在 telnet 中不可用,但是我們已經有一些工具可以提供支援,例如 Wireshark plugin

為什麼 HTTP/2 需要多路傳輸?

HTTP/1.x 有個問題叫“隊頭阻塞(head-of-line blocking)”,它是指在一次連線(connection)中,只提交一個請求的效率比較高,多了就會變慢。

HTTP/1.1 嘗試使用管線化(pipelining)來解決這個問題,但是效果並不理想(對於資料量較大或者速度較慢的響應,依舊會阻礙排在他後面的請求)。此外,由於許多網路媒介(intermediary)和伺服器不能很好的支援管線化,導致其部署起來也是困難重重。

這也就迫使客戶端使用一些啟發式的方法(基本靠猜)來決定通過哪些連線提交哪些請求;由於一個頁面載入的資料量,往往比可用連線能處理的資料量的 10 倍還多,對效能產生極大的負面影響,結果經常引起瀑布式阻塞(waterfall of blocked requests)。

而多路傳輸(Multiplexing)能很好的解決這些問題,因為它能同時處理多個訊息的請求和響應;甚至可以在傳輸過程中將一個訊息跟另外一個摻雜在一起。

所以在這種情況下,客戶端只需要一個連線就能載入一個頁面。

為什麼只需要一個 TCP 連線?

如果使用 HTTP/1,瀏覽器開啟每個點(origin)就需要 4 到 8 個連線(Connection)。而現在很多網站都使用多點傳輸(multiple origins),也就是說,光載入一個網頁,開啟的連線數量就超過 30 個。

一個應用同時開啟這麼多連線,已經遠遠超出了當初設計 TCP 時的預想;同時,因為每個連線都會響應大量的資料,使其可以造成網路快取溢位的風險,結果可能導致網路堵塞和資料重傳。

此外,使用這麼多連線還會強佔許多網路資源。這些資源都是從那些“遵紀守法”的應用那“偷”的(VoIP 就是個很好的例子)。

伺服器推送的好處是什麼?

當瀏覽器請求頁面時,伺服器傳送 HTML 作為響應,然後需要等待瀏覽器解析 HTML 併發出對所有嵌入資源的請求,然後才能開始傳送 JavaScript,影像和 CSS。

伺服器推送服務通過“推送”那些它認為客戶端將會需要的內容到客戶端的快取中,以此來避免往返的延遲。

但是,推送的響應並不是“萬金油”,如果使用不當,可能會損害效能。正確使用伺服器推送是一個長期的實驗及研究領域。

訊息頭為何需要壓縮?

來自 Mozilla 的 Patrick McManus 通過計算訊息頭對平均頁面負載的印象,對此進行了形象且充分的說明。

假定一個頁面有 80 個資源需要載入(這個數量對於今天的 Web 而言還是挺保守的),而每一次請求都有 1400 位元組的訊息頭(這同樣也並不少見,因為 Cookie 和引用等東西的存在),至少要 7 到 8 個來回去“線上”獲得這些訊息頭。這還不包括響應時間——那只是從客戶端那裡獲取到它們所花的時間而已。

這全都由於 TCP 的慢啟動機制,它根據可以確認的資料包數量對新連線上傳送資料的進行限制 — 這有效地限制了最初的幾次來回可以傳送的資料包數量。

相比之下,即使是頭部輕微的壓縮也可以是讓那些請求只需一個來回就能搞定——有時候甚至一個包就可以了。

這些額外的開銷是相當多的,特別是當你考慮對移動客戶端的影響的時候。這些往返的延遲,即使在網路狀況良好的情況下,也高達數百毫秒。

為什麼選擇 HPACK?

SPDY/2 提出在每一方都使用一個單獨的 GZIP 上下文用於訊息頭壓縮,這實現起來很容易,也很高效。

從那時起,一個重要的攻擊方式 CRIME 誕生了,這種方式可以攻擊加密檔案內部的所使用的壓縮流(如GZIP)。

使用 CRIME,那些具備向加密資料流中注入資料能力的攻擊者獲得了“探測”明文並還原的可能性。因為是 Web,JavaScript 使其成為了可能,而且已經有了通過對受到 TLS 保護的 HTTP 資源的使用CRIME來還原出 cookies 和認證令牌(Toekn)的案例。

因此,我們不應該使用 GZIP 進行壓縮。由於找不到其它適合在這種用例下使用的安全有效的演算法,所以我們創造了一種新的,針對訊息頭的,進行粗粒度操作的壓縮模式;因為HTTP訊息頭並不常常需要改變,我們仍然可以得到很好的壓縮效率,而且更加的安全。

HTTP/2 可以讓 cookies(或者其他訊息頭)變得更好嗎?

這一努力被許可在網路協議的一個修訂版本上執行 – 例如,HTTP 訊息頭、方法等等如何才能在不改變 HTTP 語義的前提下放到“網路上”。

這是因為 HTTP 的應用非常廣泛。如果我們使用了這個版本的 HTTP,它就會引入一種新的狀態機制(例如之前討論過的例子)或者改變其核心方法(幸好,這還沒有發生過),這可能就意味著新的協議將不會相容現有的 Web 內容。

具體地,我們是想要能夠從 HTTP/1 轉移到 HTTP/2,並且不會有資訊的丟失。如果我們開始”清理”訊息頭(大多數人都認為現在的 HTTP 訊息頭簡直是一團糟),我們就不得不去面對現有 Web 的諸多問題。

這樣做只會對新協議的普及造成麻煩。

總而言之,工作組 會對所有的 HTTP 負責,而不僅僅只是 HTTP/2。因此,我們才可以在版本獨立的新機制下運作,只要它們也能同現有的網路向下相容。

非瀏覽器使用者的 HTTP 是什麼樣的?

如果非瀏覽器應用已經使用過 HTTP 的話,那他們也應該可以使用 HTTP/2。

先前收到過 HTTP “APIs” 在 HTTP/2 中具有良好效能等特點這樣的反饋,那是因為 API 的設計不需要考慮類似請求開銷這樣一些事情。

話雖如此,我們正在考慮的改進重點是典型的瀏覽用例,因為這是協議主要的使用場景。

我們的章程裡面是這樣說的:

正在組織的規範需要滿足現在已經普遍部署了的 HTTP 的功能要求;具體來說主要包括,Web 瀏覽(桌面端和移動端),非瀏覽器(“HTTP APIs” 形式的),Web 服務(大範圍的),還有各種網路中介(藉助代理,企業防火牆,反向代理以及內容分發網路實現的)。同樣的,對 HTTP/1.x 當前和未來的語義擴充套件 (例如,訊息頭,方法,狀態碼,快取指令) 都應該在新的協議中支援。

值得注意的是,這裡沒有包括將 HTTP 用於非特定行為所依賴的場景中(例如超時,連線狀態以及攔截代理)。這些可能並不會被最終的產品啟用。

HTTP/2 需要加密嗎?

不需要。在激烈的討論後,工作組沒有就新協議是否使用加密(如 TLS)而達成共識。

不過,有些觀點認為只有在加密連線上使用時才會支援 HTTP/2,而目前還沒有瀏覽器支援未加密的 HTTP/2。

HTTP/2 是怎麼提高安全性的呢?

HTTP/2 定義了所需的 TLS 文件,包括版本,密碼套件黑名單和使用的擴充套件。

細節詳見相關規範

還有對於一些額外機制的討論,例如對 HTTP:// URLs(所謂的“機會主義加密”)使用 TLS;詳見 RFC 8164

我現在可以使用 HTTP/2 嗎?

瀏覽器中,最新版本的 Edge、Safari、Firefox 和 Chrome都支援 HTTP/2。其他基於 Blink 的瀏覽器也將支援HTTP/2(例如 Opera 和 Yandex 瀏覽器)。詳見 caniuse

還有幾個可用的伺服器(包括來自 AkamaiGoogleTwitter 的主要站點的 beta 支援),以及許多可以部署和測試的開源實現。

有關詳細資訊,請參閱實現列表

HTTP/2 將會取代 HTTP/1.x 嗎?

工作組的目的是讓那些使用 HTTP/1.x 的人也可以使用 HTTP/2,並能獲得 HTTP/2 所帶來的好處。他們說過,由於人們部署代理和伺服器的方式不同,我們不能強迫整個世界進行遷移,所以 HTTP/1.x 仍有可能要使用了一段時間。

HTTP/3 會出現嗎?

如果通過 HTTP/2 引入的溝通協作機制執行良好,支援新版本的 HTTP 就會比過去更加容易。

實現過程中的問題

為什麼規則會圍繞訊息頭幀的資料接續?

資料接續的存在是由於一個值(例如 cookie)可以超過 16kb,這意味著它不可能全部裝進一個幀裡面。

所以就決定以最不容易出錯的方式讓所有的訊息頭資料以一個接一個幀的方式傳遞,這樣就使得對訊息頭的解碼和緩衝區的管理變得更加容易。

HPACK 狀態的最小和最大尺寸是多少?

接收一方總是會控制 HPACK 中記憶體的使用量, 並且最小能設定到 0,最大則要看 SETTING 幀中能表示的最大整型數是多少,目前是 2^32 - 1。

我怎樣才能避免保持 HPACK 狀態?

傳送一個 SETTINGS 幀,將狀態尺寸(SETTINGS_HEADER_TABLE_SIZE)設定到 0,然後 RST 所有的流,直到一個帶有 ACT 設定位的 SETTINGS 幀被接收。

為什麼會有一個單獨的壓縮/流程控制上下文?

簡單說一下。

原來的提案裡面提到了流分組這個概念,它可以共享上下文,進行流控制等等。儘管那樣有利於代理(也有利於使用者體驗),但是這樣做相應也會增加一點複雜度。所以我們就決定先以一個簡單的東西開始,看看它會有多糟糕的問題,並且在未來的協議版本中解決這些問題(如果有的話)。

為什麼在 HPACK 中有 EOS 的符號?

由於 CPU 效率和安全的原因,HPACK 的霍夫曼編碼填充了霍夫曼編碼字串的下一個位元組邊界。因此對於任何特定的字串可能需要 0-7 個位元的填充。

如果單獨考慮霍夫曼解碼,任何比所需要的填充長的符號都可以正常工作。但是,HPACK 的設計允許按位元組對比霍夫曼編碼的字串。通過填充 EOS 符號需要的位元,我們確保使用者在做霍夫曼編碼字串位元組級比較時是相等的。反之,許多 headers 可以在不需要霍夫曼解碼的情況下被解析。

實現 HTTP/2 的時候我可以不用去實現 HTTP/1.1 嗎?

通常/大部分時候可以。

對於執行在 TLS(h2)之上的 HTTP/2 而言,如果你沒有實現 http1.1 的 ALPN 標識,那你就不需要支援任何 HTTP/1.1 的特性。

對於執行在 TCP(h2c)之上的 HTTP/2 而言,你需要實現最初始的升級(Upgrade)請求。

只支援 h2c 的客戶端需要生成一個針對 OPTIONS 的請求,因為 “*” 或者一個針對 “/” 的 HEAD 請求,他們相當安全,並且也很容易構建。僅僅只希望實現 HTTP/2 的客戶端應當把沒有帶上 101 狀態碼的 HTTP/1.1 響應看做錯誤處理。

只支援 h2c 的伺服器可以使用一個固定的 101 響應來接收一個包含升級(Upgrade)訊息頭欄位的請求。沒有 h2c 的升級令牌的請求可以使用一個包含了 Upgrade 訊息頭欄位的 505(HTTP 版本不支援)狀態碼來拒絕。那些不希望處理 HTTP/1.1 響應的伺服器應該在傳送了帶有鼓勵使用者在升級了的 HTTP/2 連線上重試的連線序言之後立即用帶有 REFUSED_STREAM 錯誤碼拒絕該請求的第一份資料流.

5.3.2節中的優先順序示例是否正確?

不,那是正確的。流 B 的權重為 4,流 C 的權重為 12。為了確定每個流接收的可用資源的比例,將所有權重(16)相加並將每個流權重除以總權重。因此,流 B 接收四分之一的可用資源,流 C 接收四分之三。因此,正如規範所述:流 B 理想地接收分配給流 C 的資源的三分之一

HTTP/2 連線中需要 TCP_NODELAY 嗎?

是的,有可能。即使對於僅使用單個流下載大量資料的客戶端,仍然需要一些資料包以相反的方向發回以實現最大傳輸速度。在沒有設定 TCP_NODELAY(仍然允許 Nagle 演算法)的情況下,可以傳輸的資料包將被延遲一段時間以允許它們與後續分組合並。

例如,如果這樣一個資料包告訴對等端有更多可用的視窗來傳送資料,那麼將其傳送延遲數毫秒(或更長時間)會對高速連線造成嚴重影響。

部署問題

我該怎麼除錯加密過的 HTTP/2?

存取應用程式資料的方法很多,最簡單的方法是使用 NSS keylogging 配上 Wireshark 外掛(包含在最新開發版中)。這種方法對 Firefox 和 Chrome 都適用。

我該怎麼使用 HTTP/2 的服務端推送?

HTTP/2 伺服器推送允許伺服器向客戶端提供內容而無需等待請求。這可以提高檢索資源的時間,特別是對於具有大頻寬延遲產品的連線,其中網路往返時間佔了在資源上花費的大部分時間。

推送基於請求內容而變化的資源可能是不明智的。目前,瀏覽器只會推送請求,如果他們不這樣做,就會提出匹配的請求(詳見 Section 4 of RFC 7234)。

有些快取不考慮所有請求頭欄位的變化,即使它們列在 Vary header 欄位中。為了使推送資源被接收的可能性最大化,內容協商是最好的選擇。基於 accept-encoding 報頭欄位的內容協商受到快取的廣泛尊重,但是其他報頭欄位可能不受支援。

如果發現譯文存在錯誤或其他需要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可獲得相應獎勵積分。文章開頭的 本文永久連結 即為本文在 GitHub 上的 MarkDown 連結。


掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智慧等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章