1 简介
在HTTP/1.1(请参阅[RFC7230])中,头部字段未被压缩。随着网页数量增长到需要数十到数百个请求,这些请求中的冗余头部字段会不必要地占用带宽,从而显着增加延迟。 SPDY [SPDY]最初通过使用DEFLATE [DEFLATE]格式压缩头部字段来解决这个冗余问题,事实证明,它在高效地表示冗余头部字段时非常有效。但是,这种方法暴露了CRIME(Compression Ratio Info-leak Made Easy)攻击所证明的安全风险(参见[CRIME])。
该规范定义了HPACK
,一种新型压缩器,可消除冗余报头字段,限制已知安全攻击的漏洞,并且在受限环境中使用有限的内存要求。 HPACK的潜在安全问题将在第7节中介绍。
HPACK格式有意地简单
且不灵活
。这两个特性都降低了由于实现错误而导致的互操作性或安全问题的风险。没有定义可扩展性机制;只有通过定义一个完整的替换才能更改格式。
本规范中定义的格式 将头部字段列表视为可包含重复对的名称 - 值
对的有序集合。名称和值被认为是不透明的八位字节序列,并且在压缩和解压缩后,头字段的顺序被保留。编码由头部字段表通知,头部字段表将头部字段映射为索引值。
2 压缩过程概述
本规范没有描述编码器的具体算法。 相反,它精确定义了解码器如何操作,允许编码器生成此定义允许的任何编码。
2.1 头部列表排序
HPACK保留头部列表中头部字段的排序。
编码器必须根据在原始头部列表中的排序顺序在头部块中排列头部字段表示。
解码器必须根据它们在头部块中的排序顺序排列解码的头部列表中的头部字段。
2.2 编解码上下文
为了解压缩头块,解码器只需要维护一个动态表
作为解码上下文。 不需要其他动态状态。 当用于诸如HTTP的双向通信时,由端点维护的编码和解码动态表是完全独立的,即请求和响应动态表是分开的。
2.3.1 静态表
静态表由一个预定义的头部字段静态列表组成。 其条目在附录A中定义。
2.3.2 动态表
动态表格由按先入先出
顺序维护的头部字段列表组成。 动态表中的第一个和最新条目处于最低索引处,并且动态表的最旧条目处于最高索引处。
动态表最初是空的。 在每个头部块被解压缩时添加条目。 动态表可以包含重复的条目(即具有相同名称和相同值的条目)。 因此,重复条目不能被解码器视为错误。
编码器决定如何更新动态表,因此可以控制动态表使用多少内存。 为了限制解码器的内存需求,动态表大小是严格限制的(见4.2节)。 解码器在处理头部字段表示的列表期间更新动态表(见第3.2节)。
2.3.3 索引地址空间
静态表和动态表合并成一个索引地址空间。
1和静态表长度(包括)之间的索引引用静态表中的元素(见第2.3.1节)。
大于静态表长度的索引是指动态表中的元素(见第2.3.2节)。将静态表的长度减去找到动态表中的索引。
严格大于两个表长度总和的索引必须被视为解码错误。 对于s的静态表大小和k的动态表大小,下图显示了整个有效索引地址空间。
2.4 头部字段表示
编码的头部字段可以表示为索引或文字。索引表示法将头字段定义为对静态表或动态表中条目的引用(参见第6.1节)。字面表示通过指定其名称和值来定义标题字段。头字段名称可以用字面表示,或者作为对静态表或动态表中条目的引用。头部字段值由字面表示。定义了三种不同的文字表示法:
- 一个文字表示法,它将头部字段添加为动态表格开头的新条目(请参阅第6.2.1节)。
- 不会将头部字段添加到动态表的文字表示(请参阅第6.2.2节)。
- 一个文字表示,它不会将头部字段添加到动态表中,另外规定这个头部字段总是使用文字表示,特别是当由中介重新编码时(参见第6.2.3节)。此表示旨在保护通过压缩而不会被置于危险中的报头字段值(有关更多详细信息,请参见第7.1.3节)。
为了保护敏感的头部字段值(见第7.1节),可以根据安全考虑来选择这些文字表示之一。头字段名称或头字段值的字面表示可以直接或使用静态霍夫曼编码对八位字节序列进行编码(请参见第5.2节)。
头部块解码
3.1 头部块处理
解码器按顺序处理头部块以重建原始头部列表。 头部块是头部字段表示的串联。 在第6节中描述了不同的可能的头部字段表示。列表中,头部字段不能被删除。 添加到头部列表中的头部字段可以安全地传递给应用程序。
3.2 头部字段表示处理
在本节中定义处理头部块以获取头部列表。 为了确保解码将成功产生头部列表,解码器必须遵守以下规则。 包含在头部块中的所有头部字段表示按其出现的顺序进行处理,如下所述。
一个indexed representation包含以下操作:
对应于静态表或动态表中引用条目的头部字段被附加到解码的头部列表中。
不添加
到动态表的literal representation表示需要执行以下操作:头部字段被附加到解码的头部列表。
添加
到动态表的literal representation表示需要执行以下操作:
- 头部字段被附加到解码的头部列表。
- 头部字段被插入到动态表格的开头。 这种插入可能会导致动态表中以前条目的驱逐(参见第4.4节)。
4 动态表管理
为了限制解码器端的内存需求,动态表的大小受到限制。
4.1 计算表大小
动态表的大小是其条目大小的总和。
一个条目的大小是它的名字长度 + 32
(如5.2节定义的那样),它的值长度以八位字节为单位.
动态表的大小 = (每个 Header 的字节数的和+32) * 键值对个数
条目的大小是使用它的名字和值的长度计算的,没有任何霍夫曼编码应用。
注意:额外的32个八位字节是与入口相关的估计开销。
4.2 表大小的最大值
使用HPACK的协议决定了允许编码器用于动态表的最大大小。在HTTP/2中,此值由SETTINGS_HEADER_TABLE_SIZE
设置确定(请参见[HTTP2]的第6.5.2节)。
编码器可以选择使用比此最大尺寸更小的容量(见第6.3节),但所选尺寸必须保持低于或等于协议设置的最大值。
动态表最大值的变化通过动态表大小更新(见第6.3节)发出。动态表大小更新必须发生在动态表大小更改后的第一个头部块的开始处。在HTTP/2中,这遵循设置确认(参见[HTTP2]的第6.5.3节)。
在传输两个头块之间可以进行多次更新以获得最大表尺寸。如果这个大小在此区间内多次更改,则必须在动态表大小更新中发出该区间中出现的最小最大表大小的信号。最终的最大尺寸总是以信号表示,导致最多两次动态表大小更新。这可以确保解码器能够基于动态表大小的减少来执行逐出(参见第4.3节)。该机制可用于通过将最大大小设置为0来完全清除动态表中的条目,随后可以将其恢复。
4.3 动态表大小更改时的条目逐出
无论何时减少动态表的最大大小,都会从动态表的末尾逐出条目,直到动态表的大小小于或等于最大大小。
4.4 在添加新条目时逐出
在将新条目添加到动态表之前,将从动态表的末尾逐出条目,直到动态表的大小小于或等于(最大大小 - 新条目大小)或直到表为空。
如果新条目的大小小于或等于最大大小,则将该条目添加到表中。 尝试添加大于最大大小的条目并不是错误; 尝试添加大于最大大小的条目会导致表清空所有现有条目并导致出现空表。
新条目可以引用动态表中的条目的名称,该条目在将新条目添加到动态表时将被删除。 如果在插入新条目之前引用的条目从动态表中被逐出,则应注意实施以避免删除引用的名称。
5 原始类型表示
HPACK编码使用两种基本类型:无符号可变长度整数
和八位字节串
。
5.1 整数表示
整数用于表示名称索引,头字段索引或字符串长度。 整数表示可以在八位组中的任何位置开始。 为了优化处理,整数表示总是在八位字节的末尾完成。
整数用两部分表示:填充当前字节的前缀
以及在整数值不适合前缀时使用的可选八位字节列表
。 前缀的位数(称为N)是整数表示的参数。
如果整数值足够小,即严格小于2 ^ N-1,则它被编码在N位前缀内。
否则,前缀的所有位都设置为1,并且减少2 ^ N-1的值使用一个或多个八位字节的列表进行编码。 每个八位字节的最高有效位用作延续标志:除了列表中的最后一个字节外,其值被设置为1。 剩余的八位位组用于编码减小的值。
从八位字节列表中解码整数值,首先反转列表中八位字节的顺序。 然后,对于每个八位组,其最重要的位被删除。 将八位字节的其余位连接起来,并将结果值增加2 ^ N-1以获得整数值。 前缀大小N始终在1到8位之间。 从八位组边界开始的整数将有一个8位前缀。
伪代码来表示一个整数,如下所示:
if I < 2^N - 1, encode I on N bits
else
encode (2^N - 1) on N bits
I = I - (2^N - 1)
while I >= 128
encode (I % 128 + 128) on 8 bits
I = I / 128
encode I on 8 bits
伪代码解码整数 I 如下:
decode I from the next N bits
if I < 2^N - 1, return I
else
M = 0
repeat
B = next octet
I = I + (B & 127) * 2^M
M = M + 7
while B & 128 == 128
return I
这个整数表示允许无限大小的值。 编码器也可能发送大量的零值,这会浪费八位字节并可能用于溢出整数值。 超过实施限制的整数编码 - 值或八位组长度 - 必须被视为解码错误。 根据实施限制,可以为整数的每种不同用途设置不同的限制。
5.2 字符串字面值表示
头部字段名称和头部字段值可以表示为字符串文字。 字符串文字被编码为八位字节序列,可以通过直接编码字符串字节的八位字节或使用霍夫曼编码(参见[HUFFMAN])。
- H:一位标志H,表示字符串的八位位组是否被霍夫曼编码。
- String Length:用于编码字符串文字的八位位组数,编码为带有7位前缀的整数(参见第5.1节)。
- String Data:字符串文字的编码数据。 如果H为’0’,则编码数据是字符串文字的原始八位字节。 如果H是’1’,则编码数据是字符串文字的霍夫曼编码。
编码数据是与字符串文字的每个字节相对应的代码的按位连接。
由于霍夫曼编码数据并不总是在八位字节边界处结束,所以在它之后插入一些填充字符,直到下一个八位字节边界。为了防止这种填充被误解为字符串文字的一部分,使用与EOS
(end-of-string)符号相对应的代码的最高有效位。
在解码时,编码数据末尾的不完整代码将被视为填充和丢弃。严格大于7位的填充必须被视为解码错误。与EOS
符号的最高有效位不对应的填充必须被视为解码错误。包含EOS
符号的Huffman编码的字符串文字必须被视为解码错误。
6 二进制格式
本部分描述每个不同头部字段表示和动态表大小更新指令的详细格式。
6.1 带索引的头部字段表示
索引头部字段表示标识静态表或动态表中的条目(请参阅第2.3节)。如3.2节所述,一个索引的头部字段表示会将头部字段添加到解码的头部列表中。
索引头部字段以’1
‘1位模式开始,随后是匹配头部字段的索引,以带有7位前缀的整数表示(见第5.1节)。没有使用0的索引值。如果在索引头部字段表示中找到它,它必须被视为解码错误。
6.2 文字头部字段表示
一个文字头部字段表示包含一个文字头部字段值。头部字段名称既可以作为文字,也可以通过引用静态表或动态表中的现有表项来提供(请参阅第2.3节)。
本规范定义了三种形式的文字头部字段表示法:带索引
,不带索引
,从不索引
。
6.2.1 带增量索引的文字头字段
具有增量索引表示的文字头部字段导致将头部字段附加到解码的头部列表,并将其作为新条目插入到动态表中。
具有增量索引表示的文字头部字段以’01
‘2位模式开始。
如果头部字段名称与存储在静态表或动态表中的条目的头部字段名称相匹配,则可以使用该条目的索引来表示头部字段名称。在这种情况下,条目的索引被表示为一个具有6位前缀的整数(参见第5.1节)。该值总是非零。
否则,头部字段名称将被表示为一个字符串文字(参见第5.2节)。值为0代替6位索引,后面跟着头部字段名称。头部字段名称表示形式之后是以字符串文字表示的头部字段值(请参阅第5.2节)。
6.2.2 不带索引的文字头部字段
没有索引表示的文字头部字段导致将头部字段附加到解码的头部列表而不改变动态表格。
没有索引表示的文字头部字段以’0000
‘4位模式开始。
如果头部字段名称与存储在静态表或动态表中的条目的头部字段名称相匹配,则可以使用该条目的索引来表示头部字段名称。在这种情况下,条目的索引用一个4位前缀的整数表示(见第5.1节)。该值总是非零。
否则,头部字段名称将被表示为一个字符串文字(参见第5.2节)。值为0代替4位索引,后跟头部字段名称。头部字段名称表示形式之后是以字符串文字表示的头部字段值(请参阅第5.2节)。
6.2.3 从不索引的文字头字段
文字头部字段从未索引的表示导致在不更改动态表格的情况下将头部字段附加到解码的头部列表。中介机构必须使用相同的表示法来编码此头部字段。
从未索引的文字头部字段表示以’0001
‘4位模式开始。
当头字段被表示为从未索引的文字头字段时,它必须始终使用此特定字面表示法进行编码。特别是,当一个对等体发送一个它接收到的头域,它表示为一个从未索引过的文本头域时,它必须使用相同的表示来转发这个头域。
此表示旨在保护通过压缩而不会被置于危险中的头部字段值(有关更多详细信息,请参阅第7.1节)。表示的编码与没有索引的文字头部字段相同(见第6.2.2节)。
6.3 更新动态表大小
动态表大小更新表示对动态表大小的更改。
动态表大小更新以’001
‘3位模式开始,然后是新的最大大小,以带5位前缀的整数表示(见第5.1节)。
新的最大尺寸必须小于或等于使用HPACK的协议所确定的限制。超过此限制的值必须被视为解码错误。在HTTP/2中,此限制是从解码器收到并由编码器确认的SETTINGS_HEADER_TABLE_SIZE
参数(参见[HTTP2]的第6.5.2节)的最后一个值(参见[HTTP2]的第6.5.3节)。
减小动态表的最大大小可能会导致条目被驱逐(参见第4.3节)。
7 安全考虑
本节介绍HPACK可能存在的安全问题:
- 使用压缩作为基于长度的oracle,用于验证对压缩到共享压缩上下文中的秘密的猜测。
- 因解码器处理或存储器容量耗尽而导致的拒绝服务。
附录A 静态表定义
静态表(请参阅第2.3.1节)包含一个预定义且不可更改的头部字段列表。
静态表是由常用网站使用的最常见的头字段创建的,并添加了HTTP/2特定的伪头字段(请参见[HTTP2]的第8.1.2.1节)。对于频繁值较少的头部字段,会为每个频繁值添加一个条目。对于其他头部字段,添加了一个空值的条目。
表1列出了构成静态表的预定义头部字段,并给出了每个条目的索引。
附录B Huffman Code
这个霍夫曼代码是根据大量HTTP头文件获得的统计信息生成的。这是一个典型的霍夫曼编码(参见[CANONICAL]),并进行了一些调整,以确保没有符号具有唯一的编码长度。
表中的每一行都定义了用于表示符号的代码:
- sym:代表的符号。它是一个八位字节的十进制值,可能以其ASCII码表示作为前缀。一个特定的符号“EOS”用于表示字符串文字的结尾。
- code as bits:符号的霍夫曼编码表示为基数为2的整数,与最高有效位(MSB)对齐。
- code as hex:符号的霍夫曼编码,表示为一个十六进制整数,与最低有效位(LSB)对齐。
- len:代表符号的代码的位数。
例如,符号47(对应于ASCII字符“/”)的代码由6位“0”,“1”,“1”,“0”,“0”,“0”组成。这对应于以6位编码的值0x18(十六进制)。