普通视图

发现新文章,点击刷新页面。
昨天以前Halfrost's Field | 冰霜之地

TLS Application-Layer Protocol Negotiation Extension

TLS Application-Layer Protocol Negotiation Extension

这篇文章我们主要来讨论讨论 Transport Layer Security (TLS) 握手中的 Application-Layer Protocol Negotiation 扩展。对于在同一 TCP 或 UDP 端口上支持多个应用程序协议的实例,此扩展允许应用程序层去协商将在 TLS 连接中使用哪个协议。

一. Introduction

应用层协议越来越多地封装在 TLS 协议 [RFC5246] 中。这种封装使应用程序可以使用几乎整个全球 IP 基础结构中已经存在的现有安全通信链路的 443 端口。

当单个服务器端端口号(例如端口 443)上支持多个应用程序协议时,客户端和服务器需要协商用于每个连接的应用程序协议。希望在不增加客户端和服务器之间的网络往返次数的情况下完成此协商,因为每次往返都会降低最终用户的体验。此外,允许基于协商的应用协议来选择证书将是有利的。

本文指定了 TLS 扩展,该扩展允许应用程序层在 TLS 握手中协商协议的选择。HTTPbis WG 要求进行这项工作,以解决通过 TLS 进行 HTTP/2([HTTP2])的协商。但是,ALPN 有助于协商任意应用程序层协议。

借助 ALPN,客户端会将支持的应用程序协议列表作为 TLS ClientHello 消息的一部分发送。服务器选择一个协议,并将所选协议作为 TLS ServerHello 消息的一部分发送。因此,可以在 TLS 握手中完成应用协议协商,而无需添加网络往返,并且允许服务器根据需要,将不同的证书与每个应用协议相关联。

二. Application-Layer Protocol Negotiation

1. The Application-Layer Protocol Negotiation Extension

定义了一个新的扩展类型("application_layer_protocol_negotiation(16)"),客户端可以在其 “ClientHello” 消息中包含该扩展类型。

   enum {
       application_layer_protocol_negotiation(16), (65535)
   } ExtensionType;

("application_layer_protocol_negotiation(16)") 扩展名的 "extension_data" 字段应包含 "ProtocolNameList" 值。

   opaque ProtocolName<1..2^8-1>;

   struct {
       ProtocolName protocol_name_list<2..2^16-1>
   } ProtocolNameList;

"ProtocolNameList" 按优先级从高到低包含客户端发布的协议列表。 协议是由 IANA 注册的不透明非空字节串命名的,如本文档第 6 节("IANA 注意事项")中所述。不能包含空字符串,并且不能截断字节字符串。

接收到包含 "application_layer_protocol_negotiation" 扩展名的 ClientHello 的服务器可以向客户端返回合适的协议选择作为响应。服务器将忽略它无法识别的任何协议名称。一个新的 ServerHello 扩展类型("application_layer_protocol_negotiation(16)") 可以在 ServerHello 消息扩展中返回给客户端。("application_layer_protocol_negotiation(16)") 扩展名的 "extension_data" 字段的结构与上述针对客户端 "extension_data" 的描述相同,只是 "ProtocolNameList" 必须包含一个 "ProtocolName"。

因此,ClientHello 和 ServerHello 消息中带有" application_layer_protocol_negotiation" 扩展名的完整握手具有以下流程(与 [RFC5246]的 7.3 节相比):

   Client                                              Server

   ClientHello                     -------->       ServerHello
     (ALPN extension &                               (ALPN extension &
      list of protocols)                              selected protocol)
                                                   Certificate*
                                                   ServerKeyExchange*
                                                   CertificateRequest*
                                   <--------       ServerHelloDone
   Certificate*
   ClientKeyExchange
   CertificateVerify*
   [ChangeCipherSpec]
   Finished                        -------->
                                                   [ChangeCipherSpec]
                                   <--------       Finished
   Application Data                <------->       Application Data

                                 Figure 1

   * Indicates optional or situation-dependent messages that are not always sent.

带有 "application_layer_protocol_negotiation" 扩展名的简短握手具有以下流程:

   Client                                              Server

   ClientHello                     -------->       ServerHello
     (ALPN extension &                               (ALPN extension &
      list of protocols)                              selected protocol)
                                                   [ChangeCipherSpec]
                                   <--------       Finished
   [ChangeCipherSpec]
   Finished                        -------->
   Application Data                <------->       Application Data

与许多其他 TLS 扩展不同,此扩展不建立会话的属性,仅建立连接的属性。当使用会话恢复或会话票证 [RFC5077] 时,此扩展的先前内容无关紧要,并且只用考虑新握手消息中的值。

2. Protocol Selection

期望服务器将具有优先级支持的协议列表,并且仅在客户端支持的情况下才选择协议。在这种情况下,服务器应该选择它所支持的,并且也是由客户端发布的最优先的协议。如果服务器不支持客户端传过来的协议,则服务器应以 "no_application_protocol" alert 错误回应。

   enum {
       no_application_protocol(120),
       (255)
   } AlertDescription;

在重新协商之前,ServerHello 的 "application_layer_protocol_negotiation" 扩展类型中标识的协议将此连接是确定的。服务器不会响应所选协议,并随后使用其他协议进行应用程序数据交换。

三. Design Considerations

ALPN 扩展旨在遵循 TLS 协议扩展的典型设计。具体而言,根据已建立的 TLS 体系结构,协商完全在 client/server hello 交换中执行。 ServerHello 的扩展 "application_layer_protocol_negotiation" 旨在确定连接中选择的协议(直到重新协商连接),并以纯文本形式发送,以允许网络元素在应用程序还没确定应用层协议的情况下,导致的 TCP 或 UDP 端口号不确定时,为连接提供差异化​​服务。通过将协议选择的所有权放在服务器上, ALPN 促进以下场景:证书选择或连接重新路由,这两者可能会基于协商的协议。

最终,通过在握手过程中以明文方式管理协议选择,ALPN 避免了在建立连接之前就隐藏协商协议而引入错误。如果需要隐藏协议,则在建立连接后进行重新协商(这将提供真正的 TLS 安全保证)将是首选方法。

四. Security Considerations

ALPN 扩展不会影响 TLS 会话建立或应用程序数据交换的安全性。ALPN 用于为与 TLS 连接关联的应用程序层协议提供一个外部可见的标记。从历史上看,可以从使用中的 TCP 或 UDP 端口号确定与连接关联的应用程序层协议。

打算通过添加新协议标识符来扩展协议标识符注册表的实现方和文档编辑者,应考虑到在 TLS 版本 1.2 及以下版本中,客户端以明文形式发送这些标识符。他们还应该考虑到,至少在接下来的十年中,预计浏览器通常会在初始 ClientHello 中使用这些早期版本的 TLS。

当此类标识符可能泄露个人可识别信息时,或当此类泄露可能导致概要分析或泄露敏感信息时,必须格外小心。如果这些标识符中的任何一个适用于此新协议标识符,则该标识符不应在清晰可见的 TLS 配置中使用,并且指定此类协议标识符的文档应建议避免这种不安全使用。

五. IANA Considerations

IANA 已更新其 "ExtensionType 值" 注册表,以包括以下条目:

      16 application_layer_protocol_negotiation

本文在现有的 "传输层安全性(TLS)扩展" 标题下为标题为“应用层协议协商(ALPN)协议 ID”的协议标识符建立了注册表。

此注册表中的条目需要以下字段:

  • Protocol:协议名称。
  • Identification Sequence:标识协议的一组精确的八位字节值。这可以是协议名称的 UTF-8 编码 [RFC3629]
  • Reference:对定义协议的规范的参考。

该注册表在 [RFC5226] 中定义的 "Expert Review" 策略下运行。建议指定的专家鼓励加入对永久性和易于获得的规范的引用,该规范能够创建所标识协议的可互操作的实现。

此注册表的初始注册集如下:

Protocol: HTTP/1.1
Identification Sequence:
0x68 0x74 0x74 0x70 0x2f 0x31 0x2e 0x31 ("http/1.1")
Reference: [RFC7230]

Protocol: SPDY/1
Identification Sequence:
0x73 0x70 0x64 0x79 0x2f 0x31 ("spdy/1")
Reference:
http://dev.chromium.org/spdy/spdy-protocol/spdy-protocol-draft1

Protocol: SPDY/2
Identification Sequence:
0x73 0x70 0x64 0x79 0x2f 0x32 ("spdy/2")
Reference:
http://dev.chromium.org/spdy/spdy-protocol/spdy-protocol-draft2

Protocol: SPDY/3
Identification Sequence:
0x73 0x70 0x64 0x79 0x2f 0x33 ("spdy/3")
Reference:
http://dev.chromium.org/spdy/spdy-protocol/spdy-protocol-draft3


Reference:

RFC 7301

GitHub Repo:Halfrost-Field

Follow: halfrost · GitHub

Source: https://halfrost.com/tls_alpn/

HPACK: Header Compression for HTTP/2

Table of Contents

1. Introduction

2. Compression Process Overview

3. Header Block Decoding

4. Dynamic Table Management

5. Primitive Type Representations

6. Binary Format

7. Security Considerations

8. References

HPACK: Header Compression for HTTP/2

这一章都是引用的论文,所以就不翻译了。

  • 8.1. Normative References
  • 8.2. Informative References

Appendix A. Static Table Definition

Appendix B. Huffman Code

Appendix C. Examples


Reference:

RFC 7541

GitHub Repo:Halfrost-Field

Follow: halfrost · GitHub

Source: https://halfrost.com/http2_rfc7541/

HTTP/2 HPACK 实际应用举例

HTTP/2 HPACK 实际应用举例

在上篇文章中,具体说明了 HPACK 算法中的 8 种场景(7 种 Name-value 的场景 + 1 种动态表更新场景)。

动态表大小更新有两种方式,一种是在 HEADERS 帧中直接修改(“001” 3 位模式开始),另外一种方式是通过 SETTINGS 帧中的 SETTINGS_HEADER_TABLE_SIZE 中设置的。

在介绍 HPACK 实际应用之前,需要先来看看静态表的定义和 HTTP/2 中霍夫曼编码的定义。

一. 静态表定义

静态表(请参阅第 2.3.1 节)包含一个预定义且不可更改的 header 字段列表。

静态表是根据流行网站使用的最频繁的 header 字段创建的,并添加了 HTTP/2 特定的伪 header 字段(请参见 [HTTP2]的 8.1.2.1 节)。对于具有一些频繁值的 header 字段,为每个这些频繁值添加了一个条目。对于其他标题字段,添加了带有空值的条目。

表 1 列出了构成静态表的预定义 header 字段,并提供了每个条目的索引。

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

二. 霍夫曼编码

1. 霍夫曼算法

如果每个字符都是等长的编码形式,是否有一种算法能保证大幅压缩这些数据?等长的编码形式首先面临的一个问题是如何避免解压的时候出现歧义误读。

霍夫曼在 1952 年发现了最优前缀码的算法,算法的核心思想是:出现概率比较大的符号采用较短的编码,概率较小的符号采用较长的编码。

举个例子,一篇文章中有很多单词,我们讲所有字母出现的频率都统计出来,以 a、b、c、d、e、f 这 6 个字母为例,它们的出现频率分别如下:

HTTP/2 HPACK 实际应用举例

第一步先从这些频率中选取频率最小的 2 个,进行合并。左子树小,右子树大。合并成新的节点以后,再放回原有的节点中。

HTTP/2 HPACK 实际应用举例

重复第一步,直到所有节点都合并到一棵树上。

HTTP/2 HPACK 实际应用举例

最后给每个左子树的指针上编码为 0,右子树的指针上编码为 1。以上 6 个字母的最终编码是 a = 0, b = 101, c = 100, d = 111, e = 1101, f = 1100 。

2. 霍夫曼编码在 HTTP/2 中的定义

当使用霍夫曼编码对字符串字面进行编码时,使用以下霍夫曼代码(请参见第 5.2 节)。

