hpack由来:
http2以前的头部报文都是文本形式发生,http2为了优化网络对头部报文进行压缩编码使其内容更精简,发送更少的数据加快网络传输。采用压缩算法就是hpack。
该图说明了http2头部报文的压缩过程
首先把头部的键值对内容根据对应的表进行转换,最后经过编码生成最终的压缩后的数据。
hpack表
http2中会把经常使用的头部键值对进行表格化(可以理解为数据缓存),使其可以通过索引进行数据关联。如果头部键值已经存在直接使用索引进行传输,对方便可解析对应数据内容。
hapck表分为静态表和动态表。
静态表
静态表总共61项,动态表索引从62开始
hapck编码
hapck 编码使用两种原始类型: 无符号可变长度整数和八位字节表示的字符串,相应地规定了以下两种编码方式
整数编码
一个整数编码可以用于表示字段索引值、首部条目索引值或者字符串长度。
一个整数编码含两部分: 一个前缀字节和可选的后跟字节序列,只有前缀字节不足以表达整数值时才需要后跟字节,前缀字节中可用比特位 N 是整数编码的一个参数
比如下面所示的是一个 N=5 的整数编码(前三比特用于其他标识),如果我们要编码的整数值小于 2^N - 1,直接用一个前缀字节表示即可,比如 10 就用 ???01010 表示
+---+---+---+---+---+---+---+---+
| ? | ? | ? | Value |
+---+---+---+-------------------+
如果要编码的整数值 X 大于等于 2^N - 1,前缀字节的可用比特位都设成 1,然后把 X 减去 2^N - 1 得到值 R,并用一个或多个字节序列表示 R,字节序列中每个字节的最高有效位 (msb) 用于表示是否结束,msb 设为 0 时代表是最后一个字节。具体编码看下面的伪代码和例子
+---+---+---+---+---+---+---+---+
| ? | ? | ? | 1 1 1 1 1 |
+---+---+---+-------------------+
| 1 | Value-(2^N-1) LSB |
+---+---------------------------+
+---+---------------------------+
| 0 | Value-(2^N-1) MSB |
+---+---------------------------+
比如使用 N=5 的整数编码表示 1337:
1337 大于 31 (2^5 - 1), 将前缀字节后五位填满 1
I = 1337 - (2^5 - 1) = 1306
I 仍然大于 128, I % 128 = 26, 26 + 128 = 154
154 二进制编码: 10011010, 这即是第一个后跟字节
I = 1306 / 128 = 10, I 小于 128, 循环结束
将 I 编码成二进制: 00001010, 这即是最后一个字节
+---+---+---+---+---+---+---+---+
| X | X | X | 1 | 1 | 1 | 1 | 1 | Prefix = 31, I = 1306
| 1 | 0 | 0 | 1 | 1 | 0 | 1 | 0 | 1306 >= 128, encode(154), I=1306/128=10
| 0 | 0 | 0 | 0 | 1 | 0 | 1 | 0 | 10 < 128, encode(10), done
+---+---+---+---+---+---+---+---+
解码时读取第一个字节,发现后五位 (11111) 对应的值 I 等于 31(>= 2^N - 1),说明还有后跟字节;令 M=0,继续读下一个字节 B,I = I + (B & 127) * 2^M = 31 + 26 * 1 = 57,M = M + 7 = 7,最高有效位为 1,表示字节序列未结束,B 指向下一个字节;I = I + (B & 127) * 2^M = 57 + 10 * 128 = 1337,最高有效位为 0,表示字节码结束,返回 I
字符编码
一个字符串可能代表 Header 条目的字段或者键值。字符编码使用字节序列表示,要么直接使用字符的八位字节码要么使用哈夫曼编码。
+---+---+---+---+---+---+---+---+
| H | String Length (7+) |
+---+---------------------------+
| String Data (Length octets) |
+-------------------------------+
H: 一个比特位表示是否使用哈夫曼编码
String Length: 代表字节序列长度,即 String Data 的长度,使用 N=7 的整数编码方式表示
String Data: 字符串的八位字节码序列表示,如果 H 为 0,则此处就是原字符的八位字节码表示;如果 H 为 1,则此处为原字符的哈夫曼编码
RFC 7541 给出了一份字符的哈夫曼编码表: Huffman Code,这是基于大量 HTTP 首部数据生成的哈夫曼编码。
当中第一列 (sym) 表示要编码的字符,最后的特殊字符 “EOS” 代表字符串结束
第二列 (code as bits) 是二进制哈夫曼编码,向最高有效位对齐
第三列 (code as hex) 是十六进制哈夫曼编码,向最低有效位对齐
最后一列 (len) 代表编码长度,单位 bit
例子:
:authority: blog.wangriyu.wang 首部对应的编码为:
41 8e 8e 83 cc bf 81 d5 35 86 f5 6a fe 07 54 df
41 (0100 0001) 表示字段存在索引值 1,即对应静态表中第一项 :authority
8e (1000 1110) 最高有效位为 1 表示键值使用哈夫曼编码,000 1110 表示字节序列长度为 14
后面 8e 83 cc bf 81 d5 35 86 f5 6a fe 07 54 df 是一段哈夫曼编码序列
由哈夫曼编码表可知 100011 -> ‘b’, 101000 -> ‘l’, 00111 -> ‘o’, 100110 -> ‘g’, 010111 -> ‘.’, 1111000 -> ‘w’, 00011 -> ‘a’, 101010 -> ‘n’, 100110 -> ‘g’, 101100 -> ‘r’, 00110 -> ‘i’, 1111010 -> ‘y’, 101101 -> ‘u’
hapck编码规范简要说明
已索引首部条目表示 (Indexed Header Field Representation)
以 1 开始为标识,能在索引空间匹配到索引的首部会替换成这种形式,后面的 index 使用上述的整数编码方式且 N = 7。
比如 :method: GET 可以用 0x82,即 10000010 表示
+---+---+---+---+---+---+---+---+
| 1 | Index (7+) |
+---+---------------------------+
未索引文字首部条目表示 (Literal Header Field Representation)
尚未被索引的首部有三种表示形式,第一种会添加进索引,第二种对于当前跳来说不会添加进索引,第三种绝对不被允许添加进索引
1.会添加索引的文字首部 (Literal Header Field with Incremental Indexing)
以 01 开始为标识,此首部会加入到解码后的首部列表 (Header List) 中并且会把它作为新条目插入到动态索引表中
+---+---+---+---+---+---+---+---+
| 0 | 1 | Index (6+) |
+---+---+-----------------------+
| H | Value Length (7+) |
+---+---------------------------+
| Value String (Length octets) |
+-------------------------------+
2.不添加索引的首部 (Literal Header Field without Indexing)
以 0000 开始为标识,此首部会加入到解码后的首部列表中,但不会插入到动态索引表中
Literal Header Field without Indexing — Indexed Name
如果字段已经存在索引,但键值未被索引,则会替换成如下形式 (index 使用 N=4 的整数编码表示)
+---+---+---+---+---+---+---+---+
| 0 | 0 | 0 | 0 | Index (4+) |
+---+---+-----------------------+
| H | Value Length (7+) |
+---+---------------------------+
| Value String (Length octets) |
+-------------------------------+
如果字段和键值均未被索引,则会替换成如下形式。
+---+---+---+---+---+---+---+---+
| 0 | 0 | 0 | 0 | 0 |
+---+---+-----------------------+
| H | Name Length (7+) |
+---+---------------------------+
| Name String (Length octets) |
+---+---------------------------+
| H | Value Length (7+) |
+---+---------------------------+
| Value String (Length octets) |
+-------------------------------+
3.绝对不添加索引的首部 (Literal Header Field Never Indexed)
这与上一种首部类似,只是标识为 0001,首部也是会添加进解码后的首部列表中但不会插入动态更新表。
区别在于这类首部发出是什么格式表示,接收也是一样的格式,作用于每一跳 (hop),如果中间通过代理,代理必须原样转发不能另行编码。
而上一种首部只是作用当前跳,通过代理后可能会被重新编码
+---+---+---+---+---+---+---+---+
| 0 | 0 | 0 | 1 | Index (4+) |
+---+---+-----------------------+
| H | Value Length (7+) |
+---+---------------------------+
| Value String (Length octets) |
+-------------------------------+
参考链接:
HTTP2 详解
Wangriyu’s Blog Halfrost-Field/HTTP:2_Header-Compression.md at master · halfrost/Halfrost-Field · GitHub