若是你有過HTTP/2的相關經驗,你可能會知道HTTP/2強大的性能依靠了一些以下的特色,好比流複用、顯式流依賴以及服務端推送。css
可是還有一個並不明顯注意到可是卻很重要的功能點,那就是HPACK頭部壓縮。html
這篇文章給出了設計HPACK的一些理由,以及它背後帶來的帶寬和縮減延時上的收益。jquery
你可能知道,常規HTTPS鏈接事實上是多層模型的多個鏈接的疊加。你一般關係的最基本的鏈接就是TCP鏈接(傳輸層),在它的上面你會有一個TLS鏈接(傳輸層/應用層混合),而後最後是一個HTTP鏈接(應用層)nginx
多年之前,HTTP壓縮是在TLS層使用gzip去處理的。header和body都會被壓縮,由於TLS層不感知傳輸的數據類型,實際上二者都是使用DEFLATE算法進行壓縮的。git
而後提出了新的專門進行頭部壓縮的算法SPDY。儘管是爲了headers特殊設計,包括使用了一個預處理字典,包括動態Huffman編碼和字符串匹配,可是它依舊使用的是DEFLATE算法。github
實際上DEFLATE和SPDY都有被攻擊的危險,由於攻擊者能夠從壓縮的頭部裏提取cookie中的受權祕鑰:由於DEFLATE使用後向字符串匹配和動態Huffman編碼,攻擊者能夠控制部分請求頭部,而後經過修改請求部分而後看壓縮以後大小改變多少,來逐步恢復完整cookie。web
因爲這種風險,大多數的邊緣網絡都禁用了頭部壓縮。直到HTTP/2的出現改變了這種窘況算法
HTTP/2支持一種新的專門進行頭部壓縮的算法,叫作HPACK。HPACK的開發設計考慮到了被攻擊的危險,所以能夠安全使用。瀏覽器
HPACK可以防護攻擊者攻擊,由於它沒有像DEFLATE同樣使用後向字符串匹配和動態Huffman,它使用了下面這三種方法進行壓縮:安全
當HPACK須要把一個header編碼爲name:value的形式,它首先會看靜態和動態字典表。若是所有的name:value都有的話,它會簡單地去找字典表的對應條目。 這一般須要1byte空間大小,在大多數狀況下2bytes也就足夠了。整個header編碼成一個byte,太讚了。
由於許多header頭都是重複的,因此上面的策略有很高的成功率。 舉個例子,像這種headers:authority:www.cloudflare.com 或者是一些大的cookie一般是這種狀況。
當HPACK在字典表裏不能匹配整個header的時候,它就會去嘗試找有相同name的header。大多數常見的haeder name會在靜態表裏有,好比content-encoding, cookie, etag。剩下的一些可能會有重複的會在動態表裏。好比Cloudflare會爲每個response分配cf-rayheader,而它的值是不一樣的,可是name是能夠複用的。
若是找到了name,它就能夠再次用1~2個bytes來表示,不然的話會使用其餘的原生編碼或者是Huffman編碼(二者中取短的那個)。header中的value也是一樣的原理。
咱們發現單獨使用Huffman編碼會節省30%的header大小。
儘管HPACK不作字符串匹配,對於攻擊者來講爲了找到header的value,他必須猜想整個字典表條目的value,而不是像DEFLATE同樣逐步匹配,可是HPACK仍是有可能受到攻擊。
HPACK爲HTTP提供的收益裏面,request會比response收益更大。request的headers可以更有效的進行壓縮,由於在header裏面有更多的重複項。好比下面是兩個requests的header,用的是Chrome瀏覽器:
authority: blog. cloudflare. com
method:GET
path: /
scheme:https
accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,/;q=0.8
accept-encoding:gzip, deflate, sdch, br
accept-language:en-US,en;q=0.8
cookie: 297 byte cookie
upgrade-insecure-requests:1
user-agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2853.0 Safari/537.36
我把那些使用靜態字典表的能夠被壓縮的headers標註了紅色。有3個field: method:GET, path:/ 和 scheme:https,它們始終在靜態字典表裏,而且會被編碼成1個byte.一些其餘的field只有name會被編碼爲1byte:authority, accept, accept-encoding, accept-language, cookie , user-agent
全部其餘的部分標記爲綠色,會按照Huffman編碼進行處理。
沒有匹配上的headers,會插到動態字典表裏爲後面的request去使用
讓咱們看一下另一種狀況:
authority:blog.cloudflare.com
method:GET
path:/assets/images/cloudflare-sprite-small.png
scheme:https
accept:image/webp,image/,/*;q=0.8
accept-encoding:gzip, deflate, sdch, br
accept-language:en-US,en;q=0.8
cookie: 297 byte cookie
referer:blog.cloudflare.com/assets/css/…
user-agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2853.0 Safari/537.36
這裏我加上了藍色的編碼域,它們代表了那些header域匹配上了動態字典表。很明顯那些域在不一樣的requests裏面都有重複。這裏面有兩個域又一次出如今靜態字典表裏,也就是說每一個域能夠編碼爲1或者2個bytes串。一個是大概有300byte場的cookie頭,另外一個是大概130byte長的user-agent。把430bytes的長度壓縮到僅僅4個bytes,壓縮了99%。
總之多於全部的重複的request,只有兩三個短字符串會編碼爲Huffman編碼。
這個是Cloudflare在6個小時裏的訪問入口的流量headers的狀況
咱們能夠看到請求來的頭部壓縮了76%。由於headers佔訪問流量大部分,所以全部的訪問流量的空間節約十分可觀咱們能夠看到因爲HPACK壓縮,整個流量數據量大小減小了53%。
對於response頭部,HPACK的收益相對少一些。
cache-control:public, max-age=30
cf-cache-status:HIT
cf-h2-pushed:</assets/css/screen.css?v=2237be22c2>,</assets/js/jquery.fitvids.js?v=2237be22c2>
cf-ray:2ded53145e0c1ffa-DFW
content-encoding:gzip
content-type:text/html; charset=utf-8
date:Wed, 07 Sep 2016 21:41:23 GMT
expires:Wed, 07 Sep 2016 21:41:53 GMT
link:<//cdn.bizible.com/scripts/bizible.js>; rel=preload; as=script,code.jquery.com/jquery-1.11…; rel=preload; as=script
server:cloudflare-nginx
status:200
vary:Accept-Encoding
x-ghost-cache-status:From Cache
x-powered-by:Express
第一個response的大部分header頭會編碼爲霍夫曼編碼,還有一些匹配上了靜態字典表。
cache-control:public, max-age=31536000
cf-bgj:imgq:100
cf-cache-status:HIT
cf-ray:2ded53163e241ffa-DFW
content-type:image/png
date:Wed, 07 Sep 2016 21:41:23 GMT
expires:Thu, 07 Sep 2017 21:41:23 GMT
server:cloudflare-nginx
status:200
vary:Accept-Encoding
x-ghost-cache-status:From Cache
x-powered-by:Express
又一次看到,藍色的部分匹配上了動態字典表,紅色代表匹配上了靜態字典表,綠色部分表明了Huffman編碼的串。
第二個response可能匹配了所有12個headers中的7個。剩下的5個裏面,有4個header的name能夠匹配上,而後有6個字符串會去使用Huffman編碼。
儘管有兩個expires的header是幾乎徹底相同的,它們也僅使用Huffman編碼,由於不能徹底匹配上。
有越多的請求去處理,動態字典表就會越大,就會有越多的headers被匹配上,就會提升壓縮率。
下面是返回的流量裏的header狀況。
平均的壓縮率是69%。可是整個出口流量的影響並無很大。
可能壓縮狀況不容易觀察,可是在整個HTTP/2的出口流量裏,咱們仍是有1.4%的節約量。雖然看起來很少,可是數據壓縮量的在不少狀況仍是有增長的。這個數字也會由於網站處理大文件的時候受到影響。咱們測量了一些網站的節約量在15%左右
blog.cloudflare.com/hpack-the-s…