likes
comments
collection
share

HTTP2之HPACK头部压缩

作者站长头像
站长
· 阅读数 22

前言

在HTTP/1.x时代,只有消息体才支持压缩,因为一般来说,消息体通常比头部要大,对消息体进行压缩,可以减小数据包的大小,提升传输性能。 但是经过长期的观察,人们发现,HTTP协议的头部存在大量重复的数据,例如:cookieAcceptUser-Agent等等,这些信息基本每次请求都不会改变,但是每次请求都需要重复的传输,实在是没有必要。 我们又知道,对于重复的数据,是很好压缩的,只需要为出现频次极高的词组建立一张索引表,传输的时候只需要一个索引号即可,大大减少了数据传输的长度,这就是压缩算法中极为常见的查表法。 因此,HTTP2协议终于支持对头部进行压缩了!

HPACK

HPACK算法的目标是在HTTP/2中压缩和传输头部字段,以减少数据传输的大小,并使用动态表来提高头部字段的重用性。 HPACK算法的特点有:

  1. 静态字典:HPACK算法使用一个静态的字典,包含了常见的HTTP头部字段名称和一些常见的值的索引。这些索引可以直接引用,而不需要传输它们的完整字符串。
  2. 动态表:HPACK算法还使用了一个动态表,用于在传输过程中存储和重用头部字段。动态表可以存储服务器发送的字段,以及客户端发送的字段。
  3. 基于引用的编码:HPACK算法使用基于引用的编码,即通过引用静态字典和动态表中的索引号来表示头部字段。这样就可以减少传输数据的大小。
  4. 链表表达:HPACK算法使用链表表达头部字段,每个字段包含名称和值。这种表达方式使得字段的重排和扩展更加容易。

具体的压缩过程如下:

  1. 首先,HPACK算法对需要压缩的头部字段进行检查,判断是否在动态表中已经存在。如果字段已经存在且值相同,可以直接引用动态表中的索引号。如果字段已经存在但值不同,需要更新动态表中的值,并生成新的索引号。
  2. 如果字段不在动态表中,HPACK算法会检查字段是否存在于静态字典中。如果在静态字典中找到了对应的索引号,可以直接引用。
  3. 如果字段既不在动态表中,也不在静态字典中,HPACK算法会使用字面量的方式表示字段。这时需要传输字段的名称和值。
  4. 名称和值除了可以明文传输外,还可以选择采用哈夫曼编码进一步压缩。

静态表

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属性配置了动态表的最大大小,通过SETTINGSFrame传输,默认是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各种类型的格式: HTTP2之HPACK头部压缩 HTTP2之HPACK头部压缩 HTTP2之HPACK头部压缩 HTTP2之HPACK头部压缩 HTTP2之HPACK头部压缩

哈夫曼编码

未被索引的头部name和value,除了明文传输,还可以选择使用哈夫曼编码进一步压缩。 如何标记是否使用哈夫曼编码呢? Name Length和Value Length拿出一个Bit来标记是否使用哈夫曼编码,如果最高位是1代表使用哈夫曼编码,否则使用Ascii字符集。