http2解釋

目錄

1. 背景

這篇文檔會從技術和協議層面來介紹http2。文檔起源於2014年4月我在斯德哥爾摩做了一次相關的演講,在那之後我對演講內容的細節進行了一些解釋和補充,從而寫出了這篇文檔。

正式版http2規格標準叫做RFC 7540,發佈於2015年5月15日:https://www.rfc-editor.org/rfc/rfc7540.txt

如果你有在這篇文章中發現任何我的失誤造成的錯誤或疏漏,請幫我指正。我會在後續版本中修改。

爲了讓閱讀體驗更流暢,在這篇文章中我會使用“http2”來指代這一新協議,但請記住該協議的正式名字是HTTP/2。

1.1 關於作者

我的名字叫做Daniel Stenberg,在Mozilla工作。在過去20年,我一直致力於開源事業,參與了多個網絡方面的項目。可能我最廣爲人知的身份是curl和libcurl的首席開發者。同時,我也參與了IETF HTTPbis工作組多年,工作在HTTP 1.1和http2標準化的一線.

Email: [email protected]

Twitter: @bagder

Web: daniel.haxx.se

Blog: daniel.haxx.se/blog

1.2 幫助我!

如果你在該文檔裏面發現任何錯誤、疏漏,請發送給我一份相關段落更改後的版本,我會進行修正並且註明所有對文檔有貢獻的人!希望能將這份文檔變得越來越好。

這篇文檔可以在https://daniel.haxx.se/http2下載。

1.3 許可證

這篇文檔基於Createive Commons Attribution 4.0發佈: https://creativecommons.org/licenses/by/4.0/

1.4 文檔歷史

該文檔的第一版發佈於2014年4月25日。下面是最近主要改動的更新歷史。

Version 1.13

  • Converted the master version of this document to Markdown syntax
  • 13: Mention more resources, updated links and descriptions
  • 12: Updated the QUIC description with reference to draft
  • 8.5: Refreshed with current numbers
  • 3.4: The average is now 40 TCP connections
  • 6.4: Updated to reflect what the spec says

Version 1.12

  • 1.1: HTTP/2 is now in an official RFC
  • 6.5.1: Link to the HPACK RFC
  • 9.1: Mention the Firefox 36+ config switch for http2
  • 12.1: Added section about QUIC

Version 1.11

  • Lots of language improvements mostly pointed out by friendly contributors
  • 8.3.1: Mention nginx and Apache httpd specific acitivities

Version 1.10

  • 1: The protocol has been “okayed”
  • 4.1: Refreshed the wording since 2014 is last year
  • Front: Added image and call it “http2 explained” there, fixed link
  • 1.4: Added document history section
  • Many spelling and grammar mistakes corrected
  • 14: Added thanks to bug reporters
  • 2.4: Better labels for the HTTP growth graph
  • 6.3: Corrected the wagon order in the multiplexed train
  • 6.5.1: HPACK draft-12

Version 1.9

  • Updated to HTTP/2 draft-17 and HPACK draft-11
  • Added section “10. http2 in Chromium” (== one page longer now)
  • Lots of spell fixes
  • At 30 implementations now
  • 8.5: Added some current usage numbers
  • 8.3: Mention internet explorer too
  • 8.3.1 Added “missing implementations”
  • 8.4.3: Mention that TLS also increases success rate

2. HTTP的現狀

幾乎所有互聯網上的內容都採用了HTTP 1.1作爲通信協議。人們在該協議上投入了大量精力,所以基於它的基礎架構也得以日臻完善。而得益於此,在現有的HTTP協議之上構建新的方案會比從底層建立新的協議要容易得多。

2.1 HTTP 1.1過於龐大

HTTP剛誕生的時候只被當作是一個相對簡單直觀的協議,但時間證明了早期的設計並不盡人意。於1996年發佈的、描述HTTP 1.0規範的RFC 1945只有60頁,但僅僅3年之後、描述HTTP 1.1規範的RFC 2616就一下增長到了176頁。而當我們在IETF小組對該規範進行更新時,它更是被拆分成了總頁數更多的六個文檔(這就是RFC 7230及其文件族的由來與誕生)。總而言之,HTTP 1.1包含了太多細節和可選的部分,這讓它變得過於龐大。

2.2 過多的可選項

HTTP 1.1不僅包含了非常多的細枝末節,同時也爲未來的擴展預留了很多選項。這種事無鉅細的風格導致在現有的軟件生態中,幾乎沒有任何實現真正實現了協議中提及的所有細節,甚至要弄清楚“所有細節”到底包括哪些細節都非常困難。而這也導致了很多最初不常用的功能在後來的實現中很少會被支持,而有些最初實現了的功能,卻又很少被使用。

隨着時間推移,這些當初看似被邊緣化的功能逐漸被用上,客戶端和服務器的互用性(interoperability)問題就被暴露了出來。HTTP管線化(HTTP pipelining)就是一個非常好的例子。

2.3 未能被充分利用的TCP

HTTP 1.1很難榨乾TCP協議所能提供的所有性能。HTTP客戶端和瀏覽器必須要另闢蹊徑的去找到新的解決方案來降低頁面載入時間。

與此同時,人們也嘗試去用新的協議來替代TCP,但結果證明這也非常困難。無奈之下,我們只能嘗試同時改進TCP協議本身和基於TCP的上層協議。

簡單來說,我們可以通過更好的利用TCP來減少傳輸過程中的暫停,並充分挖掘利用那些本可以用於發送/接受更多數據的時間。下面幾段我們將會着重討論這些問題。

2.4 傳輸大小和資源數量

如果仔細觀察打開那些最流行的網站首頁所需要下載的資源的話,會發現一個非常明顯的趨勢。 近年來加載網站首頁需要的下載的數據量在逐漸增加,並已經超過了1.9MB。但在這裏我們更應該關心的是:平均每個頁面爲了完成顯示與渲染所需要下載的資源數已經超過了100個。

