SPDY 系列協議由谷歌開發,於 2009 年公開。它的設計目標是下降 50% 的頁面加載時間。當下不少著名的互聯網公司,例如百度、淘寶、UPYUN 都在本身的網站或 APP 中採用了 SPDY 系列協議(當前最新版本是 SPDY/3.1),由於它對性能的提高是顯而易見的。主流的瀏覽器(谷歌、火狐、Opera)也都早已經支持 SPDY,它已經成爲了工業標準,HTTP Working-Group 最終決定以 SPDY/2 爲基礎,開發 HTTP/2。html
可是,HTTP/2 跟 SPDY 仍有不一樣的地方,主要是如下兩點:git
相比 HTTP/1.x,HTTP/2 在底層傳輸作了很大的改動和優化:github
HTTP/2 主要是 HTTP/1.x 在底層傳輸機制上的徹底重構,HTTP/2 是基本兼容 HTTP/1.x 的語義的(詳細兼容性說明請戳 這裏)。Content-Type
仍然是 Content-Type
,只不過它再也不是文本傳輸了。那麼 HTTP/2 的這些新特性又是如何實現的呢?算法
Frame 是 HTTP/2 二進制格式的基礎,基本能夠把它理解爲它 TCP 裏面的數據包同樣。HTTP/2 之因此可以有如此多的新特性,正是由於底層數據格式的改變。 Frame 的基本格式以下(圖中的數字表示所佔位數,內容摘自 http2-draft-17):瀏覽器
+-----------------------------------------------+ | Length (24) | +---------------+---------------+---------------+ | Type (8) | Flags (8) | +-+-------------+---------------+-------------------+ |R| Stream Identifier (31) | +=+=================================================+ | Frame Payload (0...) ... +---------------------------------------------------+
flags &= 0x01
),表示 END_STREAM,說明這個 Frame 是流的最後一個數據包。Frame 由 Frame Header 和 Frame Payload 兩部分組成。不管是原來的 HTTP Header 仍是 HTTP Body,在 HTTP/2 中,都將這些數據存儲到 Frame Payload,組成一個個 Frame,再發送響應/請求。經過 Frame Header 中的 Type 區分這個 Frame 的類型。因而可知語義並無太大變化,而是數據的格式變成二進制的 Frame。兩者的轉換和關係以下圖:緩存
圖片引用自這裏服務器
若是咱們約定將經常使用的請求好比 GET /index.html
用一個 1 來表示,POST /index.html
用 2 來表示。那麼是否是能夠節省不少字節?網絡
爲 HTTP/2 的專門量身打造的 HPACK 即是相似這樣的思路延伸。它使用一份索引表來定義經常使用的 HTTP Header。把經常使用的 HTTP Header 存放在表裏。請求的時候便只須要發送在表裏的索引位置便可。例如 :method=GET
使用索引值 2 表示,:path=/index.html
使用索引值 5 表示(完整的列表參考:HPACK Static Table)。只要給服務端發送一個 Frame,該 Frame 的 Payload 部分存儲 0x8285
,Frame 的 Type 設置爲 Header 類型,即可表示這個 Frame 屬於 HTTP Header,請求的內容是:併發
GET /index.html
爲何是 0x8285
,而不是 0x0205
? 這是由於高位設置爲 1 表示這個字節是一個徹底索引值(key 和 value 都在索引中)。相似的,經過高位的標誌位能夠區分出這個字節是屬於一個徹底索引值,仍是僅索引了 key,仍是 key 和 value 都沒有索引。由於索引表的大小的是有限的,它僅保存了一些經常使用的 HTTP Header,同時每次請求還能夠在表的末尾動態追加新的 HTTP Header 緩存。動態部分稱之爲 Dynamic Table。Static Table 和 Dynamic Table 在一塊兒組合成了索引表:性能
<---------- Index Address Space ----------> <-- Static Table --> <-- Dynamic Table --> +---+-----------+---+ +---+-----------+---+ | 1 | ... | s | |s+1| ... |s+k| +---+-----------+---+ +---+-----------+---+ ^ | | V Insertion Point Dropping Point
HPACK 不只僅經過索引鍵值對來下降數據量,同時還會將字符串進行霍夫曼編碼來壓縮字符串大小。
以經常使用的 User-Agent
爲例,它在靜態表中的索引值是 58,它的值是不存在表中的,由於它的值是多變的。第一次請求的時候它的 key 用 58 表示,表示這是一個 User-Agent
,它的值部分會進行霍夫曼編碼(若是編碼後的字符串變動長了,則不採用霍夫曼編碼)。服務端收到請求後,會將這個 User-Agent
添加到 Dynamic Table 緩存起來,分配一個新的索引值。客戶端下一次請求時,假設上次請求User-Agent
的在表中的索引位置是 62, 此時只須要發送 0xBE
(一樣的,高位置 1),即可以表明: User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.146 Safari/537.36
。其過程以下圖所示:
最終,相同的 Header 只須要發送索引值,新的 Header 會從新加入 Dynamic Table。
每一個 Frame Header 都有一個 Stream ID 就是被用於實現該特性。每次請求/響應使用不一樣的 Stream ID。就像同一個 TCP 連接上的數據包經過 IP:PORT
來區分出數據包去往哪裏同樣。經過 Stream ID 標識,全部的請求和響應均可以歡快的同時跑在一條 TCP 連接上了。 下圖是 http 和 spdy(http2 的模型和 spdy 是相似的) 的併發模型對比:
當流併發時,就會涉及到流的優先級和依賴。優先級高的流會被優先發送。圖片請求的優先級要低於 CSS 和 SCRIPT,這個設計能夠確保重要的東西能夠被優先加載完。
當服務端須要主動推送某個資源時,便會發送一個 Frame Type 爲 PUSH_PROMISE 的 Frame,裏面帶了 PUSH 須要新建的 Stream ID。意思是告訴客戶端:接下來我要用這個 ID 向你發送東西,客戶端準備好接着。客戶端解析 Frame 時,發現它是一個 PUSH_PROMISE 類型,便會準備接收服務端要推送的流。
結束語
本文簡化了不少 HTTP/2 協議中的具體細節,只描述了 HTTP/2 中主要特性實現的基本過程。
若是你想實現一個支持 HTTP/2 的服務器,那麼你能夠移步 HTTP/2 官網 作更多瞭解,它還提供了一份已經實現 HTTP/2 的項目列表:https://github.com/http2/http... 。
另外,關於 HTTP/2 性能如何,能夠參考官方小組給出的例子:https://http2.akamai.com/demo。
UPYUN 在不久的未來也會加入對 HTTP/2 協議支持,爲用戶提供更好更快的雲加速服務。
追加:目前又拍雲已全網支持 HTTP/2 協議及 SPDY3.1協議。
又拍雲 CDN 當前已全平臺支持 HTTP/2,並已默認開啓。又因 HTTP/2 是在 HTTPS 協議的基礎上實現的,因此只要使用又拍雲 HTTPS 加速服務的域名,均可免費享受 HTTP/2 服務,無需作任何特殊配置。而開啓HTTPS,只需完成證書申請與管理,無須繁雜流程,輕鬆實現網站與 Web 應用的 HTTPS 加密部署。