中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

HTTP/2如何實現頭部壓縮

發布時間:2022-02-19 09:07:08 來源:億速云 閱讀:239 作者:小新 欄目:開發技術

這篇文章給大家分享的是有關HTTP/2如何實現頭部壓縮的內容。小編覺得挺實用的,因此分享給大家做個參考,一起跟隨小編過來看看吧。

隨著web功能越來越復雜,請求數量越來越多,隨之而來的就是頭部的流量越來越多,并且在建立初次鏈接之后的鏈接也要發送user-agent等信息,是在是一種浪費,因此,http2提出了對請求和響應的頭部進行壓縮,即不再只是壓縮主題部分,這種壓縮方式就是HAPCK。

HTTP/2如何實現頭部壓縮
image-20210818200104438

為什么要壓縮

在 HTTP/1 中,HTTP 請求和響應都是由「狀態行、請求 / 響應頭部、消息主體」三部分組成。一般而言,消息主體都會經過 gzip 壓縮,或者本身傳輸的就是壓縮過后的二進制文件(例如圖片、音頻),但狀態行和頭部卻沒有經過任何壓縮,直接以純文本傳輸。隨著 Web 功能越來越復雜,每個頁面產生的請求數也越來越多,根據 HTTP Archive 的統計,當前平均每個頁面都會產生上百個請求。越來越多的請求導致消耗在頭部的流量越來越多,尤其是每次都要傳輸 UserAgent、Cookie 這類不會頻繁變動的內容,完全是一種浪費。

以下是我隨手打開的一個頁面的抓包結果。可以看到,傳輸頭部的網絡開銷超過 100kb,比 HTML 還多:

HTTP/2如何實現頭部壓縮
揭秘 HTTP/2 頭部壓縮技術揭秘 HTTP/2 頭部壓縮技術

下面是其中一個請求的明細。可以看到,為了獲得 58 字節的數據,在頭部傳輸上花費了好幾倍的流量:

HTTP/2如何實現頭部壓縮
揭秘 HTTP/2 頭部壓縮技術揭秘 HTTP/2 頭部壓縮技術

HTTP/1 時代,為了減少頭部消耗的流量,有很多優化方案可以嘗試,例如合并請求、啟用 Cookie-Free 域名等等,但是這些方案或多或少會引入一些新的問題,這里不展開討論。

壓縮后的效果

首先直接上圖。下圖選中的 Stream 是首次訪問本站,瀏覽器發出的請求頭:

HTTP/2如何實現頭部壓縮
揭秘 HTTP/2 頭部壓縮技術揭秘 HTTP/2 頭部壓縮技術

從圖片中可以看到這個 HEADERS 流的長度是 206 個字節,而解碼后的頭部長度有 451 個字節。由此可見,壓縮后的頭部大小減少了一半多。

然而這就是全部嗎?再上一張圖。下圖選中的 Stream 是點擊本站鏈接后,瀏覽器發出的請求頭:

HTTP/2如何實現頭部壓縮
揭秘 HTTP/2 頭部壓縮技術揭秘 HTTP/2 頭部壓縮技術

可以看到這一次,HEADERS 流的長度只有 49 個字節,但是解碼后的頭部長度卻有 470 個字節。這一次,壓縮后的頭部大小幾乎只有原始大小的 1/10。

為什么前后兩次差距這么大呢?我們把兩次的頭部信息展開,查看同一個字段兩次傳輸所占用的字節數:

HTTP/2如何實現頭部壓縮
揭秘 HTTP/2 頭部壓縮技術揭秘 HTTP/2 頭部壓縮技術

HTTP/2如何實現頭部壓縮 對比后可以發現,第二次的請求頭部之所以非常小,是因為大部分鍵值對只占用了一個字節。尤其是 UserAgent、Cookie 這樣的頭部,首次請求中需要占用很多字節,后續請求中都只需要一個字節。

技術原理

下面這張截圖,取自 Google 的性能專家 Ilya Grigorik 在 Velocity 2015 • SC 會議中分享的「HTTP/2 is here, let’s optimize!」,非常直觀地描述了 HTTP/2 中頭部壓縮的原理:

HTTP/2如何實現頭部壓縮
揭秘 HTTP/2 頭部壓縮技術揭秘 HTTP/2 頭部壓縮技術

我再用通俗的語言解釋下,頭部壓縮需要在支持 HTTP/2 的瀏覽器和服務端之間:維護一份相同的靜態字典(Static Table),包含常見的頭部名稱,以及特別常見的頭部名稱與值的組合;

  • 維護一份相同的靜態字典(Static Table),包含常見的頭部名稱,以及特別常見的頭部名稱與值的組合
  • 維護一份相同的動態字典(Dynamic Table),可以動態地添加內容
  • 支持基于靜態哈夫曼碼表的哈夫曼編碼(Huffman Coding)