正如下圖所示,這種趨勢已經持續了很長一段時間,並且沒有減緩的跡象。該圖表中綠色直線展示了傳輸數據大小的增長,紅色直線展示了平均請求資源數量的增長。

transfer size growth

2.5 惱人的延遲

HTTP 1.1對網絡延遲非常敏感。部分原因是HTTP pipelining還存有很多問題,所以對大部分用戶來說這項技術是被默認關閉的。

雖然近幾年來網絡帶寬增長非常快,然而我們卻並沒有看到網絡延遲有對應程度的降低。在高延遲的網絡上(比如移動設備),即使擁有高連接速率,也很難獲得優質快速的網絡體驗。

另外一個需要低延遲的場景是某些視頻服務,如視頻會議、遊戲和一些類似無法預生成待發送數據流的服務。

2.6 線頭阻塞(Head-of-line blocking)

HTTP pipelining是這樣一種技術:在等待上一個請求響應的同時,發送下一個請求。(譯者注:作者這個解釋並不完全正確,HTTP pipelining其實是把多個HTTP請求放到一個TCP連接中一一發送,而在發送過程中不需要等待服務器對前一個請求的響應;只不過,客戶端還是要按照發送請求的順序來接收響應。)但就像在超市收銀臺或者銀行櫃檯排隊時一樣,你並不知道前面的顧客是乾脆利索的還是會跟收銀員/櫃員磨蹭到世界末日(譯者注:不管怎麼說,服務器(即收銀員/櫃員)是要按照順序處理請求的,如果前一個請求非常耗時(顧客磨蹭),那麼後續請求都會受到影響),這就是所謂的線頭阻塞(head-of-line blocking)。

當然,你可以在選擇隊伍時候就做好功課,去排一個你認爲最快的隊伍,或者甚至另起一個新的隊伍(譯者注:即新建一個TCP連接)。但不管怎麼樣,你總歸得先選擇一個隊伍,而且一旦選定之後,就不能更換隊伍。

但是,另起新隊伍會導致資源耗費和性能損失(譯者注:新建 TCP 連接的開銷非常大)。這種另起新隊伍的方式只在新隊伍數量很少的情況下有作用,因此它並不具備可擴展性。(譯者注:這段話意思是說,靠大量新建連接是不能有效解決延遲問題的,即HTTP pipelining並不能徹底解決head-of-line blocking問題。)所以針對此問題並沒有完美的解決方案。

這就是爲什麼即使到了今天,大部分桌面瀏覽器仍然會選擇默認關閉HTTP pipelining這一功能的原因。

而關於這個問題的更多細節,可以參閱Firefox的 bugzilla #264354

3. 那些年,克服延遲之道

再困難的問題也有解決的方案,但這些方案卻良莠不齊。

3.1 Spriting

Spriting是一種將很多較小的圖片合併成一張大圖,再用JavaScript或者CSS將小圖重新“切割”出來的技術。

網站可以利用這一技巧來達到提速的目的——在HTTP 1.1裏,下載一張大圖比下載100張小圖快得多。

但是當某些頁面只需要顯示其中一兩張小圖時,這種緩存整張大圖的方案就顯得過於臃腫。同時,當緩存被清楚的時候的時候,Spriting會導致所有小圖片被同時刪除,而不能選擇保留其中最常用的幾個。

3.2 內聯(Inlining)

Inlining是另外一種防止發送很多小圖請求的技巧,它將圖片的原始數據嵌入在CSS文件裏面的URL裏。而這種方案的優缺點跟Spriting很類似。

.icon1 {
    background: url(data:image/png;base64,<data>) no-repeat;
  }
.icon2 {
    background: url(data:image/png;base64,<data>) no-repeat;
  }

3.3 拼接(Concatenation)

大型網站往往會包含大量的JavaScript文件。開發人員可以利用一些前端工具將這些文件合併爲一個大的文件,從而讓瀏覽器能只花費一個請求就將其下載完,而不是發無數請求去分別下載那些瑣碎的JavaScript文件。但凡事往往有利有弊,如果某頁面只需要其中一小部分代碼,它也必須下載完整的那份;而文件中一個小小的改動也會造成大量數據的被重新下載。

這種方案也給開發者造成了很大的不便。

3.4 分片(Sharding)

最後一個我要說的性能優化技術叫做“Sharding”。顧名思義,Sharding就是把你的服務分散在儘可能多的主機上。這種方案乍一聽比較奇怪,但是實際上在這背後卻蘊藏了它獨闢蹊徑的道理!

最初的HTTP 1.1規範提到一個客戶端最多隻能對同一主機建立兩個TCP連接。因此,爲了不和規範衝突,一些聰明的網站使用了新的主機名,這樣的話,用戶就能和網站建立更多的連接,從而降低載入時間。

後來,兩個連接的限制被取消了,現在的客戶端可以輕鬆地和每個主機建立6-8個連接。但由於連接的上限依然存在,所以網站還是會用這種技術來提升連接的數量。而隨着資源個數的提升(上面章節的圖例),網站會需要更多的連接來保證HTTP協議的效率,從而提升載入速度。在現今的網站上,使用50甚至100個連接來打開一個頁面已經並不罕見。根據httparchive.org的最新記錄顯示,在Top 30萬個URL中平均使用40(!)個TCP連接來顯示頁面,而且這個數字仍然在緩慢的增長中。

另外一個將圖片或者其他資源分發到不同主機的理由是可以不使用cookies,畢竟現今cookies的大小已經非常可觀了。無cookies的圖片服務器往往意味着更小的HTTP請求以及更好的性能!

下面的圖片展示了訪問一個瑞典著名網站的時產生的數據包,請注意這些請求是如何被分發到不同主機的。

image sharding at expressen.se

4. 升級HTTP

花點功夫去改善HTTP協議顯然是極好的事情。我們可以着手於以下幾個方面:

  1. 降低協議對延遲的敏感
  2. 修復pipelining和head of line blocking的問題
  3. 防止主機需求更高的連接數量
  4. 保留所有現有的接口,內容,URI格式和結構
  5. 由IETF的HTTPbis工作組來制定

4.1. IETF和HTTPbis工作組

