本文首發於 https://jaychen.cc
做者 :jaychen
寫一點東西關於 http2 的東西。css
http2 的前身是由 google 領導開發的 SPDY,後來 google 把整個成果交給 IETF,IETF 把 SPDY 標準化以後變成 http2。google 也很大方的廢棄掉 SPDY,轉向支持 http2。http2 是徹底兼容 http/1.x 的,在此基礎上添加了 4 個主要新特性:html
下面主要講下這 4 個特性。前端
http/1.x 是一個文本協議,而 http2 是一個不折不扣的二進制協議,這也是 http2 能夠折騰出那麼多新花樣的緣由。http2 的二進制協議被稱之爲二進制分幀。算法
http2 協議的格式爲幀,相似 TCP 中的數據報文。chrome
+--------------------------------------------------------------+ ^ | | | | Length (24) | | | | | | | | +----------------------+---------------------------------------+ | | | | + | | | | Type (8) | Flag (8) | Frame Header | | | + +----+-----------------+---------------------------------------+ | | | | | | | | | | R | Stream Identifier (31) | | | | | v +----+---------------------------------------------------------+ | | | Frame Payload | | | +--------------------------------------------------------------+
幀由 Frame Header 和 Frame Payload 組成。以前在 http/1.x 中的 header 和 body 都放在 Frame Payload 中。瀏覽器
Stream Identifier 用來標識該 frame 屬於哪一個 stream。這句話可能感受略突兀,這裏要明白 Stream Identifier 的做用,須要引出 http2 的第二個特性『多路複用』。服務器
在 http/1.x 狀況下,每一個 http 請求都會創建一個 TCP 鏈接,這就意味着每一個請求都須要進行三次握手。這樣子就會浪費比較多的時間和資源,這點在 http/1.x 的狀況下是沒有辦法避免的。而且瀏覽器會限制同一個域名下併發請求的個數。因此,在 http/1.x 的狀況下,一個常見的優化手段是把靜態資源分佈到不一樣域名下,以此來突破瀏覽器併發數的限制。併發
在 http2 的狀況下,全部的請求都會共用一個 TCP 鏈接,這個能夠說是 http2 殺手級的特性了。 由於這點,許多在 http/1.x 時代的優化手段均可以退休了。可是這裏也出現了一個問題,全部的請求都共用一個 TCP 鏈接,那麼客戶端/服務端怎麼知道某一幀(別忘記上面說了 http2 是的基本單位是幀)的數據屬於哪一個請求呢?性能
上面的 Stream Identifier 就是用來標識該幀屬於哪一個請求的。優化
當客戶端同時向服務端發起多個請求,那麼這些請求會被分解成一一個的幀,每一個幀都會在一個 TCP 鏈路中無序的傳輸,同一個請求的幀的 Stream Identifier 都是同樣的。當幀到達服務端以後,就能夠根據 Stream Identifier 來從新組合獲得完整的請求。
在 http/1.x 協議中,每次請求都會攜帶 header 數據,而相似 User-Agent, Accept-Language 等信息在每次請求過程當中幾乎是不變的,那麼這些信息在每次請求過程當中就變成了浪費。因此, http2 中提出了一個 HPACK 的壓縮方式,用於減小 http header 在每次請求中消耗的流量。
HPACK 壓縮的原理以下 :
客戶端和服務端共同維護一個『靜態字典』,字典中每行 3 列,相似下表
index | header name | header value |
---|---|---|
2 | :method | GET |
3 | :method | POST |
當請求的 header 頭部中包含 :mehtod:GET
,客戶端在發送請求的時候,會直接發送靜態字段中對應的 index 值,在這裏也就是 2。服務端在接受到請求的時候,去尋找靜態字典中 index = 2 對應的 header name 和 header value,就明白了客戶端發起了一個 GET 請求。
客戶端和服務端必須維護一套同樣的靜態字典,這裏給出了完整的靜態字典,客戶端和服務端都會遵照這套靜態字典。
你會發現靜態字典中有些 header value 沒有值。這是由於有些 header 字段的值是不定的,好比 User-Agent 字段,因此標準中沒有定下 header value 的值。
那麼若是碰到在靜態字典中 header value 沒有的值,HPEACK 算法會採起下面的方式:
假設 http 請求的 header 中包含了 User-Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36
,那麼 HPACK 會對 User-Agent
的值進行哈夫曼編碼,而後在靜態字典中找到 User-Agent
的 index 爲 58,那麼客戶端會把 User-Agent
的 index 值和 User-Agent
值對應的哈夫曼編碼值發送給服務端。
User-Agent : Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36 會被轉換陳下面的 kv 值發送給服務端: 58 : Huffman('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36')
服務端收到請求以後,把 User-Agent
和哈夫曼編碼值追加到靜態字典後面,這些追加的行稱之爲『動態字典』。
index | header name | header value |
---|---|---|
2 | :method | GET |
3 | :method | POST |
... | .... | ..... |
62 | User-Agent | Huffman('header value') |
客戶端在發送請求的時候,也會把該行添加到本身維護的靜態字典表後面,這樣子客戶端和服務端維護的字典表就會保持一致。以後的請求客戶端若是須要攜帶 User-Agent
字段,只要發送 62 便可。
http2 中狀況就徹底不同了,全部的請求都是在一個 TCP 鏈接中完成的。
服務端推送指的是服務端主動向客戶端推送數據。
舉個例子,index.html 有以下代碼
<!DOCTYPE html> <html> <head> <link rel="stylesheet" href="style.css"> </head> <body> <h1>hello world</h1> <img src="something.png"> </body> </html>
那麼正常狀況下,爲了展現頁面須要發 3 次請求:
若是服務端配置了服務端推送以後,那麼狀況變成下面的樣子:
這樣,服務端和瀏覽器只須要進行一次通訊,就能夠獲取到所有資源。
http2 的目的就是爲了優化 http/1.x 的一些性能問題,因此當 http2 到來以後,不少針對 http/1.x 的優化手段已經無論用。而使用 http2 咱們又應該注意一些什麼問題?
https 和 http2 的恩怨頗有趣。google 在開發 SPDY 的時候是強制使用 https 的,按照道理基於 SPDY 的 http2 也應該是強制 https 的,可是因爲社區的阻礙 http2 能夠不使用 https 協議。可是 chrome 和 firefox 都表示只會開發基於 https 的 http2,因此基本意味着使用 http2 的前提是必須是 https。
在 http/1.x 的時代,爲了減小瀏覽器的請求數/提升瀏覽器的併發數,一般會使用以下的手段來進行優化:
以上的優化手段,在 http2 的狀況下,就顯得沒必要要了。