http2头部压缩-hpack

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

你可能感兴趣的:(http2头部压缩-hpack)