The Internet Engineering Task Force (IETF)是一個開發和推廣互聯網標準的組織。他們的重心是在協議層面。他們最出名的工作是制定了TCP、DNS、FTP和它們最佳實踐的RFC規範,但HTTP和許多其他協議卻進展緩慢。

IETF成立了獨立的“工作小組”以便完成某些特定領域內的目標,他們建立了一個“章程”用以制定達到目標的指導方針和規範。在這裏,任何人都可以參與討論和開發,並且每個人有同等的話語權,沒人關心你來自哪個公司或組織。

HTTPbis工作組(我們待會兒再解釋這個名字)在2007年夏天成立之後就着手於HTTP1.1標準的更新。在組內,關於下一版本HTTP協議的討論實際上在2012年後期纔開始。而HTTP1.1的更新工作在2014年初完成,並被整理成RFC 7320系列。

2014年6月初,HTTPbis工作組名義上的最終版文檔會議在紐約召開。而剩下的討論以及等IETF走完流程通過官方的RFC版本預計在來年完成。

一些HTTP領域的權威缺席了工作組的討論和會議。我並不想在此提及任何公司和產品。但藉此,現在互聯網上也有一些參與者因此獲得了更多信心——不需要這些公司參與IETF也能做得很好。。。

4.1.1. 名字中的“bis”

工作組名字中的“bis”來自拉丁語中表示“二”的副詞,Bis通常被IETF用作名字的後綴來以表示標準的升級或者一些二次工作,比如這裏是針對HTTP1.1。

4.2. 起源於SPDY的http2

SPDY是由Google牽頭開發的協議。他們將其開源,使得每個人都可以參與開發。但很明顯,他們通過控制瀏覽器的實現和享用着優質服務的大量用戶來獲益。

當HTTPbis小組決定開始制定http2的時候,SPDY已經充分證實了它是一個非常好用的方案。當時已經有人在互聯網上成功部署SPDY,並且也有一些文章討論他的性能。因此,http2便基於SPDY/3草案進行一些修改之後發佈了http2的draft-00。

5. http2的基本概念

http2到底做了些什麼呢?而HTTPbis小組究竟又應該把它制定到什麼樣的程度呢?

事實上,http2有着非常嚴格的邊界,這也給小組成員的創新帶來了些許限制。

  • http2必須維持HTTP的範式。畢竟它只是一個讓客戶端發送請求到服務器的基於TCP的協議。

  • 不能改變 http:// 和 https:// 這樣的URL,也不能對其添加新的結構。使用這類URL的網站太多了,沒法指望他們全部改變。

  • HTTP1的服務器和客戶端依然會存在很久,所以我們必須提供HTTP1到http2服務器的代理。

  • 隨後,我們也要讓這種代理能夠將http2的功能一對一的映射到HTTP 1.1的客戶端。

  • 刪除或者減少協議裏面那些可選的部分。雖然這並不算的上是一個需求,但是SPDY和Google的團隊都非常喜歡這點。通過讓協議裏所有的內容都成爲了強制性要求,可以防止人們在實現的時候偷懶,從而規避一些將來可能會發生的問題。

  • 不再使用小版本號。服務器和客戶端都必須確定自己是否完整兼容http2或者徹底不兼容。如果將來該協議需要被擴充或者變更,那麼新的協議將會是http3,而不是http 2.x。

5.1. http2和現有的URI結構

如上所述,現有的URI結構正在被HTTP 1.x使用而不能被更換,所以http2也必須沿用該結構。因此不得不找到一種方式將使用的協議升級至http2,比如可以要求服務器讓它作響應時使用http2來替代舊的協議。

HTTP 1.1本身就制定過“升級”的方案:提供一個首部字段,表示允許服務器在收到舊協議請求的同時,可以向客戶端發送新協議的響應。但這一方案往往需要花費一次額外的往返通信來作爲升級的代價。

而這一代價是SPDY團隊不想接受的。因爲他們只實現了基於TLS的SPDY,所以他們開發了一個TLS的擴展去簡化協議的協商。這個擴展被稱作NPN(Next Protocol Negotiation),藉助於此,服務器會通知客戶端所有它支持的協議,讓客戶端從中選擇一個合適的來進行通訊。

5.2. 爲 https:// 所準備的http2

有相當多的人關注到了http2可以在TLS上正常的運作,而SPDY依賴於TLS,所以按理說TLS也應成爲http2 必需的組件,不過出乎大家意料的是http2將TLS標記成了可選。然而,全球兩大瀏覽器領導者 —— Firefox和Chrome都明確地表示,他們只會實現基於TLS的http2.

選擇TLS的原因的其中之一是希望保護以及尊重用戶的隱私,而早期的評估結果也表明,在TLS上建立新的協議更有可能獲得成功。而這其中部分原因是人們普遍認爲任何來自80端口的流量都是基於HTTP 1.1亦或者是其某個變種的,而不是另外一種全新的協議。

關於是否應該強制使用TLS的主題在郵件組內和會議上引起了不小的爭議 —— 這到底是好是壞呢?不管怎麼樣,對於這種備受爭議的話題還是請謹慎討論,尤其是當你面對一個HTTPbis小組成員的時候。

諸如此類,還有一個激烈而長期的討論,即:如果選擇了使用TLS,那http2是否應該強制規定密碼列表,也許應該建立起一個黑名單,又或者它根本就不需要從TLS層得到任何東西。不過這個問題還是留給TLS工作組去解決吧,最後的規範中指定了TLS最低版本爲1.2,並且會有加密組的限制。

5.3 基於TLS之上的http2協商

Next Protocol Negotiation (NPN)是一個用來在TLS服務器上協商SPDY的協議。IETF將這個非正式標準進行規範化,從而演變成了ALPN(Application Layer Protocol Negotiation)。ALPN會隨着http2的應用被推廣,而SPDY的客戶端與服務器則會繼續使用NPN。

