[译] HPACK:http2中沉默的杀手
如果你有过HTTP/2的相关经验,你可能会知道HTTP/2强大的性能依靠了一些如下的特点,比如流复用、显式流依赖以及服务端推送。
但是还有一个并不明显注意到但是却很重要的功能点,那就是HPACK头部压缩。
这篇文章给出了设计HPACK的一些理由,以及它背后带来的带宽和缩减延时上的收益。
一些背景
你可能知道,常规HTTPS连接事实上是多层模型的多个连接的叠加。你通常关系的最基本的连接就是TCP连接(传输层),在它的上面你会有一个TLS连接(传输层/应用层混合),然后最后是一个HTTP连接(应用层)
多年以前,HTTP压缩是在TLS层使用gzip去处理的。header和body都会被压缩,因为TLS层不感知传输的数据类型,实际上两者都是使用DEFLATE算法进行压缩的。
然后提出了新的专门进行头部压缩的算法SPDY。尽管是为了headers特殊设计,包括使用了一个预处理字典,包括动态Huffman编码和字符串匹配,但是它依旧使用的是DEFLATE算法。
实际上DEFLATE和SPDY都有被攻击的危险,因为攻击者可以从压缩的头部里提取cookie中的授权秘钥:因为DEFLATE使用后向字符串匹配和动态Huffman编码,攻击者可以控制部分请求头部,然后通过修改请求部分然后看压缩之后大小改变多少,来逐步恢复完整cookie。
由于这种风险,大多数的边缘网络都禁用了头部压缩。直到HTTP/2的出现改变了这种窘况
HPACK
HTTP/2支持一种新的专门进行头部压缩的算法,叫做HPACK。HPACK的开发设计考虑到了被攻击的危险,因此可以安全使用。
HPACK能够防御攻击者攻击,因为它没有像DEFLATE一样使用后向字符串匹配和动态Huffman,它使用了下面这三种方法进行压缩:
- 静态字典表:由61个常规header域和一些预定义的values组成的预定义字典表。
- 动态字典表:在连接中遇到的实际header域的列表。这个字典表有大小限制,新的key进来,旧的key可能会被移除。
- Huffman编码:静态的Huffman编码可以对任何字符串进行编码:名称或者是值。这种编码特定地用在HTTP的request/response头中,ASCII码和小写字母的编码会更短。编码最短可能只有5bits长(一个字节8bits),因此最大的原长和压缩长比率为8:5(或者是37.5%的压缩率)
HPACK流
当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还是有可能受到攻击。
Request Headers
HPACK为HTTP提供的收益里面,request会比response收益更大。request的headers能够更有效的进行压缩,因为在header里面有更多的重复项。比如下面是两个requests的header,用的是Chrome浏览器:
Request #1:
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%。
关于http不同版本的区别参考这篇文章https://mp.weixin.qq.com/s/GICbiyJpINrHZ41u\_4zT-A
Response Headers
对于response头部,HPACK的收益相对少一些。
Response #1:
cache-control:public, max-age=30
cf-cache-status:HIT
cf-h2-pushed:,
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头会编码为霍夫曼编码,还有一些匹配上了静态字典表。
Response #2:
cache-control:public, max-age=31536000
cf-bgj100
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…
关于http/1.0 http/1.x http/2的一些相关内容可以参考这篇文章https://mp.weixin.qq.com/s/GICbiyJpINrHZ41u\_4zT-A
转载于//juejin.im/post/5d033d3df265da1baa1e700e
还没有评论,来说两句吧...