靜態字典的作用有兩個:1)對于完全匹配的頭部鍵值對,例如 :method: GET,可以直接使用一個字符表示;2)對于頭部名稱可以匹配的鍵值對,例如 cookie: xxxxxxx,可以將名稱使用一個字符表示。HTTP/2 中的靜態字典如下

IndexHeader NameHeader Value
1:authority
2:methodGET
3:methodPOST
4:path/
5:path/index.html
6:schemehttp
7:schemehttps
8:status200
………
32cookie
………
60via
61www-authenticate

顯示詳細信息

同時,瀏覽器可以告知服務端,將 cookie: xxxxxxx 添加到動態字典中,這樣后續整個鍵值對就可以使用一個字符表示了。類似的,服務端也可以更新對方的動態字典。需要注意的是,動態字典上下文有關,需要為每個 HTTP/2 連接維護不同的字典,使用字典可以極大地提升壓縮效果,其中靜態字典在首次請求中就可以使用。對于靜態、動態字典中不存在的內容,還可以使用哈夫曼編碼來減小體積。HTTP/2 使用了一份靜態哈夫曼碼表(詳見),也需要內置在客戶端和服務端之中。 這里順便說一下,HTTP/1 的狀態行信息(Method、Path、Status 等),在 HTTP/2 中被拆成鍵值對放入頭部(冒號開頭的那些),同樣可以享受到字典和哈夫曼壓縮。另外,HTTP/2 中所有頭部名稱必須小寫。

實現細節

了解了 HTTP/2 頭部壓縮的基本原理,最后我們來看一下具體的實現細節。HTTP/2 的頭部鍵值對有以下這些情況:

1)整個頭部鍵值對都在字典中

 0   1   2   3   4   5   6   7
+---+---+---+---+---+---+---+---+
| 1 |        Index (7+)         |
+---+---------------------------+

這是最簡單的情況,使用一個字節就可以表示這個頭部了,最左一位固定為 1,之后七位存放鍵值對在靜態或動態字典中的索引。例如下圖中,頭部索引值為 2(0000010),在靜態字典中查詢可得 :method: GET。

HTTP/2如何實現頭部壓縮
揭秘 HTTP/2 頭部壓縮技術揭秘 HTTP/2 頭部壓縮技術

2)頭部名稱在字典中,更新動態字典

 0   1   2   3   4   5   6   7
+---+---+---+---+---+---+---+---+
| 0 | 1 |      Index (6+)       |
+---+---+-----------------------+
| H |     Value Length (7+)     |
+---+---------------------------+
| Value String (Length octets)  |
+-------------------------------+

對于這種情況,首先需要使用一個字節表示頭部名稱:左兩位固定為 01,之后六位存放頭部名稱在靜態或動態字典中的索引。接下來的一個字節第一位 H 表示頭部值是否使用了哈夫曼編碼,剩余七位表示頭部值的長度 L,后續 L 個字節就是頭部值的具體內容了。例如下圖中索引值為 32(100000),在靜態字典中查詢可得 cookie;頭部值使用了哈夫曼編碼(1),長度是 28(0011100);接下來的 28 個字節是 cookie 的值,將其進行哈夫曼解碼就能得到具體內容。

HTTP/2如何實現頭部壓縮
揭秘 HTTP/2 頭部壓縮技術揭秘 HTTP/2 頭部壓縮技術

客戶端或服務端看到這種格式的頭部鍵值對,會將其添加到自己的動態字典中。后續傳輸這樣的內容,就符合第 1 種情況了。

3)頭部名稱不在字典中,更新動態字典

 0   1   2   3   4   5   6   7
+---+---+---+---+---+---+---+---+
| 0 | 1 |           0           |
+---+---+-----------------------+
| H |     Name Length (7+)      |
+---+---------------------------+
|  Name String (Length octets)  |
+---+---------------------------+
| H |     Value Length (7+)     |
+---+---------------------------+
| Value String (Length octets)  |
+-------------------------------+

這種情況與第 2 種情況類似,只是由于頭部名稱不在字典中,所以第一個字節固定為 01000000;接著申明名稱是否使用哈夫曼編碼及長度,并放上名稱的具體內容;再申明值是否使用哈夫曼編碼及長度,最后放上值的具體內容。例如下圖中名稱的長度是 5(0000101),值的長度是 6(0000110)。對其具體內容進行哈夫曼解碼后,可得 pragma: no-cache。

HTTP/2如何實現頭部壓縮
揭秘 HTTP/2 頭部壓縮技術揭秘 HTTP/2 頭部壓縮技術