由於NPN先於ALPN誕生,而ALPN又經歷了一些標準化過程,所以許多早期的http2客戶端和服務器在協商http2時會將這兩者同時實現。與此同時,考慮到SPDY會使用NPN,而許多服務器又會同時提供SPDY以及http2,所以在這些服務器上同時支持ALPN以及NPN顯然會成爲最理所當然的選擇。

ALPN和NPN的主要區別在於:誰來決定通信協議。在ALPN的描述中,是讓客戶端先發送一個協議優先級列表給服務器,由服務器最終選擇一個合適的。而NPN則正好相反,客戶端有着最終的決定權。

5.4 爲 http:// 所準備的http2

正如我們之前所提到的,對於純文本的HTTP1.1來說,協商http2的方法就是通過給服務器發送一個帶升級頭部的報文。如果服務器支持http2,它將以“101 Switching”作爲回覆的狀態碼,並從此開始在該連接上使用http2。也許你很容易就發現這樣一個升級的流程會需要消耗掉一整個的往返時延,但好處是http2連接相比HTTP1可以被更大限度地重用和保持。

雖然有些瀏覽器廠商的發言人宣稱他們不會實現這樣的http2會話方式,但IE團隊已公開表示他們會實現,與此同時,curl也已經支持了這種方式。

直到今天,沒有任何主流瀏覽器支持非TLS的http2.

6. http2協議

背景介紹就到此爲止了,歷史的腳步已經將我們推到了今天。現在讓我們深入看看該協議的規範,看看那些細節和概念。

6.1. 二進制

http2是一個二進制協議。

仔細想想,如果你是一個曾經跟互聯網協議打過交道,那你很可能會本能反對二進制協議,你甚至準備好了一大堆理由來證明基於文本/ascii的協議是多麼的有用,正如你曾無數次地通過telnet等應用手工地輸入HTTP來發起請求。

基於二進制的http2可以使成幀的使用變得更爲便捷。在HTTP1.1和其他基於文本的協議中,對幀的起始和結束識別起來相當複雜。而通過移除掉可選的空白符以及其他冗餘後,再來實現這些會變得更容易。

而另一方面,這項決議同樣使得我們可以更加便捷的從幀結構中分離出那部分協議本身的內容。而在HTTP1中,各個部分相互交織,猶如一團亂麻。

事實上,由於協議提供了壓縮這一特性,而其經常運行在TLS之上的事實又再次降低了基於純文本實現的價值,反正也沒辦法直接從數據流上看到文本。因此通常情況下,我們必須習慣使用類似Wireshark這樣的工具對http2的協議層一探究竟。

我們可以使用curl這樣的工具來調試協議,而如果要進一步地分析網絡數據流則需要諸如Wireshark這樣的http2解析器。

6.2. 二進制格式

http2會發送有着不同類型的二進制幀,但他們都有如下的公共字段:Type, Length, Flags, Stream Identifier和frame payload

規範中一共定義了10種不同的幀,其中最基礎的兩種分別對應於HTTP 1.1的DATA和HEADERS。之後我會更詳細的介紹它們其中的一部分。

6.3. 多路複用的流

上一節提到的Stream Identifier將http2連接上傳輸的每個幀都關聯到一個“流”。流是一個獨立的,雙向的幀序列可以通過一個http2的連接在服務端與客戶端之間不斷的交換數據。

每個單獨的http2連接都可以包含多個併發的流,這些流中交錯的包含着來自兩端的幀。流既可以被客戶端/服務器端單方面的建立和使用,也可以被雙方共享,或者被任意一邊關閉。在流裏面,每一幀發送的順序非常關鍵。接收方會按照收到幀的順序來進行處理。

流的多路複用意味着在同一連接中來自各個流的數據包會被混合在一起。就好像兩個(或者更多)獨立的“數據列車”被拼湊到了一輛列車上,但它們最終會在終點站被分開。下圖就是兩列“數據火車”的示例

one train
another train

它們就是這樣通過多路複用的方式被組裝到了同一列火車上。

multiplexed train

6.4. 優先級和依賴性

每個流都包含一個優先級(也就是“權重”),它被用來告訴對端哪個流更重要。當資源有限的時候,服務器會根據優先級來選擇應該先發送哪些流。

藉助於PRIORITY幀,客戶端同樣可以告知服務器當前的流依賴於其他哪個流。該功能讓客戶端能建立一個優先級“樹”,所有“子流”會依賴於“父流”的傳輸完成情況。

優先級和依賴關係可以在傳輸過程中被動態的改變。這樣當用戶滾動一個全是圖片的頁面的時候,瀏覽器就能夠指定哪個圖片擁有更高的優先級。或者是在你切換標籤頁的時候,瀏覽器可以提升新切換到頁面所包含流的優先級。

6.5. 頭壓縮

HTTP是一種無狀態的協議。簡而言之,這意味着每個請求必須要攜帶服務器需要的所有細節,而不是讓服務器保存住之前請求的元數據。因爲http2並沒有改變這個範式,所以它也以同樣原理工作。

這也保證了HTTP可重複性。當一個客戶端從同一服務器請求了大量資源(例如頁面的圖片)的時候,所有這些請求看起來幾乎都是一致的,而這些大量一致的東西則正好值得被壓縮。

每個頁面請求的資源數量在增多(如前所述),同時 cookies 的使用和請求的大小也在日漸增長。cookies需要被包含在所有請求中,且他們在多個請求中經常是一模一樣的。

HTTP 1.1請求的大小正變得越來越大,有時甚至會大於TCP窗口的初始大小,這會嚴重拖累發送請求的速度。因爲它們需要等待帶着ACK的響應回來以後,才能繼續被髮送。這也是另一個需要壓縮的理由。

6.5.1. 壓縮是非常棘手的課題

HTTPS和SPDY的壓縮機制被發現有受BREACHCRIME攻擊的隱患。通過向流中注入一些已知的文本來觀察輸出的變化,攻擊者可以從加密的載荷中推導出原始發送的數據。

爲協議的動態內容進行壓縮並使其免於被攻擊,需要仔細且全面的考慮,而這也正是HTTPbis小組嘗試去做的。