此霍夫曼代码是从大量 HTTP header 样本获得的统计信息中生成的。这是规范的霍夫曼代码(请参见 [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(以十六进制表示)。

sym code as bits
aligned to MSB
code as hex
aligned to LSB
len in bits
( 0) |11111111|11000 1ff8 [13]
( 1) |11111111|11111111|1011000 7fffd8 [23]
( 2) |11111111|11111111|11111110|0010 fffffe2 [28]
( 3) |11111111|11111111|11111110|0011 fffffe3 [28]
( 4) |11111111|11111111|11111110|0100 fffffe4 [28]
( 5) |11111111|11111111|11111110|0101 fffffe5 [28]
( 6) |11111111|11111111|11111110|0110 fffffe6 [28]
( 7) |11111111|11111111|11111110|0111 fffffe7 [28]
( 8) |11111111|11111111|11111110|1000 fffffe8 [28]
( 9) |11111111|11111111|11101010 ffffea [24]
( 10) |11111111|11111111|11111111|111100 3ffffffc [30]
( 11) |11111111|11111111|11111110|1001 fffffe9 [28]
( 12) |11111111|11111111|11111110|1010 fffffea [28]
( 13) |11111111|11111111|11111111|111101 3ffffffd [30]
( 14) |11111111|11111111|11111110|1011 fffffeb [28]
( 15) |11111111|11111111|11111110|1100 fffffec [28]
( 16) |11111111|11111111|11111110|1101 fffffed [28]
( 17) |11111111|11111111|11111110|1110 fffffee [28]
( 18) |11111111|11111111|11111110|1111 fffffef [28]
( 19) |11111111|11111111|11111111|0000 ffffff0 [28]
( 20) |11111111|11111111|11111111|0001 ffffff1 [28]
( 21) |11111111|11111111|11111111|0010 ffffff2 [28]
( 22) |11111111|11111111|11111111|111110 3ffffffe [30]
( 23) |11111111|11111111|11111111|0011 ffffff3 [28]
( 24) |11111111|11111111|11111111|0100 ffffff4 [28]
( 25) |11111111|11111111|11111111|0101 ffffff5 [28]
( 26) |11111111|11111111|11111111|0110 ffffff6 [28]
( 27) |11111111|11111111|11111111|0111 ffffff7 [28]
( 28) |11111111|11111111|11111111|1000 ffffff8 [28]
( 29) |11111111|11111111|11111111|1001 ffffff9 [28]
( 30) |11111111|11111111|11111111|1010 ffffffa [28]
( 31) |11111111|11111111|11111111|1011 ffffffb [28]
' ' ( 32) |010100 14 [ 6]
'!' ( 33) |11111110|00 3f8 [10]
'"' ( 34) |11111110|01 3f9 [10]
'#' ( 35) |11111111|1010 ffa [12]
'$' ( 36) |11111111|11001 1ff9 [13]
'%' ( 37) |010101 15 [ 6]
'&' ( 38) |11111000 f8 [ 8]
''' ( 39) |11111111|010 7fa [11]
'(' ( 40) |11111110|10 3fa [10]
')' ( 41) |11111110|11 3fb [10]
'*' ( 42) |11111001 f9 [ 8]
'+' ( 43) |11111111|011 7fb [11]
',' ( 44) |11111010 fa [ 8]
'-' ( 45) |010110 16 [ 6]
'.' ( 46) |010111 17 [ 6]
'/' ( 47) |011000 18 [ 6]
'0' ( 48) |00000 0 [ 5]
'1' ( 49) |00001 1 [ 5]
'2' ( 50) |00010 2 [ 5]
'3' ( 51) |011001 19 [ 6]
'4' ( 52) |011010 1a [ 6]
'5' ( 53) |011011 1b [ 6]
'6' ( 54) |011100 1c [ 6]
'7' ( 55) |011101 1d [ 6]
'8' ( 56) |011110 1e [ 6]
'9' ( 57) |011111 1f [ 6]
':' ( 58) |1011100 5c [ 7]
';' ( 59) |11111011 fb [ 8]
'<' ( 60) |11111111|1111100 7ffc [15]
'=' ( 61) |100000 20 [ 6]
'>' ( 62) |11111111|1011 ffb [12]
'?' ( 63) |11111111|00 3fc [10]
'@' ( 64) |11111111|11010 1ffa [13]
'A' ( 65) |100001 21 [ 6]
'B' ( 66) |1011101 5d [ 7]
'C' ( 67) |1011110 5e [ 7]
'D' ( 68) |1011111 5f [ 7]
'E' ( 69) |1100000 60 [ 7]
'F' ( 70) |1100001 61 [ 7]
'G' ( 71) |1100010 62 [ 7]
'H' ( 72) |1100011 63 [ 7]
'I' ( 73) |1100100 64 [ 7]
'J' ( 74) |1100101 65 [ 7]
'K' ( 75) |1100110 66 [ 7]
'L' ( 76) |1100111 67 [ 7]
'M' ( 77) |1101000 68 [ 7]
'N' ( 78) |1101001 69 [ 7]
'O' ( 79) |1101010 6a [ 7]
'P' ( 80) |1101011 6b [ 7]
'Q' ( 81) |1101100 6c [ 7]
'R' ( 82) |1101101 6d [ 7]
'S' ( 83) |1101110 6e [ 7]
'T' ( 84) |1101111 6f [ 7]
'U' ( 85) |1110000 70 [ 7]
'V' ( 86) |1110001 71 [ 7]
'W' ( 87) |1110010 72 [ 7]
'X' ( 88) |11111100 fc [ 8]
'Y' ( 89) |1110011 73 [ 7]
'Z' ( 90) |11111101 fd [ 8]
'[' ( 91) |11111111|11011 1ffb [13]
'' ( 92) |11111111|11111110|000 7fff0 [19]
']' ( 93) |11111111|11100 1ffc [13]
'^' ( 94) |11111111|111100 3ffc [14]
'_' ( 95) |100010 22 [ 6]
'`' ( 96) |11111111|1111101 7ffd [15]
'a' ( 97) |00011 3 [ 5]
'b' ( 98) |100011 23 [ 6]
'c' ( 99) |00100 4 [ 5]
'd' (100) |100100 24 [ 6]
'e' (101) |00101 5 [ 5]
'f' (102) |100101 25 [ 6]
'g' (103) |100110 26 [ 6]
'h' (104) |100111 27 [ 6]
'i' (105) |00110 6 [ 5]
'j' (106) |1110100 74 [ 7]
'k' (107) |1110101 75 [ 7]
'l' (108) |101000 28 [ 6]
'm' (109) |101001 29 [ 6]
'n' (110) |101010 2a [ 6]
'o' (111) |00111 7 [ 5]
'p' (112) |101011 2b [ 6]
'q' (113) |1110110 76 [ 7]
'r' (114) |101100 2c [ 6]
's' (115) |01000 8 [ 5]
't' (116) |01001 9 [ 5]
'u' (117) |101101 2d [ 6]
'v' (118) |1110111 77 [ 7]
'w' (119) |1111000 78 [ 7]
'x' (120) |1111001 79 [ 7]
'y' (121) |1111010 7a [ 7]
'z' (122) |1111011 7b [ 7]
'{' (123) |11111111|1111110 7ffe [15]
' ' (124) |11111111|100 7fc
'}' (125) |11111111|111101 3ffd [14]
'~' (126) |11111111|11101 1ffd [13]
(127) |11111111|11111111|11111111|1100 ffffffc [28]
(128) |11111111|11111110|0110 fffe6 [20]
(129) |11111111|11111111|010010 3fffd2 [22]
(130) |11111111|11111110|0111 fffe7 [20]
(131) |11111111|11111110|1000 fffe8 [20]
(132) |11111111|11111111|010011 3fffd3 [22]
(133) |11111111|11111111|010100 3fffd4 [22]
(134) |11111111|11111111|010101 3fffd5 [22]
(135) |11111111|11111111|1011001 7fffd9 [23]
(136) |11111111|11111111|010110 3fffd6 [22]
(137) |11111111|11111111|1011010 7fffda [23]
(138) |11111111|11111111|1011011 7fffdb [23]
(139) |11111111|11111111|1011100 7fffdc [23]
(140) |11111111|11111111|1011101 7fffdd [23]
(141) |11111111|11111111|1011110 7fffde [23]
(142) |11111111|11111111|11101011 ffffeb [24]
(143) |11111111|11111111|1011111 7fffdf [23]
(144) |11111111|11111111|11101100 ffffec [24]
(145) |11111111|11111111|11101101 ffffed [24]
(146) |11111111|11111111|010111 3fffd7 [22]
(147) |11111111|11111111|1100000 7fffe0 [23]
(148) |11111111|11111111|11101110 ffffee [24]
(149) |11111111|11111111|1100001 7fffe1 [23]
(150) |11111111|11111111|1100010 7fffe2 [23]
(151) |11111111|11111111|1100011 7fffe3 [23]
(152) |11111111|11111111|1100100 7fffe4 [23]
(153) |11111111|11111110|11100 1fffdc [21]
(154) |11111111|11111111|011000 3fffd8 [22]
(155) |11111111|11111111|1100101 7fffe5 [23]
(156) |11111111|11111111|011001 3fffd9 [22]
(157) |11111111|11111111|1100110 7fffe6 [23]
(158) |11111111|11111111|1100111 7fffe7 [23]
(159) |11111111|11111111|11101111 ffffef [24]
(160) |11111111|11111111|011010 3fffda [22]
(161) |11111111|11111110|11101 1fffdd [21]
(162) |11111111|11111110|1001 fffe9 [20]
(163) |11111111|11111111|011011 3fffdb [22]
(164) |11111111|11111111|011100 3fffdc [22]
(165) |11111111|11111111|1101000 7fffe8 [23]
(166) |11111111|11111111|1101001 7fffe9 [23]
(167) |11111111|11111110|11110 1fffde [21]
(168) |11111111|11111111|1101010 7fffea [23]
(169) |11111111|11111111|011101 3fffdd [22]
(170) |11111111|11111111|011110 3fffde [22]
(171) |11111111|11111111|11110000 fffff0 [24]
(172) |11111111|11111110|11111 1fffdf [21]
(173) |11111111|11111111|011111 3fffdf [22]
(174) |11111111|11111111|1101011 7fffeb [23]
(175) |11111111|11111111|1101100 7fffec [23]
(176) |11111111|11111111|00000 1fffe0 [21]
(177) |11111111|11111111|00001 1fffe1 [21]
(178) |11111111|11111111|100000 3fffe0 [22]
(179) |11111111|11111111|00010 1fffe2 [21]
(180) |11111111|11111111|1101101 7fffed [23]
(181) |11111111|11111111|100001 3fffe1 [22]
(182) |11111111|11111111|1101110 7fffee [23]
(183) |11111111|11111111|1101111 7fffef [23]
(184) |11111111|11111110|1010 fffea [20]
(185) |11111111|11111111|100010 3fffe2 [22]
(186) |11111111|11111111|100011 3fffe3 [22]
(187) |11111111|11111111|100100 3fffe4 [22]
(188) |11111111|11111111|1110000 7ffff0 [23]
(189) |11111111|11111111|100101 3fffe5 [22]
(190) |11111111|11111111|100110 3fffe6 [22]
(191) |11111111|11111111|1110001 7ffff1 [23]
(192) |11111111|11111111|11111000|00 3ffffe0 [26]
(193) |11111111|11111111|11111000|01 3ffffe1 [26]
(194) |11111111|11111110|1011 fffeb [20]
(195) |11111111|11111110|001 7fff1 [19]
(196) |11111111|11111111|100111 3fffe7 [22]
(197) |11111111|11111111|1110010 7ffff2 [23]
(198) |11111111|11111111|101000 3fffe8 [22]
(199) |11111111|11111111|11110110|0 1ffffec [25]
(200) |11111111|11111111|11111000|10 3ffffe2 [26]
(201) |11111111|11111111|11111000|11 3ffffe3 [26]
(202) |11111111|11111111|11111001|00 3ffffe4 [26]
(203) |11111111|11111111|11111011|110 7ffffde [27]
(204) |11111111|11111111|11111011|111 7ffffdf [27]
(205) |11111111|11111111|11111001|01 3ffffe5 [26]
(206) |11111111|11111111|11110001 fffff1 [24]
(207) |11111111|11111111|11110110|1 1ffffed [25]
(208) |11111111|11111110|010 7fff2 [19]
(209) |11111111|11111111|00011 1fffe3 [21]
(210) |11111111|11111111|11111001|10 3ffffe6 [26]
(211) |11111111|11111111|11111100|000 7ffffe0 [27]
(212) |11111111|11111111|11111100|001 7ffffe1 [27]
(213) |11111111|11111111|11111001|11 3ffffe7 [26]
(214) |11111111|11111111|11111100|010 7ffffe2 [27]
(215) |11111111|11111111|11110010 fffff2 [24]
(216) |11111111|11111111|00100 1fffe4 [21]
(217) |11111111|11111111|00101 1fffe5 [21]
(218) |11111111|11111111|11111010|00 3ffffe8 [26]
(219) |11111111|11111111|11111010|01 3ffffe9 [26]
(220) |11111111|11111111|11111111|1101 ffffffd [28]
(221) |11111111|11111111|11111100|011 7ffffe3 [27]
(222) |11111111|11111111|11111100|100 7ffffe4 [27]
(223) |11111111|11111111|11111100|101 7ffffe5 [27]
(224) |11111111|11111110|1100 fffec [20]
(225) |11111111|11111111|11110011 fffff3 [24]
(226) |11111111|11111110|1101 fffed [20]
(227) |11111111|11111111|00110 1fffe6 [21]
(228) |11111111|11111111|101001 3fffe9 [22]
(229) |11111111|11111111|00111 1fffe7 [21]
(230) |11111111|11111111|01000 1fffe8 [21]
(231) |11111111|11111111|1110011 7ffff3 [23]
(232) |11111111|11111111|101010 3fffea [22]
(233) |11111111|11111111|101011 3fffeb [22]
(234) |11111111|11111111|11110111|0 1ffffee [25]
(235) |11111111|11111111|11110111|1 1ffffef [25]
(236) |11111111|11111111|11110100\ fffff4 [24]
(237) |11111111|11111111|11110101\ fffff5 [24]
(238) |11111111|11111111|11111010|10 3ffffea [26]
(239) |11111111|11111111|1110100 \ 7ffff4 [23]
(240) |11111111|11111111|11111010|11 3ffffeb [26]
(241) |11111111|11111111|11111100|110 7ffffe6 [27]
(242) |11111111|11111111|11111011|00 3ffffec [26]
(243) |11111111|11111111|11111011|01 3ffffed [26]
(244) |11111111|11111111|11111100|111 7ffffe7 [27]
(245) |11111111|11111111|11111101|000 7ffffe8 [27]
(246) |11111111|11111111|11111101|001 7ffffe9 [27]
(247) |11111111|11111111|11111101|010 7ffffea [27]
(248) |11111111|11111111|11111101|011 7ffffeb [27]
(249) |11111111|11111111|11111111|1110 ffffffe [28]
(250) |11111111|11111111|11111101|100 7ffffec [27]
(251) |11111111|11111111|11111101|101 7ffffed [27]
(252) |11111111|11111111|11111101|110 7ffffee [27]
(253) |11111111|11111111|11111101|111 7ffffef [27]
(254) |11111111|11111111|11111110|000 7fffff0 [27]
(255) |11111111|11111111|11111011|10 3ffffee [26]
EOS (256) |11111111|11111111|11111111|111111 3fffffff [30]

三. 例子

本章节包含一些示例,这些示例涵盖整数编码,header 字段表示以及使用和不使用霍夫曼编码的请求和响应的 header 字段的整个列表的编码。

1. 整数表示的示例

本节详细显示了整数值的表示形式(请参见第 5.1 节)。

(1). 使用 5 位前缀对 10 进行编码

10 小于 31(2^5-1),并使用 5 位前缀表示。

     0   1   2   3   4   5   6   7
   +---+---+---+---+---+---+---+---+
   | X | X | X | 0 | 1 | 0 | 1 | 0 |   10 stored on 5 bits
   +---+---+---+---+---+---+---+---+

(2). 使用 5 位前缀对 1337 进行编码

1337 大于 31(2^5-1),并使用 5 位前缀表示。5 位前缀使用其最大值(31)填充。

I = 1337 - (2^5 - 1) = 1306。I (1306) 大于等于 128。I % 128 == 26,26 + 128 == 154,154 用 8 位表示为: 10011010。I 现在是 10,(1306 / 128 == 10),用 8 位表示为: 00001010。

     0   1   2   3   4   5   6   7
   +---+---+---+---+---+---+---+---+
   | 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
   | 0 | 0 | 0 | 0 | 1 | 0 | 1 | 0 |  10<128, encode(10), done
   +---+---+---+---+---+---+---+---+

(3). 从八位字节边界开始对 42 进行编码

42 小于 255 (2^8 - 1) 用 8 位前缀表示。

     0   1   2   3   4   5   6   7
   +---+---+---+---+---+---+---+---+
   | 0 | 0 | 1 | 0 | 1 | 0 | 1 | 0 |   42 stored on 8 bits
   +---+---+---+---+---+---+---+---+

2. header 字段表示的示例

本节显示了几个独立的表示示例。

(1). 带索引的字面 header 字段

header 字段表示使用字面名称 name 和字面值 value。header 字段将添加到动态表。

需要编码的 header 列表:

custom-key: custom-header

编码数据的十六进制表示:

   400a 6375 7374 6f6d 2d6b 6579 0d63 7573 | @.custom-key.cus
   746f 6d2d 6865 6164 6572                | tom-header

解码过程:

   40                                      | == Literal indexed ==
   0a                                      |   Literal name (len = 10)
   6375 7374 6f6d 2d6b 6579                | custom-key
   0d                                      |   Literal value (len = 13)
   6375 7374 6f6d 2d68 6561 6465 72        | custom-header
                                           | -> custom-key:
                                           |   custom-header

HTTP/2 HPACK 实际应用举例

由于 H 位传了 0,所以后面字符串用的字面形式表示,即 ASCII 码表示,通过查表可以知道,6375 7374 6f6d 2d6b 6579 表示的值是 custom-key,6375 7374 6f6d 2d68 6561 6465 72 表示的值是 custom-header。

编码后的动态表:

   [  1] (s =  55) custom-key: custom-header
         Table size:  55

解码后的 header 列表:

custom-key: custom-header

(2). 没有索引的字面 header 字段

header 字段表示使用索引名称 name 和字面值 value。header 字段未添加到动态表。

需要编码的 header 列表:

   :path: /sample/path

编码数据的十六进制表示:

   040c 2f73 616d 706c 652f 7061 7468      | ../sample/path

解码过程:

   04                                      | == Literal not indexed ==
                                           |   Indexed name (idx = 4)
                                           |     :path
   0c                                      |   Literal value (len = 12)
   2f73 616d 706c 652f 7061 7468           | /sample/path
                                           | -> :path: /sample/path

HTTP/2 HPACK 实际应用举例

由于 H 位传了 0,所以后面字符串用的字面形式表示,即 ASCII 码表示,通过查表可以知道,2f73 616d 706c 652f 7061 7468 表示的值是 /sample/path。由于 :path 存在于静态表中,所以只需要传 index = 4 即可。

编码后的动态表:

解码后的 header 列表:

   :path: /sample/path

(3). 从不索引的字面 header 字段

header 字段表示使用字面名称 name 和字面值 value。header 字段不会添加到动态表中,并且如果由中间件重新编码,则必须使用相同的表示形式。

需要编码的 header 列表:

   password: secret

编码数据的十六进制表示:

   1008 7061 7373 776f 7264 0673 6563 7265 | ..password.secre
   74                                      | t

解码过程:

   10                                      | == Literal never indexed ==
   08                                      |   Literal name (len = 8)
   7061 7373 776f 7264                     | password
   06                                      |   Literal value (len = 6)
   7365 6372 6574                          | secret
                                           | -> password: secret

HTTP/2 HPACK 实际应用举例

由于 H 位传了 0,所以后面字符串用的字面形式表示,即 ASCII 码表示,通过查表可以知道,7061 7373 776f 7264 表示的值是 password。7365 6372 6574 表示的值是 secret。

编码后的动态表:

解码后的 header 列表:

   password: secret

(4). 索引的 header 字段

header 字段表示使用静态表中的索引 header 字段。

需要编码的 header 列表:

   :method: GET

编码数据的十六进制表示:

   82                                      | .

解码过程:

   82                                      | == Indexed - Add ==
                                           |   idx = 2
                                           | -> :method: GET

HTTP/2 HPACK 实际应用举例

由于 :method 和 GET 都在静态表中,所以用静态表中的 index 即可。

编码后的动态表:

解码后的 header 列表:

   :method: GET

3. 没有霍夫曼编码请求的示例

本节显示了同一连接上与 HTTP 请求相对应的几个连续的 header 列表。

(1). 第一个请求

需要编码的 header 列表:

   :method: GET
   :scheme: http
   :path: /
   :authority: www.example.com

编码数据的十六进制表示:

   8286 8441 0f77 7777 2e65 7861 6d70 6c65 | ...A.www.example
   2e63 6f6d                               | .com

解码过程:

   82                                      | == Indexed - Add ==
                                           |   idx = 2
                                           | -> :method: GET
   86                                      | == Indexed - Add ==
                                           |   idx = 6
                                           | -> :scheme: http
   84                                      | == Indexed - Add ==
                                           |   idx = 4
                                           | -> :path: /
   41                                      | == Literal indexed ==
                                           |   Indexed name (idx = 1)
                                           |     :authority
   0f                                      |   Literal value (len = 15)
   7777 772e 6578 616d 706c 652e 636f 6d   | www.example.com
                                           | -> :authority:
                                           |   www.example.com

编码后的动态表:

   [  1] (s =  57) :authority: www.example.com
         Table size:  57

解码后的 header 列表:

   :method: GET
   :scheme: http
   :path: /
   :authority: www.example.com

(2). 第二个请求

需要编码的 header 列表:

   :method: GET
   :scheme: http
   :path: /
   :authority: www.example.com
   cache-control: no-cache

编码数据的十六进制表示:

   8286 84be 5808 6e6f 2d63 6163 6865      | ....X.no-cache

解码过程:

   82                                      | == Indexed - Add ==
                                           |   idx = 2
                                           | -> :method: GET
   86                                      | == Indexed - Add ==
                                           |   idx = 6
                                           | -> :scheme: http
   84                                      | == Indexed - Add ==
                                           |   idx = 4
                                           | -> :path: /
   be                                      | == Indexed - Add ==
                                           |   idx = 62
                                           | -> :authority:
                                           |   www.example.com
   58                                      | == Literal indexed ==
                                           |   Indexed name (idx = 24)
                                           |     cache-control
   08                                      |   Literal value (len = 8)
   6e6f 2d63 6163 6865                     | no-cache
                                           | -> cache-control: no-cache

编码后的动态表:

   [  1] (s =  53) cache-control: no-cache
   [  2] (s =  57) :authority: www.example.com
         Table size: 110

解码后的 header 列表:

   :method: GET
   :scheme: http
   :path: /
   :authority: www.example.com
   cache-control: no-cache

(3). 第三个请求

需要编码的 header 列表:

   :method: GET
   :scheme: https
   :path: /index.html
   :authority: www.example.com
   custom-key: custom-value

编码数据的十六进制表示:

   8287 85bf 400a 6375 7374 6f6d 2d6b 6579 | ....@.custom-key
   0c63 7573 746f 6d2d 7661 6c75 65        | .custom-value

解码过程:

   82                                      | == Indexed - Add ==
                                           |   idx = 2
                                           | -> :method: GET
   87                                      | == Indexed - Add ==
                                           |   idx = 7
                                           | -> :scheme: https
   85                                      | == Indexed - Add ==
                                           |   idx = 5
                                           | -> :path: /index.html
   bf                                      | == Indexed - Add ==
                                           |   idx = 63
                                           | -> :authority:
                                           |   www.example.com
   40                                      | == Literal indexed ==
   0a                                      |   Literal name (len = 10)
   6375 7374 6f6d 2d6b 6579                | custom-key
   0c                                      |   Literal value (len = 12)
   6375 7374 6f6d 2d76 616c 7565           | custom-value
                                           | -> custom-key:
                                           |   custom-value

编码后的动态表:

   [  1] (s =  54) custom-key: custom-value
   [  2] (s =  53) cache-control: no-cache
   [  3] (s =  57) :authority: www.example.com
         Table size: 164

解码后的 header 列表:

   :method: GET
   :scheme: https
   :path: /index.html
   :authority: www.example.com
   custom-key: custom-value

4. 有霍夫曼编码请求的示例

本节显示与上一节相同的示例,但对字面值 value 使用霍夫曼编码。

(1). 第一个请求

需要编码的 header 列表:

   :method: GET
   :scheme: http
   :path: /
   :authority: www.example.com

编码数据的十六进制表示:

   8286 8441 8cf1 e3c2 e5f2 3a6b a0ab 90f4 | ...A......:k....
   ff                                      | .

解码过程:

   82                                      | == Indexed - Add ==
                                           |   idx = 2
                                           | -> :method: GET
   86                                      | == Indexed - Add ==
                                           |   idx = 6
                                           | -> :scheme: http
   84                                      | == Indexed - Add ==
                                           |   idx = 4
                                           | -> :path: /
   41                                      | == Literal indexed ==
                                           |   Indexed name (idx = 1)
                                           |     :authority
   8c                                      |   Literal value (len = 12)
                                           |     Huffman encoded:
   f1e3 c2e5 f23a 6ba0 ab90 f4ff           | .....:k.....
                                           |     Decoded:
                                           | www.example.com
                                           | -> :authority:
                                           |   www.example.com

编码后的动态表:

   [  1] (s =  57) :authority: www.example.com
         Table size:  57

解码后的 header 列表:

   :method: GET
   :scheme: http
   :path: /
   :authority: www.example.com

(2). 第二个请求

需要编码的 header 列表:

   :method: GET
   :scheme: http
   :path: /
   :authority: www.example.com
   cache-control: no-cache

编码数据的十六进制表示:

   8286 84be 5886 a8eb 1064 9cbf           | ....X....d..

解码过程:

   82                                      | == Indexed - Add ==
                                           |   idx = 2
                                           | -> :method: GET
   86                                      | == Indexed - Add ==
                                           |   idx = 6
                                           | -> :scheme: http
   84                                      | == Indexed - Add ==
                                           |   idx = 4
                                           | -> :path: /
   be                                      | == Indexed - Add ==
                                           |   idx = 62
                                           | -> :authority:
                                           |   www.example.com
   58                                      | == Literal indexed ==
                                           |   Indexed name (idx = 24)
                                           |     cache-control
   86                                      |   Literal value (len = 6)
                                           |     Huffman encoded:
   a8eb 1064 9cbf                          | ...d..
                                           |     Decoded:
                                           | no-cache
                                           | -> cache-control: no-cache

编码后的动态表:

   [  1] (s =  53) cache-control: no-cache
   [  2] (s =  57) :authority: www.example.com
         Table size: 110

解码后的 header 列表:

   :method: GET
   :scheme: http
   :path: /
   :authority: www.example.com
   cache-control: no-cache

(3). 第三个请求

需要编码的 header 列表:

   :method: GET
   :scheme: https
   :path: /index.html
   :authority: www.example.com
   custom-key: custom-value

编码数据的十六进制表示:

   8287 85bf 4088 25a8 49e9 5ba9 7d7f 8925 | ....@.%.I.[.}..%
   a849 e95b b8e8 b4bf                     | .I.[....

解码过程:

   82                                      | == Indexed - Add ==
                                           |   idx = 2
                                           | -> :method: GET
   87                                      | == Indexed - Add ==
                                           |   idx = 7
                                           | -> :scheme: https
   85                                      | == Indexed - Add ==
                                           |   idx = 5
                                           | -> :path: /index.html
   bf                                      | == Indexed - Add ==
                                           |   idx = 63
                                           | -> :authority:
                                           |   www.example.com
   40                                      | == Literal indexed ==
   88                                      |   Literal name (len = 8)
                                           |     Huffman encoded:
   25a8 49e9 5ba9 7d7f                     | %.I.[.}.
                                           |     Decoded:
                                           | custom-key
   89                                      |   Literal value (len = 9)
                                           |     Huffman encoded:
   25a8 49e9 5bb8 e8b4 bf                  | %.I.[....
                                           |     Decoded:
                                           | custom-value
                                           | -> custom-key:
                                           |   custom-value

编码后的动态表:

   [  1] (s =  54) custom-key: custom-value
   [  2] (s =  53) cache-control: no-cache
   [  3] (s =  57) :authority: www.example.com
         Table size: 164

解码后的 header 列表:

   :method: GET
   :scheme: https
   :path: /index.html
   :authority: www.example.com
   custom-key: custom-value

5. 没有霍夫曼编码响应的示例

本节显示了同一连接上对应于 HTTP 响应的几个连续的 header 列表。 HTTP/2 设置参数 SETTINGS_HEADER_TABLE_SIZE 设置为 256 个八位字节的值,导致某些驱逐条目发生。

(1). 第一个响应

需要编码的 header 列表:

   :status: 302
   cache-control: private
   date: Mon, 21 Oct 2013 20:13:21 GMT
   location: https://www.example.com

编码数据的十六进制表示:

   4803 3330 3258 0770 7269 7661 7465 611d | H.302X.privatea.
   4d6f 6e2c 2032 3120 4f63 7420 3230 3133 | Mon, 21 Oct 2013
   2032 303a 3133 3a32 3120 474d 546e 1768 |  20:13:21 GMTn.h
   7474 7073 3a2f 2f77 7777 2e65 7861 6d70 | ttps://www.examp
   6c65 2e63 6f6d                          | le.com

解码过程:

   48                                      | == Literal indexed ==
                                           |   Indexed name (idx = 8)
                                           |     :status
   03                                      |   Literal value (len = 3)
   3330 32                                 | 302
                                           | -> :status: 302
   58                                      | == Literal indexed ==
                                           |   Indexed name (idx = 24)
                                           |     cache-control
   07                                      |   Literal value (len = 7)
   7072 6976 6174 65                       | private
                                           | -> cache-control: private
   61                                      | == Literal indexed ==
                                           |   Indexed name (idx = 33)
                                           |     date
   1d                                      |   Literal value (len = 29)
   4d6f 6e2c 2032 3120 4f63 7420 3230 3133 | Mon, 21 Oct 2013
   2032 303a 3133 3a32 3120 474d 54        |  20:13:21 GMT
                                           | -> date: Mon, 21 Oct 2013
                                           |   20:13:21 GMT
   6e                                      | == Literal indexed ==
                                           |   Indexed name (idx = 46)
   17                                      |   Literal value (len = 23)
   6874 7470 733a 2f2f 7777 772e 6578 616d | https://www.exam
   706c 652e 636f 6d                       | ple.com
                                           | -> location:
                                           |   https://www.example.com

编码后的动态表:

   [  1] (s =  63) location: https://www.example.com
   [  2] (s =  65) date: Mon, 21 Oct 2013 20:13:21 GMT
   [  3] (s =  52) cache-control: private
   [  4] (s =  42) :status: 302
         Table size: 222

解码后的 header 列表:

   :status: 302
   cache-control: private
   date: Mon, 21 Oct 2013 20:13:21 GMT
   location: https://www.example.com

(2). 第二个响应

从动态表中将(“:status”,“302”)header 字段驱逐出可用空间,以允许添加(“:status”,“307”)header 字段。

需要编码的 header 列表:

   :status: 307
   cache-control: private
   date: Mon, 21 Oct 2013 20:13:21 GMT
   location: https://www.example.com

编码数据的十六进制表示:

   4803 3330 37c1 c0bf                     | H.307...

解码过程:

   48                                      | == Literal indexed ==
                                           |   Indexed name (idx = 8)
                                           |     :status
   03                                      |   Literal value (len = 3)
   3330 37                                 | 307
                                           | - evict: :status: 302
                                           | -> :status: 307
   c1                                      | == Indexed - Add ==
                                           |   idx = 65
                                           | -> cache-control: private
   c0                                      | == Indexed - Add ==
                                           |   idx = 64
                                           | -> date: Mon, 21 Oct 2013
                                           |   20:13:21 GMT
   bf                                      | == Indexed - Add ==
                                           |   idx = 63
                                           | -> location:
                                           |   https://www.example.com

编码后的动态表:

   [  1] (s =  42) :status: 307
   [  2] (s =  63) location: https://www.example.com
   [  3] (s =  65) date: Mon, 21 Oct 2013 20:13:21 GMT
   [  4] (s =  52) cache-control: private
         Table size: 222

解码后的 header 列表:

   :status: 307
   cache-control: private
   date: Mon, 21 Oct 2013 20:13:21 GMT
   location: https://www.example.com

(3). 第三个响应

在处理此 header 列表期间,会从动态表中逐出几个 header 字段。

需要编码的 header 列表:

   :status: 200
   cache-control: private
   date: Mon, 21 Oct 2013 20:13:22 GMT
   location: https://www.example.com
   content-encoding: gzip
   set-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1

编码数据的十六进制表示:

   88c1 611d 4d6f 6e2c 2032 3120 4f63 7420 | ..a.Mon, 21 Oct
   3230 3133 2032 303a 3133 3a32 3220 474d | 2013 20:13:22 GM
   54c0 5a04 677a 6970 7738 666f 6f3d 4153 | T.Z.gzipw8foo=AS
   444a 4b48 514b 425a 584f 5157 454f 5049 | DJKHQKBZXOQWEOPI
   5541 5851 5745 4f49 553b 206d 6178 2d61 | UAXQWEOIU; max-a
   6765 3d33 3630 303b 2076 6572 7369 6f6e | ge=3600; version
   3d31                                    | =1

解码过程:

   88                                      | == Indexed - Add ==
                                           |   idx = 8
                                           | -> :status: 200
   c1                                      | == Indexed - Add ==
                                           |   idx = 65
                                           | -> cache-control: private
   61                                      | == Literal indexed ==
                                           |   Indexed name (idx = 33)
                                           |     date
   1d                                      |   Literal value (len = 29)
   4d6f 6e2c 2032 3120 4f63 7420 3230 3133 | Mon, 21 Oct 2013
   2032 303a 3133 3a32 3220 474d 54        |  20:13:22 GMT
                                           | - evict: cache-control:
                                           |   private
                                           | -> date: Mon, 21 Oct 2013
                                           |   20:13:22 GMT
   c0                                      | == Indexed - Add ==
                                           |   idx = 64
                                           | -> location:
                                           |   https://www.example.com
   5a                                      | == Literal indexed ==
                                           |   Indexed name (idx = 26)
                                           |     content-encoding
   04                                      |   Literal value (len = 4)
   677a 6970                               | gzip
                                           | - evict: date: Mon, 21 Oct
                                           |    2013 20:13:21 GMT
                                           | -> content-encoding: gzip
   77                                      | == Literal indexed ==
                                           |   Indexed name (idx = 55)
                                           |     set-cookie
   38                                      |   Literal value (len = 56)
   666f 6f3d 4153 444a 4b48 514b 425a 584f | foo=ASDJKHQKBZXO
   5157 454f 5049 5541 5851 5745 4f49 553b | QWEOPIUAXQWEOIU;
   206d 6178 2d61 6765 3d33 3630 303b 2076 |  max-age=3600; v
   6572 7369 6f6e 3d31                     | ersion=1
                                           | - evict: location:
                                           |   https://www.example.com
                                           | - evict: :status: 307
                                           | -> set-cookie: foo=ASDJKHQ
                                           |   KBZXOQWEOPIUAXQWEOIU; ma
                                           |   x-age=3600; version=1

编码后的动态表:

   [  1] (s =  98) set-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU;
                    max-age=3600; version=1
   [  2] (s =  52) content-encoding: gzip
   [  3] (s =  65) date: Mon, 21 Oct 2013 20:13:22 GMT
         Table size: 215

解码后的 header 列表:

   :status: 200
   cache-control: private
   date: Mon, 21 Oct 2013 20:13:22 GMT
   location: https://www.example.com
   content-encoding: gzip
   set-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1

6. 有霍夫曼编码响应的示例

本节显示与上一节相同的示例,但对字面值使用霍夫曼编码。HTTP/2 设置参数 SETTINGS_HEADER_TABLE_SIZE 设置为 256 个八位字节的值,导致某些驱逐事件发生。驱逐机制使用已解码字面值的长度,因此发生与上一节相同的驱逐。

(1). 第一个响应

需要编码的 header 列表:

   :status: 302
   cache-control: private
   date: Mon, 21 Oct 2013 20:13:21 GMT
   location: https://www.example.com

编码数据的十六进制表示:

   4882 6402 5885 aec3 771a 4b61 96d0 7abe | H.d.X...w.Ka..z.
   9410 54d4 44a8 2005 9504 0b81 66e0 82a6 | ..T.D. .....f...
   2d1b ff6e 919d 29ad 1718 63c7 8f0b 97c8 | -..n..)...c.....
   e9ae 82ae 43d3                          | ....C.

解码过程:

   48                                      | == Literal indexed ==
                                           |   Indexed name (idx = 8)
                                           |     :status
   82                                      |   Literal value (len = 2)
                                           |     Huffman encoded:
   6402                                    | d.
                                           |     Decoded:
                                           | 302
                                           | -> :status: 302
   58                                      | == Literal indexed ==
                                           |   Indexed name (idx = 24)
                                           |     cache-control
   85                                      |   Literal value (len = 5)
                                           |     Huffman encoded:
   aec3 771a 4b                            | ..w.K
                                           |     Decoded:
                                           | private
                                           | -> cache-control: private
   61                                      | == Literal indexed ==
                                           |   Indexed name (idx = 33)
                                           |     date
   96                                      |   Literal value (len = 22)
                                           |     Huffman encoded:
   d07a be94 1054 d444 a820 0595 040b 8166 | .z...T.D. .....f
   e082 a62d 1bff                          | ...-..
                                           |     Decoded:
                                           | Mon, 21 Oct 2013 20:13:21
                                           | GMT
                                           | -> date: Mon, 21 Oct 2013
                                           |   20:13:21 GMT
   6e                                      | == Literal indexed ==
                                           |   Indexed name (idx = 46)
                                           |     location
   91                                      |   Literal value (len = 17)
                                           |     Huffman encoded:
   9d29 ad17 1863 c78f 0b97 c8e9 ae82 ae43 | .)...c.........C
   d3                                      | .
                                           |     Decoded:
                                           | https://www.example.com
                                           | -> location:
                                           |   https://www.example.com

编码后的动态表:

   [  1] (s =  63) location: https://www.example.com
   [  2] (s =  65) date: Mon, 21 Oct 2013 20:13:21 GMT
   [  3] (s =  52) cache-control: private
   [  4] (s =  42) :status: 302
         Table size: 222

解码后的 header 列表:

   :status: 302
   cache-control: private
   date: Mon, 21 Oct 2013 20:13:21 GMT
   location: https://www.example.com

(2). 第二个响应

从动态表中将(“:status”,“302”)头字段驱逐出可用空间,以允许添加(“:status”,“307”)header 字段。

需要编码的 header 列表:

   :status: 307
   cache-control: private
   date: Mon, 21 Oct 2013 20:13:21 GMT
   location: https://www.example.com

编码数据的十六进制表示:

   4883 640e ffc1 c0bf                     | H.d.....

解码过程:

   48                                      | == Literal indexed ==
                                           |   Indexed name (idx = 8)
                                           |     :status
   83                                      |   Literal value (len = 3)
                                           |     Huffman encoded:
   640e ff                                 | d..
                                           |     Decoded:
                                           | 307
                                           | - evict: :status: 302
                                           | -> :status: 307
   c1                                      | == Indexed - Add ==
                                           |   idx = 65
                                           | -> cache-control: private
   c0                                      | == Indexed - Add ==
                                           |   idx = 64
                                           | -> date: Mon, 21 Oct 2013
                                           |   20:13:21 GMT
   bf                                      | == Indexed - Add ==
                                           |   idx = 63
                                           | -> location:
                                           |   https://www.example.com

编码后的动态表:

   [  1] (s =  42) :status: 307
   [  2] (s =  63) location: https://www.example.com
   [  3] (s =  65) date: Mon, 21 Oct 2013 20:13:21 GMT
   [  4] (s =  52) cache-control: private
         Table size: 222

解码后的 header 列表:

   :status: 307
   cache-control: private
   date: Mon, 21 Oct 2013 20:13:21 GMT
   location: https://www.example.com

(3). 第三个响应

在处理此 header 列表期间,会从动态表中逐出几个 header 字段。

需要编码的 header 列表:

   :status: 200
   cache-control: private
   date: Mon, 21 Oct 2013 20:13:22 GMT
   location: https://www.example.com
   content-encoding: gzip
   set-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1

编码数据的十六进制表示:

   88c1 6196 d07a be94 1054 d444 a820 0595 | ..a..z...T.D. ..
   040b 8166 e084 a62d 1bff c05a 839b d9ab | ...f...-...Z....
   77ad 94e7 821d d7f2 e6c7 b335 dfdf cd5b | w..........5...[
   3960 d5af 2708 7f36 72c1 ab27 0fb5 291f | 9`..'..6r..'..).
   9587 3160 65c0 03ed 4ee5 b106 3d50 07   | ..1`e...N...=P.

解码过程:

   88                                      | == Indexed - Add ==
                                           |   idx = 8
                                           | -> :status: 200
   c1                                      | == Indexed - Add ==
                                           |   idx = 65
                                           | -> cache-control: private
   61                                      | == Literal indexed ==
                                           |   Indexed name (idx = 33)
                                           |     date
   96                                      |   Literal value (len = 22)
                                           |     Huffman encoded:
   d07a be94 1054 d444 a820 0595 040b 8166 | .z...T.D. .....f
   e084 a62d 1bff                          | ...-..
                                           |     Decoded:
                                           | Mon, 21 Oct 2013 20:13:22
                                           | GMT
                                           | - evict: cache-control:
                                           |   private
                                           | -> date: Mon, 21 Oct 2013
                                           |   20:13:22 GMT
   c0                                      | == Indexed - Add ==
                                           |   idx = 64
                                           | -> location:
                                           |   https://www.example.com
   5a                                      | == Literal indexed ==
                                           |   Indexed name (idx = 26)
                                           |     content-encoding
   83                                      |   Literal value (len = 3)
                                           |     Huffman encoded:
   9bd9 ab                                 | ...
                                           |     Decoded:
                                           | gzip
                                           | - evict: date: Mon, 21 Oct
                                           |    2013 20:13:21 GMT
                                           | -> content-encoding: gzip
   77                                      | == Literal indexed ==
                                           |   Indexed name (idx = 55)
                                           |     set-cookie
   ad                                      |   Literal value (len = 45)
                                           |     Huffman encoded:
   94e7 821d d7f2 e6c7 b335 dfdf cd5b 3960 | .........5...[9`
   d5af 2708 7f36 72c1 ab27 0fb5 291f 9587 | ..'..6r..'..)...
   3160 65c0 03ed 4ee5 b106 3d50 07        | 1`e...N...=P.
                                           |     Decoded:
                                           | foo=ASDJKHQKBZXOQWEOPIUAXQ
                                           | WEOIU; max-age=3600; versi
                                           | on=1
                                           | - evict: location:
                                           |   https://www.example.com
                                           | - evict: :status: 307
                                           | -> set-cookie: foo=ASDJKHQ
                                           |   KBZXOQWEOPIUAXQWEOIU; ma
                                           |   x-age=3600; version=1

编码后的动态表:

   [  1] (s =  98) set-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU;
                    max-age=3600; version=1
   [  2] (s =  52) content-encoding: gzip
   [  3] (s =  65) date: Mon, 21 Oct 2013 20:13:22 GMT
         Table size: 215

解码后的 header 列表:

   :status: 200
   cache-control: private
   date: Mon, 21 Oct 2013 20:13:22 GMT
   location: https://www.example.com
   content-encoding: gzip
   set-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1

7. 一些抓包的例子

先来看看首次请求中 HPACK 是如何压缩 HEADERS 帧中的首部字段的。

HTTP/2 HPACK 实际应用举例

:method:GET 在静态表中的第 2 项。Name 和 Value 都已经存在了。所以直接用 2 即可表示这一项头部字段。

HTTP/2 HPACK 实际应用举例

相同的,:path:/ 在静态表中的第 4 项。Name 和 Value 都已经存在了。所以直接用 4 即可表示这一项头部字段。

HTTP/2 HPACK 实际应用举例

再来看看第二次请求中,同样是 :method:GET,和第一次请求一样,直接用 2 即可表示这一项头部字段。

HTTP/2 HPACK 实际应用举例

回到首次请求中,if-none-match 首部字段,在静态表中的第 41 项,但是静态表里面没有值。根据前一篇文章讲解的 HPACK 算法,压缩串以 01 开头,101001 是 41,10011110,第一个 1 代表是霍夫曼编码,0011110 代表 30,表明 value 是紧接着的 30 个字节里面的内容。

HTTP/2 HPACK 实际应用举例

还是首次请求,user-agent 首部字段,在静态表中的第 58 项,但是静态表里面没有值。根据前一篇文章讲解的 HPACK 算法,压缩串以 01 开头,111010 是 58,11011011,第一个 1 代表是霍夫曼编码,1011011 代表 91,表明 value 是紧接着的 91 个字节里面的内容。

HTTP/2 HPACK 实际应用举例

到了第二次请求中,user-agent 首部字段在动态表中已经存储了 name 和 value 了,所以直接命中动态表中第 86 项。1010110 代表的就是 86。这个例子可以很明显的看到,动态表大幅缩减了 header 大小。

对比同一个 HTTP/2 连接中的 2 次相同的请求。可以看到首部大小已经大幅减少了。

HTTP/2 HPACK 实际应用举例

在首次请求中,HAPCK 使得原有的头部减少了 44%。

HTTP/2 HPACK 实际应用举例

在第二次请求中,由于补充了动态表,HAPCK 使得原有的头部减少了 97%。

8. HPACK 优化效果

最后,让我们用工具具体测试一下 HPACK 的“威力”。可以使用 h2load 工具测试

以下分别是 3 个测试用例,第一个测试用例只请求一次,第二个测试用例请求二次,第三个测试用例请求三次,看每次测试用来能缩小头部字段开销。

HTTP/2 HPACK 实际应用举例

从上图中可以看到,请求越多,头部字段越来越小。

请求次数 首部字段占比 节约百分比
1 1.002% 29.89%
2 0.521% 63.75%
3 0.359% 75.04%
5 0.241% 83.28%
10 0.137% 90.48%
20 0.092% 93.65%
30 0.074% 94.85%
50 0.061% 95.75%
100 0.052% 96.39%

由此可以看出 HTTP/2 中的 HPACK 算法对 header 整体的压缩率还是非常不错的。


Reference:

RFC 7541

GitHub Repo:Halfrost-Field

Follow: halfrost · GitHub

Source: GHOST_URL/http2-hpack-example/

详解 HTTP/2 头压缩算法 —— HPACK

一. 简介

详解 HTTP/2 头压缩算法 —— HPACK

在 HTTP/1.1(请参阅[RFC7230])中,header 字段未被压缩。随着网页内的请求数增长到需要数十到数百个请求的时候,这些请求中的冗余 header 字段不必要地消耗了带宽,从而显着增加了延迟。

SPDY [SPDY] 最初通过使用 DEFLATE [DEFLATE] 格式压缩 header 字段来解决此冗余问题,事实证明,这种格式非常有效地表示了冗余 header 字段。但是,这种方法暴露了安全风险,如 CRIME(轻松实现压缩率信息泄漏)攻击所证明的安全风险(请参阅 [CRIME])。

本规范定义了 HPACK,这是一种新的压缩方法,它消除了多余的 header 字段,将漏洞限制到已知的安全攻击,并且在受限的环境中具有有限的内存需求。第 7 节介绍了 HPACK 的潜在安全问题。

HPACK 格式特意被设计成简单且不灵活的形式。两种特性都降低了由于实现错误而引起的互操作性或安全性问题的风险。没有定义扩展机制;只能通过定义完整的替换来更改格式。

1. 总览

详解 HTTP/2 头压缩算法 —— HPACK

本规范中定义的格式将 header 字段列表视为 name-value 对的有序集合,其中可以包括重复的对。名称和值被认为是八位字节的不透明序列,并且 header 字段的顺序在压缩和解压缩后保持不变。

header 字段表将 header 字段映射到索引值,从而得到编码。这些 header 字段表可以在编码或解码新 header 字段时进行增量更新。

在编码形式中,header 字段以字面形式表示或作为对 header 字段表中的一个 header 字段的引用。因此,可以使用引用和字面值的混合来编码 header 字段的列表。

字面值可以直接编码,也可以使用静态霍夫曼编码(最高压缩比 8:5)。

编码器负责决定将哪些 header 字段作为新条目插入 header 字段表中。解码器执行对编码器指定的 header 字段表的修改,从而在此过程中重建 header 字段的列表。这使解码器保持简单并可以与多种编码器互操作。

附录C 中提供了使用这些不同的机制表示 header 字段的示例。

注:在 HTTP/2 中,请求和响应标头字段的定义保持不变,仅有一些微小的差异:所有标头字段名称均为小写,请求行现在拆分成各个 :method、:scheme、:authority 和 :path 伪标头字段。

2. 约定

本文档中的关键字 “必须”,“不得”,“必须”,“应”,“应禁止”,“应”,“不应”,“建议”,“可以”和“可选”是 RFC 2119 [RFC2119] 中定义的。

所有数值均以网络字节顺序排列。 除非另有说明,否则值是无符号的。适当时以十进制或十六进制提供字面值。

3. 术语

本文使用以下术语:

Header Field:一个名称/值 name-value 对。名称和值都被视为八位字节的不透明序列。

Dynamic Table:动态表(请参阅第 2.3.2 节)是将存储的 header 字段与索引值相关联的表。该表是动态的,并且特定于编码或解码上下文。

Static Table:静态表(请参阅第 2.3.1 节)是将经常出现的 header 字段与索引值静态关联的表。该表是有序的,只读的,始终可访问的,并且可以在所有编码或解码上下文之间共享。

Header List:header 列表是 header 字段的有序集合,这些 header 字段经过联合编码,可以包含重复的 header 字段。HTTP/2 header 块中包含的 header 字段的完整列表是 header 列表。

Header Field Representation:header 字段可以编码形式表示为字面或索引(请参见第 2.4 节)。

Header Block:header 字段表示形式的有序列表,解码后会产生完整的 header 列表。

二. 压缩过程概述

本规范未描述编码器的具体算法。相反,它精确定义了解码器的预期工作方式,从而允许编码器产生此定义允许的任何编码。

1. Header List Ordering

HPACK 保留 header 列表内 header 字段的顺序。编码器必须根据其在原始 header 列表中的顺序对 header 块中的 header 字段表示进行排序。解码器必须根据其在 header 块中的顺序对已解码 header 列表中的 header 字段进行排序。

2. Encoding and Decoding Contexts

为了解压缩 header 块,解码器只需要维护一个动态表(参见第 2.3.2 节)作为解码上下文。不需要其他动态状态。

当用于双向通信时(例如在 HTT P中),由端点维护的编码和解码动态表是完全独立的,即请求和响应动态表是分开的。

3. Indexing Tables

HPACK 使用两个表将 header 字段与索引相关联。静态表(请参阅第 2.3.1 节)是预定义的,并包含公共 header 字段(其中大多数带有空值)。动态表(请参阅第 2.3.2 节)是动态的,编码器可以使用它来索引已编码 header 列表中重复的 header 字段。

这两个表被合并到一个用于定义索引值的地址空间中(请参阅第 2.3.3 节)。

(1) 静态表

静态表由 header 字段的预定义静态列表组成。其条目在附录 A 中定义。

(2) 动态表

动态表包含以先进先出的顺序维护的 header 字段列表。动态表中的第一个条目和最新条目在最低索引处,而动态表的最旧条目在最高索引处。

动态表最初是空的。当每个 header 块被解压缩时,将添加条目。动态表可以包含重复的条目(即,具有相同名称和相同值的条目)。因此,解码器不得将重复的条目视为错误。

编码器决定如何更新动态表,因此可以控制动态表使用多少内存。为了限制解码器的存储需求,动态表的 size 受到严格限制(请参见第 4.2 节)。

解码器在处理 header 字段表示列表时更新动态表(请参见第 3.2 节)。

(3) 索引地址空间

静态表和动态表被组合到单个索引地址空间中。

在 1 和静态表的长度(包括在内)之间的索引是指静态表中的元素(请参阅第 2.3.1 节)。

严格大于静态表长度的索引是指动态表中的元素(请参见第 2.3.2 节)。 减去静态表的长度即可找到动态表的索引。

严格大于两个表的长度之和的索引必须视为解码错误。

对于 s 的静态表 size 和 k 的动态表 size ,下图显示了整个有效索引地址空间

详解 HTTP/2 头压缩算法 —— HPACK

4. Header Field Representation

编码的 header 字段可以表示为索引或字面。

有索引的表示形式定义了一个 header 字段,作为对静态表或动态表中条目的引用(请参见第 6.1 节);字面表示形式通过指定其 name 和 value 来定义 header 字段。header 字段 name 可以用字面形式表示,也可以作为对静态表或动态表中条目的引用。header 字段 value 按字面表示。定义了三种不同的字面表示形式:

  • 在动态表的开头添加 header 字段作为新条目的字面表示形式(请参见第 6.2.1 节)。

  • 不将 header 字段添加到动态表的字面表示形式(请参见第 6.2.2 节)。

  • 不将 header 字段添加到动态表的字面表示形式,另外规定该 header 字段始终使用字面表示形式,尤其是在由中介程序重新编码时(请参阅第 6.2.3 节)。此表示旨在保护 header 字段值,这些 header 字段值通过压缩以后就不会受到威胁(有关更多详细信息,请参见第 7.1.3 节)。

为了保护敏感的 header 字段值(请参阅第 7.1 节),可以从安全考虑出发选择这些字面表示形式之一。

header 字段 name 或 header 字段 value 的字面表示可以直接或使用静态霍夫曼代码对八位字节序列进行编码(请参见第 5.2 节

三. header 块的解码

1. Header Block Processing

解码器顺序处理 header 块以重建原始 header 列表。

header 块是 header 字段表示形式的串联。第 6 节中介绍了不同的可能的 header 字段表示形式。

一旦 header 字段被解码并添加到重建的 header 列表中,就不能删除 header 字段。添加到 header 列表的 header 字段可以安全地传递到应用程序。

通过将结果 header 字段传递给应用程序,除了动态表所需的内存外,还需要使用最少的临时内存来实现解码器。

2. Header Field Representation Processing

在本节中定义了对 header 块进行处理以获得 header 列表的过程。为了确保解码将成功产生 header 列表,解码器必须遵守以下规则。

header 块中包含的所有 header 字段表示形式将按照它们出现的顺序进行处理,如下所示。有关各种 header 字段表示形式的格式的详细信息以及一些其他处理指令,请参见第 6 节

_indexed representation_需要执行以下操作:

  • 与静态表或动态表中被引用条目相对应的 header 字段被附加到解码后的 header 列表中。

动态表中未添加的 “_literal representation_” 需要执行以下操作:

  • header 字段被附加到解码的 header 列表中。

在动态表中添加了 “_literal representation_” 需要执行以下操作:

  • header 字段被附加到解码的 header 列表中。
  • header 字段插入在动态表的开头。这种插入可能导致驱逐动态表中的先前条目(请参见第 4.4 节)。

四. 动态表管理

详解 HTTP/2 头压缩算法 —— HPACK

为了限制解码器端的存储要求,动态表的 size 受到限制。

动态字典上下文有关,需要为每个 HTTP/2 连接维护不同的字典。

1. Calculating Table Size

动态表的 size 是其表项 size 的总和。条目的 size 是其 name 的长度(以八位字节为单位)(如第 5.2 节中所定义),value 的长度(以八位字节为单位)和 32 的总和。条目的 size 是使用其 name 和 value 的长度来计算的,而无需应用任何霍夫曼编码。

注意:额外的 32 个八位字节说明了与条目相关的估计开销。例如,使用两个 64 位指针引用条目的 name 和 value 以及使用两个 64 位整数来计数对该 name 和 value 的引用次数的条目结构,该数据结构将具有 32 个八位字节的开销。(64*2*2/8=32 字节)

2. Maximum Table Size

使用 HPACK 的协议确定允许编码器用于动态表的最大 size 。在 HTTP/2 中,此值由 SETTINGS_HEADER_TABLE_SIZE 设置来确定(请参见[HTTP2]的 6.5.2 节)。

编码器可以选择使用小于此最大 size 的容量(请参阅第 6.3 节),但是所选 size 必须保持小于或等于协议设置的最大容量。

动态表最大 size 的变化是因为动态表 size 的更新引起的(请参见第 6.3 节)。动态表 size 更新必须在更改动态表 size 之后的第一个 header 块的开头进行。在 HTTP/2 中,这遵循 settings 的确认(请参阅 [HTTP2]的 6.5.3 节)。

在传输两个 header 块之间,可能会发生多次最大表 size 的更新。如果在此间隔中,这个 size 更改一次以上的话,那么就必须在动态表 size 更新中,用信号通知在该间隔中出现的,最小的最大表 size 。一定会发出最终最大 size 的信号,从而导致最多两个动态表 size 的更新。这样可确保解码器能够基于动态表 size 的减小执行逐出(请参见第 4.3 节)。

使用此机制通过将最大 size 设置为 0,从动态表中完全清除条目,然后可以将其恢复。

HTTP/2 提倡使用尽可能少的连接数,头部压缩是其中一个重要的原因:在同一个连接上产生的请求和响应越多,动态字典累积的越全,头部压缩的效果就越好。

3. Entry Eviction When Dynamic Table Size Changes

只要减小了动态表的最大 size,就会从动态表的末尾逐出条目,直到动态表的 size 小于或等于最大 size 为止。

4. Entry Eviction When Adding New Entries

在将新条目添加到动态表之前,将从动态表的末尾逐出条目,直到动态表的 size 小于或等于(最大 size -新条目大小)或直到表为空。

如果新条目的 size 小于或等于最大 size,则会将该条目添加到表中。 尝试添加大于最大 size 的条目不是错误;尝试添加大于最大 size 的条目会导致该表清空所有现有条目,并导致表为空。

新条目可以引用动态表中条目 A 的 name,当将该新条目添加到动态表中时,该条目 A 将被逐出。请注意,如果在插入新条目之前从动态表中删除了引用条目,则应避免删除引用 name。

五. 基本类型表示

HPACK 编码使用两种原始类型:无符号的可变长度整数和八位字节串。

1. Integer Representation

整数用于表示 name 索引,header 字段索引或字符串长度。整数表示可以在八位字节内的任何位置开始。为了优化处理,整数表示总是在八位字节的末尾结束。

整数分为两部分:填充当前八位字节的前缀和可选的八位字节列表,如果整数值不适合该前缀,则使用这些可选的八位字节。前缀的位数(称为 N)是整数表示的参数。

如果整数值足够小,即严格小于 2^N-1,则将其编码在 N 位前缀中。

详解 HTTP/2 头压缩算法 —— HPACK

上图的例子中,N = 5,所以能表示的最大的整数是 2^5-1 = 31

如果整数数值大于 2^N-1,则将前缀的所有位设置为 1,并使用一个或多个八位字节的列表对减少了 2^N-1 的值进行编码。每个八位字节的最高有效位用作连续标志:除了列表中的最后一个八位字节,其值均设置为 1。八位字节的其余位用于对减小的值进行编码。

详解 HTTP/2 头压缩算法 —— HPACK

从八位字节列表中解码整数值是通过反转八位字节在列表中的顺序开始的。 然后,对于每个八位字节,将其最高有效位删除。八位字节的其余位被级联起来,结果值增加 2^N-1 以获得整数值。

前缀 size N 始终在 1 到 8 位之间。从八位字节边界开始的整数将具有 8 位前缀。

表示整数 I 的伪代码如下:

   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

附录 C.1 中提供了说明整数编码的示例。

整数表示形式允许使用不确定大小的值。编码器也可能发送大量的零值,这可能浪费八位字节,并可能使整数值溢出。超出实现限制的整数编码(值或八位字节长度)必须视为解码错误。基于实现方的约束,可以为整数的每种不同用途设置不同的限制。

2. String Literal Representation

header 字段 name 和 header 字段 value 可以表示为字符串字面量。可以通过直接编码字符串字面的八位字节或使用霍夫曼代码将字符串字面编码为八位字节序列(请参见[HUFFMAN]

详解 HTTP/2 头压缩算法 —— HPACK

字符串字面表示形式包含以下字段:

  • H:
    一位标志 H,指示字符串的八位字节是否经过霍夫曼编码。

  • String Length:
    用于编码字符串字面的八位字节数,编码为带有 7 位前缀的整数(请参阅第 5.1 节)。

  • String Data:
    字符串字面的编码数据。如果 H 为'0',则编码后的数据为字符串字面量的原始八位字节。如果 H 为'1',则编码数据为字符串字面量的霍夫曼编码。

使用霍夫曼编码的字符串字面量使用 附录 B 中定义的霍夫曼代码进行编码(有关示例,请参见 附录 C.4 中的示例以及 附录 C.6 中的响应示例)。编码的数据是与字符串字面的每个八位字节相对应的代码的按位级联。

由于霍夫曼编码的数据并不总是在八位字节的边界处结束,因此在其后插入填充,直到下一个八位字节的边界。为避免将此填充误解为字符串字面的一部分,使用了与 EOS(end-of-string)符号相对应的代码的最高有效位。

在解码时,编码数据末尾的不完整代码将被视为填充和丢弃。严格长于 7 位的填充必须被视为解码错误。与 EOS 符号的代码的最高有效位不对应的填充必须被视为解码错误。包含 EOS 符号的霍夫曼编码的字符串字面必须被视为解码错误。

六. 二进制格式

本节描述每种不同的 header 字段表示形式的详细格式以及动态表大小更新指令。

1. 索引 header 字段表示

索引 header 字段表示可标识静态表或动态表中的条目(请参见第 2.3 节)。

索引的 header 字段表示会将 header 字段添加到已解码的 header 列表中,如第 3.2 节所述。

详解 HTTP/2 头压缩算法 —— HPACK

上面这种情况对应的是 Name 和 Value 都在索引表(包括静态表和动态表)中

索引 header 字段以 1 位模式 “1” 开头,后跟匹配 header 字段的索引,以 7 位前缀的整数表示(请参阅第 5.1 节)。

不使用索引值 0。如果在索引 header 域表示中发现了索引值 0,则必须将其视为解码错误。

2. 字面 header 字段标识

header 字段表示形式包含字面 header 字段 value。header 字段名称 name 以字面形式提供,也可以通过引用静态表或动态表中的现有表条目来提供(请参见第 2.3 节)。

本规范定义了字面 header 字段表示形式的三种形式:带索引,不带索引以及从不索引。

(1). 带增量索引的字面 header 字段

具有增量索引表示形式的字面 header 字段会将 header 字段附加到已解码的 header 列表中,并将其作为新条目插入动态表中。

详解 HTTP/2 头压缩算法 —— HPACK

上面这种情况对应的是 Name 在索引表(包括静态表和动态表)中,Value 需要编码传递,并同时新增到动态表中

详解 HTTP/2 头压缩算法 —— HPACK

上面这种情况对应的是 Name 和 Value 都需要编码传递,并同时新增到动态表中

具有增量索引表示的字面 header 字段以 “01” 2 位模式开头。

如果 header 字段名称 name 与存储在静态表或动态表中的条目的 header 字段名称 name 匹配,则可以使用该条目的索引表示 header 字段名称 name。在这种情况下,条目的索引表示为带有 6 位前缀的整数(请参阅第 5.1 节)。此值一般为非零值。

否则,header 字段名称 name 表示为字符串字面(请参见第 5.2 节)。使用值 0 代替 6 位索引,后跟 header 字段名称 name。

两种形式的 header 字段名称 name 表示形式之后跟着的是以字符串字面表示的 header 字段值 value(参见第 5.2 节)。

(2). 不带索引的字面 header 字段

没有索引表示形式的字面 header 字段会使在不更改动态表的情况下将 header 字段附加到已解码的 header 列表中。

详解 HTTP/2 头压缩算法 —— HPACK

上面这种情况对应的是 Name 在索引表(包括静态表和动态表)中,Value 需要编码传递,并不新增到动态表中

详解 HTTP/2 头压缩算法 —— HPACK

上面这种情况对应的是 Name 和 Value 需要编码传递,并不新增到动态表中

没有索引表示的字面 header 字段以 “0000” 4 位模式开头。

如果 header 字段名称 name 与存储在静态表或动态表中的条目的 header 字段名称 name 匹配,则可以使用该条目的索引表示 header 字段名称 name。在这种情况下,条目的索引表示为带有 4 位前缀的整数(请参见第 5.1 节)。此值一般为非零值。

否则,header 字段名称 name 表示为字符串字面(请参见第 5.2 节)。使用值 0 代替 4 位索引,后跟 header 字段名称 name。

两种形式的 header 字段名称 name 表示形式之后跟着的是字符串字面的 header 字段值 value(参见第 5.2 节)。

(3). 从不索引的字面 header 字段

字面 header 字段永不索引表示形式会使得在不更改动态表的情况下将 header 字段附加到已解码的 header 列表中。中间件必须使用相同的表示形式来编码该 header 字段。

详解 HTTP/2 头压缩算法 —— HPACK

上面这种情况对应的是 Name 在索引表(包括静态表和动态表)中,Value 需要编码传递,并永远不新增到动态表中

详解 HTTP/2 头压缩算法 —— HPACK

上面这种情况对应的是 Name 和 Value 需要编码传递,并永远不新增到动态表中

字面 header 字段永不索引的表示形式以 “0001” 4 位模式开头。

当 header 字段表示为永不索引的字面 header 字段时,务必使用此特定字面表示进行编码。特别地,当一个对端发送了一个接收到的 header 域的时候,并且接收到的 header 表示为从未索引的字面 header 域时,它必须使用相同的表示来转发该 header 域。

此表示目的是为了保护 header 字段值 value,通过压缩来保护它们不会被置于风险之中(有关更多详细信息,请参见第 7.1 节)。

该表示形式的编码与不带索引的字面 header 字段相同(请参见第 6.2.2 节)。

3. 动态表大小更新

动态表 size 更新代表更改动态表 size。

详解 HTTP/2 头压缩算法 —— HPACK

动态表 size 更新从 “001” 3 位模式开始,然后是新的最大 size,以 5 位前缀的整数表示(请参阅第 5.1 节)。

新的最大 size 必须小于或等于协议使用 HPACK 确定的限制。超过此限制的值必须视为解码错误。在 HTTP/2 中,此限制是从解码器接收并由编码器(请参见 [HTTP2]的 6.5.3 节)确认的 SETTINGS_HEADER_TABLE_SIZE (请参见 [HTTP2]的 6.5.2 节)参数的最后一个值。

减小动态表的最大 size 会导致驱逐条目(先进先出)(请参见第 4.3 节)。

动态表大小更新有上述这两种方式,一种是在 HEADERS 帧中直接修改(“001” 3 位模式开始),另外一种方式是通过 SETTINGS 帧中的 SETTINGS_HEADER_TABLE_SIZE 中设置的。

七. 安全注意事项

本节介绍了 HPACK 的潜在安全隐患:

  • 将压缩用作基于长度的预测,以验证有关被压缩到共享压缩上下文中的加密的猜想。

  • 由于耗尽解码器的处理或存储容量而导致的拒绝服务。

1. 探测动态表状态

HPACK 通过利用 HTTP 等协议固有的冗余性来减少 header 字段编码的长度。这样做的最终目的是减少发送 HTTP 请求或响应所需的数据量。

攻击者可以探测用于编码 header 字段的压缩上下文,攻击者也可以定义要编码和传输的 header 字段,并在编码后观察这些字段的长度。当攻击者可以同时执行这两种操作时,他们可以自适应地修改请求,以确认有关动态表状态的猜想。如果将猜想压缩到较短的长度,则攻击者可以观察编码的长度并推断出猜测是正确的。

即使通过传输层安全性(TLS)协议(请参阅 [TLS12]),这也是有可能被攻击的,因为 TLS 为内容提供加密保护,但仅提供有限的内容长度保护。

注意:填充方案只能对具有这些功能的攻击者提供有限的保护能力,可能对攻击者的影响仅仅只是迫使他增加猜测的次数,来推测与给定猜测相关的长度。填充方案还可以通过增加传输的位数直接抵抗压缩。

诸如 CRIME [CRIME] 之类的攻击证明了这些攻击者的存在。特定攻击利用了 DEFLATE [DEFLATE] 删除基于前缀匹配的冗余这一事实。这使攻击者一次可以确定一个字符,从而将指数时间的 攻击减少为线性时间的攻击。

(1). 适用于 HPACK 和 HTTP

HPACK 通过强制猜测以匹配整个 header 字段值而不是单个字符,来缓解但不能完全阻止以CRIME [CRIME] 为模型的攻击。攻击者只能了解猜测是否正确,因此可以将攻击手段其简化为针对 header 字段值的蛮力猜测。因此,恢复特定 header 字段值的可行性取决于值的熵。结果是,具有高熵的值不太可能成功恢复。但是,低熵值仍然容易受到攻击。

每当两个互不信任的实体在单个 HTTP/2 连接上的接收和发送请求或响应时,就可能发生这种性质的攻击。如果共享的 HPACK 压缩器允许一个实体向动态表添加条目,而另一实体访问这些条目,则可以了解到表的状态。

当中间件发生以下情况时,就会出现来自互不信任实体的请求或响应:

  • 从单个连接上的多个客户端向原始服务器发送请求。

  • 从多个原始服务器获取响应,并将其在与客户端的共享连接上发送响应。

Web 浏览器还需要假设不同 Web 来源 [ORIGIN] 在同一连接上发出的请求是由互不信任的实体发出的。

(2). 减轻

要求 header 字段具有加密性的 HTTP 用户可以使用具有足以使猜测不可行的熵的值。但是,这作为通用解决方案是不切实际的,因为它会强制 HTTP 的所有用户采取措施减轻攻击。它将对使用 HTTP 的方式施加新的限制。

HPACK 的实现不是在 HTTP 用户上施加约束,而是可以约束压缩的应用方式,以限制动态表探测的潜力。

理想的解决方案基于正在构造 header 字段的实体来隔离对动态表的访问。添加到表中的 header 字段值将归因于一个实体,只有创建特定值的实体才能提取该值。

为了提高此选项的压缩性能,可以将某些条目标记为公共。例如,Web 浏览器可能使 Accept-Encoding header 字段的值在所有请求中都可用。

不太了解 header 字段出处的编码器可能会对具有许多不同值的 header 字段引入惩罚机制,如果攻击者大量尝试去猜测 header 字段值,触发惩罚机制,会导致 header 字段在将来的消息中不再与动态表实体进行比较。这样可以有效地防止了进一步的猜测。

注意:如果攻击者有一个可靠的方法来重新安装值,只是从动态表中删除与 header 字段相对应的条目可能是无效的攻击。例如,在网络浏览器中加载图像的请求通常包含 Cookie header 字段(此类攻击的潜在价值很高的目标),并且网站可以轻松地强制加载图像,从而刷新动态表中的条目。

该响应可能与 header 字段值的长度成反比。与更短的值相比,更短的值更可能以更快的速度或更高的概率将 header 字段标记为不再使用的动态表。

(3). 永不索引的字面

实现方也可以选择不对敏感 header 字段进行压缩,而是将其值编码为字面,从而保护它们。

仅仅只在避免在所有跃点上都进行压缩的情况下,拒绝生成 header 字段的索引表示才有效。永不索引的字面(请参阅第 6.2.3 节)可用于向中间件发出信号,指示有意将特定值作为字面发送。

中间件不得将使用永不索引的字面表示形式的值与将对其进行索引的另一个表示形式重新编码。如果使用 HPACK 进行重新编码,则必须使用永不索引的字面表示。

对于 header 字段使用从不索引的字面表示形式的选择取决于多个因素。由于 HPACK 不能防止猜测整个 header 字段值,因此攻击者更容易恢复短的或低熵的值。因此,编码器可能选择不索引具有低熵的值。

编码器还可能选择不为被认为具有很高价值或对恢复敏感的 header 字段(例如 Cookie 或授权 header 字段)的值增加索引。

相反,如果值被公开了,则编码器可能更喜欢索引值很小或没有值的 header 字段的索引值。例如,User-Agent header 字段在请求之间通常不会发生变化,而是发送到任何服务器。在这种情况下,确认已使用特定的 User-Agent 值提供的价值很小。

请注意,随着新的攻击不断被发现,这些决定使用永不索引的字面表示形式的标准将随着时间的推移而演变。

2. 静态霍夫曼编码

目前还没有针对静态霍夫曼编码的攻击。一项研究表明,使用静态霍夫曼编码表会造成信息泄漏; 但是,同一项研究得出的结论是,攻击者无法利用此信息泄漏来恢复任何有意义的信息量(请参阅 [PETAL]

动态的霍夫曼编码容易受到攻击!

3. 内存管理

攻击者可以尝试使端点耗尽其内存。HPACK 旨在限制端点分配的内存峰值和状态量。

压缩程序使用的内存量受到遵循 HPACK 协议的动态表中定义的最大 size 限制。在 HTTP/2 中,此值由解码器通过设置参数 SETTINGS_HEADER_TABLE_SIZE 来控制的(请参见 [HTTP2]的 6.5.2 节)。此限制既考虑了动态表中存储的数据大小,又考虑了少量的开销。

解码器可以通过为动态表的最大 size 设置适当的值来限制状态存储器的使用量。在 HTTP/2 中,这是通过为 SETTINGS_HEADER_TABLE_SIZE 参数设置适当的值来实现的。编码器可以通过发信号通知动态表 size 小于解码器允许的状态来限制其使用的状态存储器的数量(请参见第 6.3 节)。

编码器或解码器消耗的临时内存量可以通过顺序处理 header 字段来限制。实现方不需要保留 header 字段的完整列表。但是请注意,由于其他原因,应用程序可能有必要保留完整的 header 列表。即使 HPACK 不会强迫这种情况发生,应用程序约束也可能使得它变得有必要。

4. 实现方的限制

HPACK 的实现方需要确保整数的大值,整数的长编码或长字符串字面不会造成安全漏洞。

一个实现必须为它接受的整数值和编码长度设置一个限制(请参阅第 5.1 节)。同样,它必须为字符串字面设置一个限制长度(请参见第 5.2 节)。


Reference:

RFC 7541

GitHub Repo:Halfrost-Field

Follow: halfrost · GitHub

Source: GHOST_URL/http2-header-compression/

Hypertext Transfer Protocol Version 2 (HTTP/2)

Table of Contents

1. Introduction

2. HTTP/2 Protocol Overview

3. Starting HTTP/2

4. HTTP Frames

5. Streams and Multiplexing

6. Frame Definitions

7. Error Codes

8. HTTP Message Exchanges

9. Additional HTTP Requirements/Considerations

10. Security Considerations

11. IANA Considerations

12. References

Hypertext Transfer Protocol Version 2 (HTTP/2)

这一章都是引用的论文,所以就不翻译了。

  • 12.1. Normative References
  • 12.2. Informative References

Appendix A. TLS 1.2 Cipher Suite Black List

这一章是 TLS 1.2 中加入黑名单的加密套件


Reference:

RFC 7540

GitHub Repo:Halfrost-Field

Follow: halfrost · GitHub

Source: https://halfrost.com/http2_rfc7540/

HTTP/2 中的常见问题

HTTP/2 中的常见问题

以下是有关 HTTP/2 的常见问题。

一. 一般的问题

1. 为什么要修改 HTTP?

HTTP/1.1 在 Web 上已经服务了 15 年以上,但是它的缺点正在开始显现。加载网页比以往任何时候都需要更多资源(请参阅HTTP Archive’s page size statistics),并且要高效地加载所有这些资源非常困难,因为事实上,HTTP 只允许每个 TCP 连接有一个未完成的请求。

过去,浏览器使用多个 TCP 连接来发出并行请求。但是,这是有局限性的。如果使用的连接过多,则将适得其反(TCP 拥塞控制将被无效化,导致的用塞事件将会损害性能和网络),并且从根本上讲是不公平的(因为浏览器会占用许多本不该属于它的资源)。同时,大量请求意味着“在线”上有大量重复数据。

这两个因素都意味着 HTTP/1.1 请求有很多与之相关的开销。如果请求过多,则会影响性能。

这使得业界误解了“最佳实践”,进行诸如 spriting 图片合并,data: inlining 内联数据,Domain Sharding 域名分片和 Concatenation 文件合并之类的事情。这些 hack 行为表明协议本身存在潜在问题,在使用的时候会出现很多问题。

2. 谁制定了 HTTP/2?

HTTP/2 是由 IETFHTTP 工作组开发的,该工作组维护 HTTP 协议。它由许多 HTTP 实现者,用户,网络运营商和 HTTP 专家组成。

请注意,虽然我们的邮件列表托管在 W3C 网站上,但这并不是 W3C 的努力。但是,Tim Berners-Lee 和 W3C TAG 与 WG 的工作进度保持同步。

大量的人为这项工作做出了贡献,最活跃的参与者包括来自诸如 Firefox,Chrome,Twitter,Microsoft 的 HTTP stack,Curl 和 Akamai 等“大型”项目的工程师,以及许多诸如 Python、Ruby 和 NodeJS 之类的 HTTP 实现者。

要了解有关 IETF 的更多信息,请参见Tao of the IETF。您还可以在 Github 的贡献者图中了解谁为规范做出了贡献,以及谁在我们的实现列表中参与该项目。

3. HTTP/2 与 SPDY 有什么关系?

HTTP/2 第一次出现并被讨论的时候,SPDY 正逐渐受到实现者(例如 Mozilla 和 nginx)的青睐时,并且被当成对 HTTP/1.x 的重大改进。

在征求提案和进行选择过程之后,选择 SPDY/2 作为 HTTP/2 的基础。此后,根据工作组的讨论和实现者的反馈,进行了许多更改。在整个过程中,SPDY 的核心开发人员都参与了 HTTP/2 的开发,包括 Mike Belshe 和 Roberto Peon。2015 年 2 月,Google 宣布了其计划删除对 SPDY 的支持,转而支持 HTTP/2。

4. 是 HTTP/2.0 还是 HTTP/2?

工作组决定删除次版本(“.0”),因为它在 HTTP/1.x 中引起了很多混乱。换句话说,HTTP 版本仅表示网络兼容性,而不表示功能集或“亮点”。

5. HTTP/2 和 HTTP/1.x 的主要区别是什么?

在高版本的 HTTP/2 中:

  • 是二进制的,而不是文本的
  • 完全多路复用,而不是有序和阻塞
  • 因此可以使用一个连接进行并行处理
  • 使用头压缩​​来减少开销
  • 允许服务器主动将响应"推送"到客户端缓存中

6. 为什么 HTTP/2 是二进制的?

与诸如 HTTP/1.x 之类的文本协议相比,二进制协议解析起来更高效,更“紧凑”,并且最重要的是,它们比二进制协议更不容易出错,因为它们对空格处理,大写,行尾,空白行等的处理很有帮助。例如,HTTP/1.1 定义了四种不同的解析消息的方式。在 HTTP/2 中,只有一个代码路径。

HTTP/2 在 telnet 中不可用,但是我们已经有了一些工具支持,例如 Wireshark 插件

7. 为什么 HTTP/2 需要多路复用?

HTTP/1.x 存在一个称为“队头阻塞”的问题,指的是一次连接(connection)中,只提交一个请求的效率比较高,多了就会变慢。

HTTP/1.1 试图通过管道修复此问题,但是并不能完全解决问题(较大或较慢的响应仍会阻止其他问题)。此外,由于许多中间件和服务器未正确处理管线化,因此很难部署它。

这迫使客户使用多种试探法(通常是猜测法)来决定通过哪些连接提交哪些请求;由于页面加载的数据量通常是可用连接数的 10 倍(或更多),因此会严重影响性能,通常会导致被阻止的请求“泛滥”。

多路复用通过允许同时发送多个请求和响应消息来解决这些问题。甚至有可能将一条消息的一部分与另一条消息混合在一起。所以在这种情况下,客户端只需要一个连接就能加载一个页面。

8. 为什么只有一个 TCP 连接?

使用 HTTP/1,浏览器打开每个站点需要 4 个到 8 个连接。现在很多网站都使用多点传输,因此这可能意味着单个页面加载会打开 30 多个连接。

一个应用程序打开如此多的连接,已经远远超出了当初设计 TCP 时的预想。由于每个连接都会响应大量的数据,这会造成中间网络中的缓冲区溢出的风险,从而导致网络拥塞事件并重新传输。

此外,使用这么多连接还会强占许多网络资源。这些资源都是从那些“遵纪守法”的应用那“偷”的(VoIP 就是个很好的例子)。

9. 服务器推送的好处是什么?

当浏览器请求页面时,服务器将在响应中发送 HTML,然后需要等待浏览器解析 HTML 并发出对所有嵌入资源的请求,然后才能开始发送 JavaScript,图像和 CSS。

服务器推送可以通过“推送”它认为客户端需要的响应到其缓存中,来避免服务器的这种往返延迟。

但是,“推送”响应不是“神奇的”——如果使用不正确,可能会损害性能。正确使用 Server Push 是正在进行的实验和研究领域。

10. 为什么我们需要头压缩?

Mozilla 的 Patrick McManus 通过计算平均页面加载消息头的效果,生动地展示了这一点。

假设一个页面包含大约 80 个资源需要加载(在当今的 Web 中是保守的),并且每个请求具有 1400 字节的消息头(这并不罕见,这要归功于 Cookie,Referer 等),至少要 7 到 8 个来回去“在线”获得这些消息头。这还不包括响应时间——那只是从客户端那里获取到它们所花的时间而已。

这是因为 TCP 的慢启动机制造成的,根据已确认的数据包数量,从而对新连接上发送数据的进行限制——有效地限制了最初的几次来回可以发送的数据包数量。

相比之下,即使对报头进行轻微的压缩,这些请求也可以在一次往返(甚至一个数据包)内搞定。

这种额外开销是相当大的,尤其是考虑到对移动客户端的影响时,即使在网络状况良好的条件下,移动客户端的往返延迟通常也要几百毫秒。

11. 为什么选择 HPACK?

SPDY/2 建议每个方都使用单独的 GZIP 上下文进行消息头压缩,该方法易于实现且效率很高。

从那时起,一个重要的攻击方式 CRIME 诞生了,这种方式可以攻击加密文件内部的所使用的压缩流(如 GZIP)。

使用 CRIME,攻击者有能力将数据注入加密流中,并可以“探测”明文并恢复它。由于这是 Web,因此 JavaScript 使这成为可能,而且已经有了通过对受到 TLS 保护的 HTTP 资源的使用CRIME来还原出 cookies 和认证令牌(Toekn)的案例。

结果,我们无法使用 GZIP 压缩。没有找到适合该用例并且可以安全使用的其他算法,我们创建了一种新的,专门针对报头的压缩方案,该方案以粗粒度压缩模式运行;由于 HTTP 标头通常在消息之间不改变,因此仍然可以提供合理的压缩效率,并且更加安全。

12. HTTP/2 可以使 Cookie(或其他头字段)变得更好吗?

这一努力被许可在网络协议的一个修订版本上运行 —— 例如,HTTP 消息头、方法等等如何才能在不改变 HTTP 语义的前提下放到“网络上”。

这是因为 HTTP 被广泛使用。如果我们使用此版本的 HTTP 引入一种新的状态机制(例如之前讨论过的例子)或更改了核心方法(值得庆幸的是,尚未提出该方法),则意味着新协议与现有 Web 不兼容。

特别是,我们希望能够在不损失任何信息的情况下从 HTTP/1 转换为 HTTP/2。如果我们开始“清理”报头(并且大多数人会同意,因为 HTTP 报头很乱),将会出现很多与现有 Web 互操作性的问题。

这样做只会对新协议的普及造成麻烦。

综上所述,HTTP 工作组负责所有 HTTP,而不仅仅是 HTTP/2。这样,我们可以研究与版本无关的新机制,只要它们与现有 Web 向后兼容即可。

13. 非浏览器的 HTTP 用户呢?

如果非浏览器应用程序已经在使用 HTTP,则它们也应该能够使用 HTTP/2。

先前收到过 HTTP “APIs” 在 HTTP/2 中具有良好性能等特点这样的反馈,那是因为 API 不需要在设计中考虑诸如请求开销之类的问题。

话虽如此,我们正在考虑的改进的主要焦点是典型的浏览用例,因为这是该协议的核心用例。

我们的章程对此表示:

The resulting specification(s) are expected to meet these goals for common existing deployments of HTTP; in particular, Web browsing (desktop and mobile), non-browsers ("HTTP APIs"), Web serving (at a variety of scales), and intermediation (by proxies, corporate firewalls, "reverse" proxies and Content Delivery Networks). Likewise, current and future semantic extensions to HTTP/1.x (e.g., headers, methods, status codes, cache directives) should be supported in the new protocol.

正在制定的规范需要满足现在已经普遍部署了的 HTTP 的功能要求;具体来说主要包括,Web 浏览(桌面端和移动端),非浏览器(“HTTP APIs” 形式的),Web 服务(大范围的),还有各种网络中介(借助代理,企业防火墙,反向代理以及内容分发网络实现的)。同样的,对 HTTP/1.x 当前和未来的语义扩展 (例如,消息头,方法,状态码,缓存指令) 都应该在新的协议中支持。


Note that this does not include uses of HTTP where non-specified behaviours are relied upon (e.g., connection state such as timeouts or client affinity,and "interception" proxies); these uses may or may not be enabled by the final product.

值得注意的是,这里没有包括将 HTTP 用于非特定行为所依赖的场景中(例如超时,连接状态以及拦截代理)。这些可能并不会被最终的产品启用。

14. HTTP/2 是否需要加密?

否。经过广泛讨论,工作组尚未对新协议必须要使用加密(例如 TLS)达成共识,。

但是,一些实现已声明它们仅在通过加密连接使用 HTTP/2 时才支持 HTTP/2,并且当前没有浏览器支持未加密的 HTTP/2。

15. HTTP/2 如何提高安全性?

HTTP/2 定义了必需的 TLS 配置文件;这包括了版本,密码套件黑名单和使用的扩展。

有关详细信息,请参见规范

还讨论了其他机制,例如对 HTTP:// URL 使用 TLS(所谓的“机会主义加密”);参见 RFC 8164

16. 我现在可以使用 HTTP/2 吗?

在浏览器中,Edge,Safari,Firefox 和 Chrome 的最新版本都支持 HTTP/2。其他基于 Blink 的浏览器也将支持 HTTP/2(例如 Opera 和 Yandex Browser)。有关更多详细信息,请参见这里

还有几种可用的服务器(包括 AkamaiGoogleTwitter 的主要站点提供的 beta 支持),以及许多可以部署和测试的开源实现。

有关更多详细信息,请参见实现列表

17. HTTP/2 会取代 HTTP/1.x 吗?

工作组的目的是让那些使用 HTTP/1.x 的人也可以使用 HTTP/2,并能获得 HTTP/2 所带来的好处。他们说过,由于人们部署代理和服务器的方式不同,我们不能强迫整个世界进行迁移,所以 HTTP/1.x 仍有可能要使用了一段时间。

18. 会有 HTTP/3 吗?

如果通过 HTTP/2 引入的协商机制运行良好,支持新版本的 HTTP 就会比过去更加容易。

二. 实现相关的问题

1. 为什么规则会围绕 HEADERS frame 的 Continuation?

存在连续性是因为单个值(例如 Set-Cookie)可能超过 16KiB-1,这意味着它无法放入单个帧中。决定处理该问题的最不容易出错的方法是要求所有消息头数据都以一个接一个帧的方式传递,这使得解码和缓冲区管理也变得更加容易。

2. HPACK 状态的最小或最大大小是多少?

接收方始终控制 HPACK 中使用的内存量,并且可以将其最小设置为 0,最大值与 SETTINGS 帧中的最大可表示整数(当前为 2^32-1)有关。

3. 如何避免保持 HPACK 状态?

发送一个 SETTINGS 帧,将状态尺寸(SETTINGS_HEADER_TABLE_SIZE)设置到 0,然后 RST 所有的流,直到一个带有 ACT 设置位的 SETTINGS 帧被接收。

4. 为什么只有一个压缩/流控制上下文?

简单的说一下。

最初的提议里有流分组的概念,它可以共享上下文,流量控制等。虽然这将使代理受益(以及代理用户的体验),但这样做却增加了相当多的复杂性。所以我们就决定先以一个简单的东西开始,看看它会有多糟糕的问题,并且在未来的协议版本中解决这些问题(如果有的话)。

5. 为什么 HPACK 中有 EOS 符号?

HPACK 的霍夫曼编码,出于 CPU 效率和安全性的考虑,将霍夫曼编码的字符串填充到下一个字节边界;任何特定的字符串可能需要 0-7 位之间的填充。

如果单独考虑霍夫曼解码,那么任何比所需填充长的符号都可以工作;但是,HPACK 的设计允许按字节比较霍夫曼编码的字符串。通过要求将 EOS 符号的位用于填充,我们确保用户可以对霍夫曼编码的字符串进行字节比较,以确定是否相等。反过来,这意味着许多 headers 可以在不需要霍夫曼解码的情况下被解析。

6. 是否可以在不实现 HTTP/1.1 的情况下实现 HTTP/2?

是的,大部分情况都可以。

对于 TLS(h2)上的 HTTP/2 ,如果您未实现 http1.1 ALPN 标识符,则无需支持任何 HTTP/1.1 功能。

对于基于 TCP(h2c)的 HTTP/2 ,您需要实现初始 Upgrade 升级请求。

只支持 h2c 的客户端需要生成一个针对 OPTIONS 的请求,因为 “*” 或者一个针对 “/” 的 HEAD 请求,它们相当安全且易于构造。希望仅实现 HTTP/2 的客户端将需要将没有 101 状态码的 HTTP/1.1 响应视为错误。

只支持 h2c 的服务器可以使用一个固定的 101 响应来接收一个包含升级(Upgrade)消息头字段的请求。没有 h2c 升级令牌的请求可以通过包含 Upgrade 头字段的 505(不支持 HTTP 版本)状态码拒绝。不希望处理 HTTP/1.1 响应的服务器应在发送连接序言后,应该立即用 REFUSED_STREAM 错误码拒绝 stream 1,以鼓励客户端通过 upgraded 的 HTTP/2 连接重试请求。

7. 第 5.3.2 节中的优先级示例不正确吗?

是正确的。流 B 的权重为 4,流 C 的权重为 12。要确定这些流中的每一个接收的可用资源的比例,请将所有权重相加(16),然后将每个流的权重除以总权重。因此,流 B 获得了四分之一的可用资源,流C获得了四分之三。因此,如规范所述:流 B 理想地接收分配给流 C 的资源的三分之一

8. HTTP/2 连接需要 TCP_NODELAY 么?

有可能需要。即使对于仅使用单个流下载大量数据的客户端实现,仍将有必要向相反方向发送一些数据包以实现最大传输速度。如果未设置 TCP_NODELAY(仍允许 Nagle 算法),则传出数据包可能会保留一段时间,以允许它们与后续数据包合并。

例如,如果这样一个数据包告诉对等端有更多可用的窗口来发送数据,那么将其发送延迟数毫秒(或更长时间)会对高速连接造成严重影响。

三. 部署问题

1. 如果 HTTP/2 是加密的,我该如何调试?

有很多方法可以访问应用程序数据,但最简单的方法是 NSS keylogging 与 Wireshark 插件(包含在最新开发版本中)结合使用。这个方法对 Firefox 和 Chrome 均可适用。

2. 如何使用 HTTP/2 服务器推送

HTTP/2 服务器推送允许服务器无需等待请求即可向客户端提供内容。这可以改善检索资源的时间,特别是对于具有大带宽延迟产品的连接,其中网络往返时间占了在资源上花费的大部分时间。

推送基于请求内容而变化的资源可能是不明智的。目前,浏览器只会推送请求,如果他们不这样做,就会提出匹配的请求(请参阅 RFC 7234 的第 4 节)。

某些缓存不考虑所有请求头字段中的变化,即使它们在 Vary 头字段中。为了使推送资源被接收的可能性最大化,内容协商是最好的选择。基于 accept-encoding 报头字段的内容协商受到缓存的广泛尊重,但是可能无法很好地支持其他头字段。


Reference:

HTTP/2 Frequently Asked Questions

GitHub Repo:Halfrost-Field

Follow: halfrost · GitHub

Source: GHOST_URL/http2-frequently-asked-questions/

❌
❌