客戶端或服務端看到這種格式的頭部鍵值對,會將其添加到自己的動態字典中。后續傳輸這樣的內容,就符合第 1 種情況了。

4)頭部名稱在字典中,不允許更新動態字典

 0   1   2   3   4   5   6   7
+---+---+---+---+---+---+---+---+
| 0 | 0 | 0 | 1 |  Index (4+)   |
+---+---+-----------------------+
| H |     Value Length (7+)     |
+---+---------------------------+
| Value String (Length octets)  |
+-------------------------------+

這種情況與第 2 種情況非常類似,唯一不同之處是:第一個字節左四位固定為 0001,只剩下四位來存放索引了,如下圖:

HTTP/2如何實現頭部壓縮
揭秘 HTTP/2 頭部壓縮技術揭秘 HTTP/2 頭部壓縮技術

這里需要介紹另外一個知識點:對整數的解碼。上圖中第一個字節為 00011111,并不代表頭部名稱的索引為 15(1111)。第一個字節去掉固定的 0001,只剩四位可用,將位數用 N 表示,它只能用來表示小于「2 ^ N – 1 = 15」的整數 I。對于 I,需要按照以下規則求值(RFC 7541 中的偽代碼,via):

if I return I         # I 小于 2 ^ N - 1 時,直接返回else   M = 0
   repeat
       B = next octet             # 讓 B 等于下一個八位       I = I + (B & 127) * 2 ^ M  # I = I + (B 低七位 * 2 ^ M)       M = M + 7
   while B & 128 == 128           # B 最高位 = 1 時繼續,否則返回 I   return I

對于上圖中的數據,按照這個規則算出索引值為 32(00011111 00010001,15 + 17),代表 cookie。需要注意的是,協議中所有寫成(N+)的數字,例如 Index (4+)、Name Length (7+),都需要按照這個規則來編碼和解碼。

這種格式的頭部鍵值對,不允許被添加到動態字典中(但可以使用哈夫曼編碼)。對于一些非常敏感的頭部,比如用來認證的 Cookie,這么做可以提高安全性。

5)頭部名稱不在字典中,不允許更新動態字典

 0   1   2   3   4   5   6   7
+---+---+---+---+---+---+---+---+
| 0 | 0 | 0 | 1 |       0       |
+---+---+-----------------------+
| H |     Name Length (7+)      |
+---+---------------------------+
|  Name String (Length octets)  |
+---+---------------------------+
| H |     Value Length (7+)     |
+---+---------------------------+
| Value String (Length octets)  |
+-------------------------------+

這種情況與第 3 種情況非常類似,唯一不同之處是:第一個字節固定為 00010000。這種情況比較少見,沒有截圖,各位可以腦補。同樣,這種格式的頭部鍵值對,也不允許被添加到動態字典中,只能使用哈夫曼編碼來減少體積。

實際上,協議中還規定了與 4、5 非常類似的另外兩種格式:將 4、5 格式中的第一個字節第四位由 1 改為 0 即可。它表示「本次不更新動態詞典」,而 4、5 表示「絕對不允許更新動態詞典」。區別不是很大,這里略過。

明白了頭部壓縮的技術細節,理論上可以很輕松寫出 HTTP/2 頭部解碼工具了。我比較懶,直接找來 node-http2 中的 compressor.js 驗證一下:

var Decompressor = require('./compressor').Decompressor;

var testLog = require('bunyan').createLogger({name: 'test'});
var decompressor = new Decompressor(testLog, 'REQUEST');

var buffer = new Buffer('820481634188353daded6ae43d3f877abdd07f66a281b0dae053fad0321aa49d13fda992a49685340c8a6adca7e28102e10fda9677b8d05707f6a62293a9d810020004015309ac2ca7f2c3415c1f53b0497ca589d34d1f43aeba0c41a4c7a98f33a69a3fdf9a68fa1d75d0620d263d4c79a68fbed00177febe58f9fbed00177b518b2d4b70ddf45abefb4005db901f1184ef034eff609cb60725034f48e1561c8469669f081678ae3eb3afba465f7cb234db9f4085aec1cd48ff86a8eb10649cbf', 'hex');

console.log(decompressor.decompress(buffer));

decompressor._table.forEach(function(row, index) {
   console.log(index + 1, row[0], row[1]);
});

頭部原始數據來自于本文第三張截圖,運行結果如下(靜態字典只截取了一部分):

{ ':method': 'GET',
 ':path': '/',
 ':authority': 'imququ.com',
 ':scheme': 'https',
 'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:41.0) Gecko/20100101 Firefox/41.0',
 accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
 'accept-language': 'en-US,en;q=0.5',
 'accept-encoding': 'gzip, deflate',
 cookie: 'v=47; u=6f048d6e-adc4-4910-8e69-797c399ed456',
 pragma: 'no-cache' }