HPACK,HTTP/2頭部壓縮,顧名思義它是一個專爲http2頭部設計的壓縮格式。確切的講,它甚至被制定寫入在另外一個單獨的草案裏。新的格式同時引入了一些其他對策讓破解壓縮變得困難,例如採用幀的可選填充和用一個bit作爲標記,來讓中間人不壓縮指定的頭部。

用Roberto Peon(HPACK的設計者之一)的話說

“HPACK旨在提供一個一致性的實現使信息量的損失儘可能少,使編解碼快速而方便,使接收方能控制壓縮文本的大小,允許代理重新建立索引(如,通過代理在前後端共享狀態),以及對哈夫曼編碼串的更快速比較”

6.6. 重置 - 後悔藥

HTTP 1.1的有一個缺點是:當一個含有確切值的Content-Length的HTTP消息被送出之後,你就很難中斷它了。當然,通常你可以斷開整個TCP鏈接(但也不總是可以這樣),但這樣導致的代價就是需要通過三次握手來重新建立一個新的TCP連接。

一個更好的方案是隻終止當前傳輸的消息並重新發送一個新的。在http2裏面,我們可以通過發送RST_STREAM幀來實現這種需求,從而避免浪費帶寬和中斷已有的連接。

6.7. 服務器推送

這個功能通常被稱作“緩存推送”。主要的思想是:當一個客戶端請求資源X,而服務器知道它很可能也需要資源Z的情況下,服務器可以在客戶端發送請求前,主動將資源Z推送給客戶端。這個功能幫助客戶端將Z放進緩存以備將來之需。

服務器推送需要客戶端顯式的允許服務器提供該功能。但即使如此,客戶端依然能自主選擇是否需要中斷該推送的流。如果不需要的話,客戶端可以通過發送一個RST_STREAM幀來中止。

6.8. 流量控制

每個http2流都擁有自己的公示的流量窗口,它可以限制另一端發送數據。如果你正好知道SSH的工作原理的話,這兩者非常相似。

對於每個流來說,兩端都必須告訴對方自己還有足夠的空間來處理新的數據,而在該窗口被擴大前,另一端只被允許發送這麼多數據。

而只有數據幀會受到流量控制。

7. 擴展

http2協議強制規定了接收方必須讀取並忽略掉所有未知幀(即未知幀類型的幀)。雙方可以在逐跳原則(hop-by-hop basis)基礎上協商使用新的幀,但這些幀的狀態無法被改變,也不受流控制。

是否應該允許添加擴展的這個話題在制定http2協議的時候被反覆討論了很久,但在draft-12之後,最終塵埃落定確定了允許添加擴展。

但擴展不再是協議本身的一部分,它被記錄在覈心協議規範之外。現在已經有兩種類型的幀被工作組記錄在案,它們很可能率先被納入協議的擴展部分,而這兩個曾被當作“原生”的幀非常流行,所以接下來我會詳細討論它們。

7.1. 備選服務(Alternative Services)

隨着http2逐漸被接受,我們有理由相信,相對於HTTP 1.x,TCP連接會更長並被保持的更久。對客戶端來講,最好是到每個主機/站點的每一條連接都可以做儘可能多的事情,而這也需要每個連接可以保持更長的時間。

但這會影響到HTTP負載均衡器的正常工作,比如在一個網站會出於性能的考慮,當然也可能是正常的維護或者一些類似的原因,想建議客戶端連接到另外一個主機的時候。

服務器將會通過發送Alt-Svc頭(或者http2的ALTSVC幀)來告知客戶端另一個備選服務。即另外一條指向不同的服務源、主機或端口,但卻能獲取同樣內容的路由。

客戶端應該嘗試異步的去連接到該服務,如果連接成功的話,即可以使用該備選服務。

7.1.1. 機會型TLS(Opportunistic TLS)

Alt-Svc頭部意味着允許服務器基於http://提供內容,與此同時,這個頭部也意味着告知客戶端:同樣的內容也可以通過TLS連接來獲取。

這是個還在討論中的功能。因爲這樣的連接會產生一個未認證的、在任何地方也不會被標示爲“安全”的TLS連接,也不會在客戶端界面上出現任何鎖標識,所以沒法讓用戶知道這其實不是常規的HTTP連接。這就是很多人強烈反對機會型TLS的原因。

7.2. 阻塞(Blocked)

這個類型的幀意味着:當服務端存在需要發送的內容,但流控制卻禁止發送任何數據時,那麼此類型的幀將會被髮送且發送一次。這種幀設計的目的在於,如果你接收到了此幀,那麼連接中必然有錯誤發生或者是得到了低於期望的傳輸速度。

在此幀被放到協議擴展部分之前,draft-12中的一段話:

”阻塞幀被包含在草案版本中作爲實驗性的特性,如果它無法獲得良好的反饋,那麼該特性最後會被移除。”

8. http2的世界

那麼當http2被廣泛採用的時候,世界將會成什麼樣呢?或者說,它會被真正的採用嗎?

8.1. http2會如何影響普通人?

到目前爲止,http2還沒被大範圍部署使用,我們也無法確定到底會發生什麼變化,但至少可以參考SPDY的例子和曾經做過的實驗來進行大概的估計。

http2減少了網絡往返傳輸的數量,並且用多路複用和快速丟棄不需要的流的辦法來完全避免了head of line blocking(線頭阻塞)的困擾。

它也支持大量並行流,所以即使網站的數據分發在各處也不是問題。

合理利用流的優先級,可以讓客戶端儘可能優先收到更重要的數據。

所有這些加起來,我認爲頁面載入時間和站點的響應速度都會更快。簡而言之,它們都代表着更好的web體驗。

但到底能變得多快,到底提升有多大呢?我認爲目前很難說清楚。畢竟這些技術依然在早期階段,我們還無法看見客戶端和服務器實現這些並真正受益於它所提供的強大功能。

8.2. http2會如何影響web開發?

近年來,web開發者、web開發環境爲HTTP 1.1存在的一些問題提供了一部分臨時的解決方案。其中的一部分我已在上文中簡單的介紹了,不妨簡單的回憶一下。

