HTTP2之HPACK头部压缩
前言
在HTTP/1.x时代,只有消息体才支持压缩,因为一般来说,消息体通常比头部要大,对消息体进行压缩,可以减小数据包的大小,提升传输性能。
但是经过长期的观察,人们发现,HTTP协议的头部存在大量重复的数据,例如:cookie
、Accept
、User-Agent
等等,这些信息基本每次请求都不会改变,但是每次请求都需要重复的传输,实在是没有必要。
我们又知道,对于重复的数据,是很好压缩的,只需要为出现频次极高的词组建立一张索引表,传输的时候只需要一个索引号即可,大大减少了数据传输的长度,这就是压缩算法中极为常见的查表法
。
因此,HTTP2协议终于支持对头部进行压缩了!
HPACK
HPACK算法的目标是在HTTP/2中压缩和传输头部字段,以减少数据传输的大小,并使用动态表来提高头部字段的重用性。 HPACK算法的特点有:
- 静态字典:HPACK算法使用一个静态的字典,包含了常见的HTTP头部字段名称和一些常见的值的索引。这些索引可以直接引用,而不需要传输它们的完整字符串。
- 动态表:HPACK算法还使用了一个动态表,用于在传输过程中存储和重用头部字段。动态表可以存储服务器发送的字段,以及客户端发送的字段。
- 基于引用的编码:HPACK算法使用基于引用的编码,即通过引用静态字典和动态表中的索引号来表示头部字段。这样就可以减少传输数据的大小。
- 链表表达:HPACK算法使用链表表达头部字段,每个字段包含名称和值。这种表达方式使得字段的重排和扩展更加容易。
具体的压缩过程如下:
- 首先,HPACK算法对需要压缩的头部字段进行检查,判断是否在动态表中已经存在。如果字段已经存在且值相同,可以直接引用动态表中的索引号。如果字段已经存在但值不同,需要更新动态表中的值,并生成新的索引号。
- 如果字段不在动态表中,HPACK算法会检查字段是否存在于静态字典中。如果在静态字典中找到了对应的索引号,可以直接引用。
- 如果字段既不在动态表中,也不在静态字典中,HPACK算法会使用字面量的方式表示字段。这时需要传输字段的名称和值。
- 名称和值除了可以明文传输外,还可以选择采用哈夫曼编码进一步压缩。
静态表
HPACK定义了一个静态表,里面包含61个常用的HTTP头部,如下:
+-------+-----------------------------+---------------+
| Index | Header Name | Header Value |
+-------+-----------------------------+---------------+
| 1 | :authority | |
| 2 | :method | GET |
| 3 | :method | POST |
| 4 | :path | / |
| 5 | :path | /index.html |
| 6 | :scheme | http |
| 7 | :scheme | https |
| 8 | :status | 200 |
| 9 | :status | 204 |
| 10 | :status | 206 |
| 11 | :status | 304 |
| 12 | :status | 400 |
| 13 | :status | 404 |
| 14 | :status | 500 |
| 15 | accept-charset | |
| 16 | accept-encoding | gzip, deflate |
| 17 | accept-language | |
| 18 | accept-ranges | |
| 19 | accept | |
| 20 | access-control-allow-origin | |
| 21 | age | |
| 22 | allow | |
| 23 | authorization | |
| 24 | cache-control | |
| 25 | content-disposition | |
| 26 | content-encoding | |
| 27 | content-language | |
| 28 | content-length | |
| 29 | content-location | |
| 30 | content-range | |
| 31 | content-type | |
| 32 | cookie | |
| 33 | date | |
| 34 | etag | |
| 35 | expect | |
| 36 | expires | |
| 37 | from | |
| 38 | host | |
| 39 | if-match | |
| 40 | if-modified-since | |
| 41 | if-none-match | |
| 42 | if-range | |
| 43 | if-unmodified-since | |
| 44 | last-modified | |
| 45 | link | |
| 46 | location | |
| 47 | max-forwards | |
| 48 | proxy-authenticate | |
| 49 | proxy-authorization | |
| 50 | range | |
| 51 | referer | |
| 52 | refresh | |
| 53 | retry-after | |
| 54 | server | |
| 55 | set-cookie | |
| 56 | strict-transport-security | |
| 57 | transfer-encoding | |
| 58 | user-agent | |
| 59 | vary | |
| 60 | via | |
| 61 | www-authenticate | |
+-------+-----------------------------+---------------+
动态表
除了静态表,HPACK还支持为每个连接单独维护一张动态表。SETTINGS_HEADER_TABLE_SIZE
属性配置了动态表的最大大小,通过SETTINGS
Frame传输,默认是4096个字节。动态表的索引位置从62开始,因为前61属于静态表。
Field Block Fragment
一个HEADERS Frame包含n个Field Block Fragment
,HPACK头部有4种类型:
类型 | 说明 |
---|---|
索引首部字段类型 | 高位以1开头,只有索引号,因为name和value均被索引 |
带递增索引的字符串首部字段 | 高位以01开头,Name 被索引,Value 没被索引,但是会注册到动态表,以备后续使用 |
索引的字符串首部字段 | 高位以 01000000 开头,Name Value 都没被索引,但是会注册到动态表,以备后续使用 |
从不索引的字符串首部字段 | 高位以0000开头,Name 可选是否被索引,适用于频繁变化的头部,绝不会注册到动态表 |
下面是Field Block Fragment各种类型的格式:
哈夫曼编码
未被索引的头部name和value,除了明文传输,还可以选择使用哈夫曼编码进一步压缩。
如何标记是否使用哈夫曼编码呢?
Name Length和Value Length拿出一个Bit来标记是否使用哈夫曼编码,如果最高位是1代表使用哈夫曼编码,否则使用Ascii
字符集。
转载自:https://juejin.cn/post/7288526152216854528