1 ':authority' ''2 ':method' 'GET'3 ':method' 'POST'4 ':path' '/'5 ':path' '/index.html'6 ':scheme' 'http'7 ':scheme' 'https'8 ':status' '200'... ...
32 'cookie' ''... ...
60 'via' ''61 'www-authenticate' ''62 'pragma' 'no-cache'63 'cookie' 'u=6f048d6e-adc4-4910-8e69-797c399ed456'64 'accept-language' 'en-US,en;q=0.5'65 'accept' 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'66 'user-agent' 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:41.0) Gecko/20100101 Firefox/41.0'67 ':authority' 'imququ.com'

可以看到,這段從 Wireshark 拷出來的頭部數據可以正常解碼,動態字典也得到了更新(62 – 67)。

總結

在進行 HTTP/2 網站性能優化時很重要一點是「使用盡可能少的連接數」,本文提到的頭部壓縮是其中一個很重要的原因:同一個連接上產生的請求和響應越多,動態字典積累得越全,頭部壓縮效果也就越好。所以,針對 HTTP/2 網站,最佳實踐是不要合并資源,不要散列域名。

默認情況下,瀏覽器會針對這些情況使用同一個連接:

  • 同一域名下的資源;
  • 不同域名下的資源,但是滿足兩個條件:1)解析到同一個 IP;2)使用同一個證書;

上面第一點容易理解,第二點則很容易被忽略。實際上 Google 已經這么做了,Google 一系列網站都共用了同一個證書,可以這樣驗證:

$ openssl s_client -connect google.com:443 |openssl x509 -noout -text | grep DNS

depth=2 C = US, O = GeoTrust Inc., CN = GeoTrust Global CA
verify error:num=20:unable to get local issuer certificate
verify return:0
               DNS:*.google.com, DNS:*.android.com, DNS:*.appengine.google.com, DNS:*.cloud.google.com, DNS:*.google-analytics.com, DNS:*.google.ca, DNS:*.google.cl, DNS:*.google.co.in, DNS:*.google.co.jp, DNS:*.google.co.uk, DNS:*.google.com.ar, DNS:*.google.com.au, DNS:*.google.com.br, DNS:*.google.com.co, DNS:*.google.com.mx, DNS:*.google.com.tr, DNS:*.google.com.vn, DNS:*.google.de, DNS:*.google.es, DNS:*.google.fr, DNS:*.google.hu, DNS:*.google.it, DNS:*.google.nl, DNS:*.google.pl, DNS:*.google.pt, DNS:*.googleadapis.com, DNS:*.googleapis.cn, DNS:*.googlecommerce.com, DNS:*.googlevideo.com, DNS:*.gstatic.cn, DNS:*.gstatic.com, DNS:*.gvt1.com, DNS:*.gvt2.com, DNS:*.metric.gstatic.com, DNS:*.urchin.com, DNS:*.url.google.com, DNS:*.youtube-nocookie.com, DNS:*.youtube.com, DNS:*.youtubeeducation.com, DNS:*.ytimg.com, DNS:android.com, DNS:g.co, DNS:goo.gl, DNS:google-analytics.com, DNS:google.com, DNS:googlecommerce.com, DNS:urchin.com, DNS:youtu.be, DNS:youtube.com, DNS:youtubeeducation.com

使用多域名加上相同的 IP 和證書部署 Web 服務有特殊的意義:讓支持 HTTP/2 的終端只建立一個連接,用上 HTTP/2 協議帶來的各種好處;而只支持 HTTP/1.1 的終端則會建立多個連接,達到同時更多并發請求的目的。這在 HTTP/2 完全普及前也是一個不錯的選擇。

感謝各位的閱讀!關于“HTTP/2如何實現頭部壓縮”這篇文章就分享到這里了,希望以上內容可以對大家有一定的幫助,讓大家可以學到更多知識,如果覺得文章不錯,可以把它分享出去讓更多的人看到吧!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

城步| 景东| 鄂伦春自治旗| 托克托县| 沁水县| 盈江县| 光山县| 栾川县| 汝阳县| 大冶市| 乌拉特中旗| 五大连池市| 屯门区| 黎平县| 闸北区| 青神县| 原阳县| 维西| 延长县| 武清区| 洞头县| 台北县| 客服| 马龙县| 定日县| 柳州市| 中阳县| 鹤庆县| 射阳县| 宕昌县| 准格尔旗| 阳春市| 盐津县| 曲靖市| 错那县| 高雄市| 玉山县| 新昌县| 闻喜县| 万山特区| 凌云县|