很多工具和開發者可能會默認使用這些方案,但它們其中的一部分也許會損害到http2的性能,或者至少讓我們無法真正利用到http2新提供的強大威力。Spriting和內聯應該是http2裏面最不需要的了。因爲http2更傾向於使用更少的連接,所以Sharding甚至會傷害到http2的性能。

這裏的問題在於:對於網站的開發者而言,在短期內開發和部署同一套前端來支持HTTP 1.1和http2的客戶端訪問並獲得最大性能將會是一個挑戰。

考慮到這些問題,我認爲徹底發掘http2的潛力還有很長一段路要走。

8.3. http2的各種實現

在這樣一篇文章中詳細說明每個實現細節註定乏味且毫無意義,我將用更通用的術語來解釋實際的場景,並在此給大家提供一個http2的實現列表作爲參考。

在http2的早期就已經有大量的實現。並且在http2標準化工作期間,這個數量還持續增長。截至我寫這篇文檔的時候,共有40種實現已記錄在案,他們中的大多數都實現了最新的草案。

8.3.1. 瀏覽器

Firefox一直緊跟最新的協議,Twitter也緊追不捨提供了基於http2的服務。2014年4月期間,Google在少數測試服務器上提供http2支持。從同年5月開始,開發版的Chrome支持http2。Microsoft也在他們的產品預發佈會上展示了支持http2的下一代瀏覽器。Safari (iOS 9 以及 Mac OS X El Capitan) 和 Opera也都表態它們將會支持http2。

8.3.2 服務器

事實上,已經有不少的服務器實現了http2。

時下最流行的Nginx自1.9.5(發佈於2015年9月22號)版本後提供了對http2的支持並且取締了原來的SPDY模塊(因此SPDY和http2無法同時運行在同一個Nginx服務器實例中)。

而Apache HTTPD服務器也實現了一個名爲mod_http2的http2模塊,並與2015年10月9號在2.4.17的版本中發佈。

此外,H2O, Apache Traffic Server, nghttp2, Caddy 以及 LiteSpeed 也都發布了可以工作於http2下的服務器。

8.3.3 其他

curl和libcurl支持未加密的http2並藉助某些TLS庫支持了TLS版本。

Wireshark同樣支持了http2, 所以用它來分析http2網絡數據流着實是再好不過的了。

8.4. 對http2的常見批評

在制定協議的討論過程中往往存在許多爭議,甚至會有不少人認爲這樣的協議最終會以失敗告終。這裏我想提一些常見的對協議的批評以及我的解釋:

8.4.1. “這個協議是Google設計制定的”

江湖上有太多傳言暗示着這個世界越來越被Google所控制,但事實顯然並非如此。這個協議是IETF制定的,就跟過去30年間很多其他協議一樣。但不得不承認,SPDY是Google非常出色的成果。它不僅僅證明了開發一個新協議的可行性,還充分展現了新協議所能帶來的好處。

而Google也公開聲明了他們會在2016年移除Chrome裏對SPDY和NPN的支持,並且極力推動服務器遷移至HTTP/2。2016年2月他們聲明了SPDY和NPN會在Chrome 51被移除.

8.4.2. “這個協議只在瀏覽器上有用”

在一定意義上,這是對的。開發http2的其中一個主要原因就是修復HTTP pipelining。如果在你的應用場景裏本來就不需要pipelining,那麼確實很有可能http2對你沒有太大幫助。雖然這並不是唯一的提升,但顯然這是非常重要的一個。

一旦當某些服務意識到在一個連接上建立多路複用流的強大威力時,我認爲會有越來越多的程序採用http2。

小規模的REST API和採用HTTP 1.x的簡單程序可能並不會從遷移到http2中獲得多大的收益。但至少,遷移至http2對絕大部分用戶來講幾乎是沒有壞處的。

8.4.3. “這個協議只對大型網站有用”

完全不是這樣。因爲缺乏內容分發網絡,小網站的網絡延遲往往較高,而多路複用的能力可以極大的改善在高網絡延遲下的體驗。大型網站往往已經將內容分發到各處,所以速度其實已經非常快了。

8.4.4. “TLS讓速度變得更慢”

這個評價在某種程度上是對的。雖然TLS的握手確實增加了額外的開銷,但也有越來越多的方案提出來減少TLS往返的時間。使用TLS而不是純文本帶來的開銷是顯著的,有可觀證據表明,和傳輸同樣的流量相比,TLS會消耗更多的CPU和其他資源。具體影響有多大以及怎麼影響是一個和具體測量有關的課題。更多的例子可以參看istlsfastyet.com

Telecom和一些其他網絡服務商,例如ATIS開放網絡聯盟,表示爲了爲衛星、飛機等提供的快速網絡體驗,他們需要一些不加密的流量來提供caching,壓縮和其他技術。

由於http2並不強制要求使用TLS,所以我們不應該爲此擔心。

如今,很多互聯網使用者都希望TLS能被更廣泛的使用來保護用戶隱私。

實驗也證明了通過使用TLS能比用在80端口實現一個新的基於文本的協議更容易成功。因爲當前已經有太多中間商使用該方案,所以凡是基於80端口的協議,都很可能被理所當然的當作HTTP 1.1。

最後,得益於http2可以在單一連接上提供多路複用的流,正常使用普通瀏覽器也可以減少TLS握手的次數,所以使用HTTPS仍然會比HTTP 1.1更快。

8.4.5. “不基於ASCII是沒法忍受的”

是的,如果我們可以直接讀出協議內容,那麼調試和追蹤都會變得更爲簡單。但是基於文本的協議更容易產生錯誤,造成更多解析的問題。

假如你真的無法接受二進制協議,那麼你也很難在HTTP 1.x中處理TLS和壓縮。因爲其實這些技術已經被使用了很久了。

8.4.6. “它根本沒有比HTTP/1.1快”

當然,到底該如何定義和衡量“快”就是另外一個話題了,但在SPDY的時代,已經有很多實驗證明了該協議會讓瀏覽器載入頁面變得更快(例如華盛頓大學的“SPDY有多快?”和Hervé Servy的“評估啓用SPDY後的Web服務器的性能”),同樣這些實驗也可以被用來證明http2。我期待能有越來越多的諸如此類的測試實驗結果發佈。而這篇文章httpwatch.com的一個簡單測試亦能證明HTTP/2名副其實。

8.4.7. “它違反了網絡分層”

你確定這也是反對的理由麼?網絡分層並不是不可侵犯的。如果我們在制定http2的時候已經踏入了灰色地帶,那我們當然可以嘗試在限制內制定出更好更高效的協議。

8.4.8. “它並沒有修復很多HTTP/1.1的短板”

確實是這樣。兼容HTTP/1.1的範式是我們的目標之一,所以一些老的HTTP功能仍然被保留。例如一些常用的協議頭、可怕的cookies、驗證頭等等。但保留這些範式的好處就是我們在升級到新協議的時候少掉很多工作,也不需要重寫很多底層的東西。Http2其實只是一個新的幀層。

8.5. http2會被廣泛部署嗎?

現在討論這個議題還言之尚早,但我仍然要在這裏做出我的預估。

很多懷疑論者會以“看看IPv6現在的德性”爲讓我們回想起這個經歷了10多年纔開始慢慢被採用的協議。但http2畢竟不是IPv6。它是一個建立在TCP之上的使用基於原有HTTP協議升級過後的機制、端口號和TLS等的協議。大部分路由器或者防火牆不需要爲此而進行更改。

Google向世界展示了他們的SPDY,證明了像這樣的新協議也能在足夠短的時間內擁有多種實現,並且能被瀏覽器和服務所採用。雖然如今支持SPDY服務器端數量在1%以內,但通過這些服務器所交換的數據卻要大很多。很多非常流行的網站現在也有提供SPDY支持。

我認爲建立在SPDY的基本範式之上的http2會被更廣泛的部署,其中一個主要的原因是:它是一個IETF制定的協議。而SPDY則因爲揹負了“它是Google的協議”這個惡名,導致它的發展總是畏首畏腳。

在它首次發佈的幕後有很多大型瀏覽器支持。來自Firefox,Chrome,Safari,Internet Explorer和Opera的代表宣佈了他們會發布支持http2特性的瀏覽器,並且他們已經演示了一些能正常運作的實現。

也有很多像Google,Twitter和Facebook這樣的服務器運營者希望儘快支持http2,也同樣希望可以快點在主流服務器實現中出現對http2的支持(例如Apache HTTP Server和nginx)。而H2o作爲一個極具潛力的新生HTTP服務器,已經支持了http2。

那些大型代理程序開發者,例如HAProxy、Squid和Varnish也表示出了他們對支持http2的興趣。

縱觀2015年,http2的流量正在逐步上升。9月初,Firefox 40中http2流量佔據了所有HTTP流量中的13%,HTTPS中的27%。與此同時,Google表示約有18%的流量來自HTTP/2。值得注意的是,Google同時也在實驗其他協議(參見12.1中的QUIC),這也使得http2的使用量暫時比正常值低一些。

9. Firefox裏的http2

Firefox緊跟着草案,並且很早之前就實現了http2的測試實現。在http2協議開發的時候,客戶端和服務器需要採用同一的協議草案版本,進行測試也變得比較繁瑣。所以請一定注意你的客戶端和服務器支持的是一樣的版本。

9.1. 首先,確保它已被啓用

從發佈於2015年1月13日的Firefox 35之後,http2支持是默認開啓的。

在地址欄裏進入’about:config’,再搜索一個名爲“network.http.spdy.enabled.http2draft”的選項,確保它被設置爲true。Firefox 36添加了一個“network.http.spdy.enabled.http2”的配置項,並默認設置爲true。後者控制的是“純”http2版本,而前者控制了啓用/禁用通過http2草案版本進行協商。從Firefox 36之後,這兩者都默認爲true。

9.2. 僅限TLS

請記住Firefox只在TLS上實現了http2。你只會看到http2只在https://的網站裏得到支持。

9.3. 透明!

transparent http2 use

在UI上,沒有任何元素標明你正在使用http2。但想確認也並不複雜,一種方法是啓用“Web developer->Network”,再查看響應頭裏面服務器發回來的內容。這個響應是“HTTP/2.0”,並且Firefox也插入了一個自己頭“X-Firefox-Spdy:”,如上面截圖所示。

你在這裏看到的頭文件是網絡工具把二進制的http2格式轉換成類似HTTP 1.x顯示方式的文本格式。

9.4. 圖形化HTTP/2

有一些Firefox的插件可以圖形化HTTP/2,比如“HTTP/2 and SPDY Indicator”

10. Chromium裏的http2

Chromium團隊並且很早之前就已經在dev和beta分支裏面實現並支持了HTTP/2。從2015年1月27日發佈的Chrome 40起,http2已經默認爲一些用戶啓用該功能。雖然剛開始用戶的數量會很少,但會慢慢增加。

Chrome 51移除了SPDY的支持來爲http2鋪路。在2016年2月的一篇博客裏面有如下一段話:

“在Chrome裏有超過25%的資源是通過HTTP/2來傳輸的,而SPDY只有不到5%。考慮到如此大範圍的採用,自5月15日,也就是HTTP/2 RFC的週年紀念日起,Chrome將不再支持SPDY。”

10.1. 首先,確保它已被啓用

在地址欄裏進入chrome://flags/#enable-spdy4,如果沒有被enable的話,點擊"enable"啓用它。

10.2. TLS-only

請記住Chrome只在TLS上實現了http2。你只會在以https://做前綴的網站裏得到http2的支持。

10.3. 圖形化HTTP/2

有一些Chrome的插件可以圖形化HTTP/2,比如“HTTP/2 and SPDY Indicator”

10.4. QUIC

Chrome正在試驗QUIC(詳情請看12.1),所以或多或少稀釋了HTTP/2的份額。

11. curl中的http2

curl項目從2013年9月就開始對http2提供實驗性的支持。

爲了遵從curl的要旨,我們儘可能全方位地支持http2。curl通常被用作一個網站連接測試工具,希望這項使命也能在http2上被得以延續。

curl使用一個叫做nghttp2的庫來提供http2幀層的支持。curl依賴於nghttp2 1.0以上版本。

請注意當前linux curl和libcurl並沒有默認啓用對HTTP/2協議的支持。

11.1. 跟HTTP 1.x非常相似

curl會在內部把收到的http2頭部轉換爲HTTP1.x風格的頭部再呈現給用戶,這樣一來,它們就和目前的HTTP非常類似。這也使得無論是用curl還是HTTP,轉換都非常容易。類似地,curl會用相同的方式對發出的HTTP頭部做轉換,即發給curl的HTTP 1.x風格頭部會在被髮送到http2服務器之前完成轉換。這使得戶無需關心底層到底使用的是哪個版本的HTTP協議。

11.2. 不安全的純文本

curl通過升級頭部支持基於標準TCP的http2. 當發起一個使用http2的HTTP請求,如果可能,curl會請求服務器把連接升級到http2.

11.3. TLS和相關庫

curl可以使用許多不同TLS的底層庫來提供TLS支持,http2也得這樣。TLS兼容http2的挑戰來自於對ALPN以及一些NPN擴展的支持。

基於最新版本的OpenSSL或NSS編譯curl可以同時獲得ALPN和NPN支持。而使用GnuTLS或PolarSSL只能得到ALPN。

11.4. 命令行中使用

無論是用純文本還是通過TLS,必須使用--http2參數來讓curl使用http2。在未使用該參數的默認情況下,curl會使用HTTP/1.1。

11.5. libcurl參數

11.5.1 啓用HTTP/2

應用程序和從前一樣使用https://或者http://風格的URL,但你可以通過將curl_easy_setoptSURLOPT_HTTP_VERSION參數設置爲CURL_HTTP_VERSION_2來使libcurl嘗試使用http2。它將優先儘可能地使用http2,如果不行的話,會繼續使用HTTP 1.1。

11.5.2 多路複用

正如libcurl想儘可能量維持以前的用法,你需要通過CURLMOPT_PIPELINING參數爲你的程序啓用HTTP/2多路複用功能。不然的話,它會保持一個連接只發送一個請求。

另一個需要注意的小細節是,當你通過libcurl同時請求多個傳輸的時候,請使用多接口模式。這樣能使應用程序能同時啓用任意數量的傳輸。如果你寧願讓libcurl等待也要把它們放到同一個連接來傳輸的話,請使用CURLOPT_PIPEWAIT參數。

11.5.3 服務器推送

libcurl 7.44.0及其後續版本開始支持HTTP/2服務器推送功能。你可以通過在CURLMOPT_PUSHFUNCTION參數中設定一個推送回調來激活該功能。如果應用程序接受了該推送,它將爲CURL建立一個新的傳輸,以便接受內容。

12. 後http2時代

http2做了許多艱難的折衷和妥協。隨着http2逐漸部署,將會帶來一個健全的協議升級方式,而這爲將來更多的協議升級奠定了基礎。同時,它也引入了一套概念和基礎架構來並行處理多個不同版本協議。也許我們並不需要在引入新協議時就完全將舊的淘汰掉。

http2仍然揹負了許多HTTP1的歷史包袱,主要是爲了保證數據流量能夠在HTTP 1和http2之間無礙轉發。這些包袱會阻礙進一步的的開發和創造,期待http3能丟掉其中一部分。

親愛的讀者,你認爲http還缺少什麼?

12.1. QUIC

Google的QUIC (快速UDP互聯網連接)協議是一個非常有趣的試驗,它在很大程度上繼承了SPDY的衣鉢。QUIC是一個UDP版的TCP + TLS + HTTP/2替代實現。

QUIC可以創建更低延遲的連接,並且也像HTTP/2一樣,通過僅僅阻塞部分流解決了包裹丟失這個問題,讓連接在不同網絡上建立變得更簡單 - 這其實正是MPTCP想去解決的問題。

QUIC現在還只有Google的Chrome和它後臺服務器上的實現,雖然有第三方庫libquic,但這些代碼仍然很難在其他地方被複用。該協議也被IETF通信工作組引入了草案

13. 擴展閱讀

如果對讀者你來說,這份文檔的內容或技術細節稍顯淺嘗輒止,下面的資源也許能滿足你的好奇:

  • HTTPBis小組郵件列表和歸檔:https://lists.w3.org/Archives/Public/ietf-http-wg/

  • HTML版本的http2協議規範本體:https://httpwg.github.io/specs/rfc7540.html

  • Firefox http2網絡細節:https://wiki.mozilla.org/Networking/http2

  • curl http2實現細節:https://curl.haxx.se/docs/http2.html

  • http2網站:https://http2.github.io/ ,以及關於http2的FAQ:https://http2.github.io/faq/

  • Ilya Grigorik的“High Performance Browser Networking”一書中關於HTTP/2的章節: https://hpbn.co/http2/

14. 致謝

介紹數據包格式的樂高圖片來自於Mark Nottingham,感謝他給我提供的靈感。

HTTP趨勢數據來自https://httparchive.org/。

RTT圖來自Mike Belshe的演講。

感謝我的孩子Agnes和Rex,他們把樂高玩具借給我作爲head of line的圖片。

感謝以下爲我提供校對和反饋的朋友:Kjell Ericson, Bjorn Reese, Linux Swälas和Anthony Bryan。是你們讓這份文檔變得更好!

在完成文檔的過程中,以下朋友反饋了bug幫助改進文檔:Mikael Olsson, Remi Gacogne, Benjamin Kircher, saivlis, florin-andrei-tp, Brett Anthoine, Nick Parlante, Matthew King, Nicolas Peels, Jon Forrest, sbrickey, Marcin Olak, Gary Rowe, Ben Frain, Mats Linander, Raul Siles, Alex Lee, Richard Moore

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章