// HTTP1.1
GET /resource HTTP/1.1
Host: example.org
Accept: image/jpeg
// HTTP2
HEADERS
+ END_STREAM
+ END_HEADERS
:method = GET
:scheme = https
:path = /resource
host = example.org
accept = image/jpeg
// HTTP1.1
POST /resource HTTP/1.1
Host: example.org
Content-Type: image/jpeg
Content-Length: 123
{binary data}
// HTTP2
HEADERS
:method = POST
:path = /resource
:scheme = https
CONTINUATION
END_HEADERS
content-type = image/jpeg
host = example.org
content-length = 123
DATA
END_STREAM
{binary data}
<---------- Index Address Space ----------> <-- Static Table --> <-- Dynamic Table --> +---+-----------+---+ +---+-----------+---+ | 1 | ... | s | |s+1| ... |s+k| +---+-----------+---+ +---+-----------+---+
例如 :method: GET,可以直接使用一个字符表示; index 2 :method GET 小知识点:HTTP/1 的状态行信息(Method、Path、Status 等), 在 HTTP/2 中被拆成键值对放入头部(冒号开头的那些),同样可以享受到字典和哈夫曼压缩。 举个🌰 index 4 :path /
0 1 2 3 4 5 6 7 +---+---+---+---+---+---+---+---+ | 1 | Index (7+) | +---+---------------------------+这是最简单的情况,使用一个字节就可以表示这个头部了,最左一位固定为 1,之后七位存放键值对在静态或动态字典中的索引。 例如下图中,头部索引值为 2(0000010),在静态字典中查询可得 :method: GET。
例如 cookie: xxxxxxx,可以将名称使用一个字符表示。 index 32 cookie 你可能会问key压缩了,value呢?这就是后面的 Dynamic Table 和 Huffman Coding 要做的了。 同时,浏览器可以告知服务端,将 cookie: xxxxxxx 添加到动态字典中, 这样后续整个键值对就可以使用一个字符表示了。 类似的,服务端也可以更新对方的动态字典。 需要注意的是,动态字典上下文有关,需要为每个 HTTP/2 连接维护不同的字典。
static ngx_http_v2_header_t ngx_http_v2_static_table[] = {
{ ngx_string(":authority"), ngx_string("") },
{ ngx_string(":method"), ngx_string("GET") },
{ ngx_string(":method"), ngx_string("POST") },
{ ngx_string(":path"), ngx_string("/") },
{ ngx_string(":path"), ngx_string("/index.html") },
{ ngx_string(":scheme"), ngx_string("http") },
{ ngx_string(":scheme"), ngx_string("https") },
{ ngx_string(":status"), ngx_string("200") },
{ ngx_string(":status"), ngx_string("204") },
{ ngx_string(":status"), ngx_string("206") },
{ ngx_string(":status"), ngx_string("304") },
{ ngx_string(":status"), ngx_string("400") },
{ ngx_string(":status"), ngx_string("404") },
{ ngx_string(":status"), ngx_string("500") },
{ ngx_string("accept-charset"), ngx_string("") },
{ ngx_string("accept-encoding"), ngx_string("gzip, deflate") },
{ ngx_string("accept-language"), ngx_string("") },
{ ngx_string("accept-ranges"), ngx_string("") },
{ ngx_string("accept"), ngx_string("") },
{ ngx_string("access-control-allow-origin"), ngx_string("") },
{ ngx_string("age"), ngx_string("") },
{ ngx_string("allow"), ngx_string("") },
{ ngx_string("authorization"), ngx_string("") },
{ ngx_string("cache-control"), ngx_string("") },
{ ngx_string("content-disposition"), ngx_string("") },
{ ngx_string("content-encoding"), ngx_string("") },
{ ngx_string("content-language"), ngx_string("") },
{ ngx_string("content-length"), ngx_string("") },
{ ngx_string("content-location"), ngx_string("") },
{ ngx_string("content-range"), ngx_string("") },
{ ngx_string("content-type"), ngx_string("") },
{ ngx_string("cookie"), ngx_string("") },
{ ngx_string("date"), ngx_string("") },
{ ngx_string("etag"), ngx_string("") },
{ ngx_string("expect"), ngx_string("") },
{ ngx_string("expires"), ngx_string("") },
{ ngx_string("from"), ngx_string("") },
{ ngx_string("host"), ngx_string("") },
{ ngx_string("if-match"), ngx_string("") },
{ ngx_string("if-modified-since"), ngx_string("") },
{ ngx_string("if-none-match"), ngx_string("") },
{ ngx_string("if-range"), ngx_string("") },
{ ngx_string("if-unmodified-since"), ngx_string("") },
{ ngx_string("last-modified"), ngx_string("") },
{ ngx_string("link"), ngx_string("") },
{ ngx_string("location"), ngx_string("") },
{ ngx_string("max-forwards"), ngx_string("") },
{ ngx_string("proxy-authenticate"), ngx_string("") },
{ ngx_string("proxy-authorization"), ngx_string("") },
{ ngx_string("range"), ngx_string("") },
{ ngx_string("referer"), ngx_string("") },
{ ngx_string("refresh"), ngx_string("") },
{ ngx_string("retry-after"), ngx_string("") },
{ ngx_string("server"), ngx_string("") },
{ ngx_string("set-cookie"), ngx_string("") },
{ ngx_string("strict-transport-security"), ngx_string("") },
{ ngx_string("transfer-encoding"), ngx_string("") },
{ ngx_string("user-agent"), ngx_string("") },
{ ngx_string("vary"), ngx_string("") },
{ ngx_string("via"), ngx_string("") },
{ ngx_string("www-authenticate"), ngx_string("") },
};
0 1 2 3 4 5 6 7 +---+---+---+---+---+---+---+---+ | 0 | 1 | Index (6+) | +---+---+-----------------------+ | H | Value Length (7+) | +---+---------------------------+ | Value String (Length octets) | +-------------------------------+
对于这种情况,首先需要使用一个字节表示头部名称: 左两位固定为 01,之后六位存放头部名称在静态或动态字典中的索引。 接下来的一个字节第一位 H 表示头部值是否使用了哈夫曼编码, 剩余七位表示头部值的长度 L,后续 L 个字节就是头部值的具体内容了。 例如索引值为 32(100000),在静态字典中查询可得 cookie;头部值使用了哈夫曼编码(1),长度是 28(0011100); 接下来的 28 个字节是 cookie 的值,将其进行哈夫曼解码就能得到具体内容。
0 1 2 3 4 5 6 7 +---+---+---+---+---+---+---+---+ | 0 | 1 | 0 | +---+---+-----------------------+ | H | Name Length (7+) | +---+---------------------------+ | Name String (Length octets) | +---+---------------------------+ | H | Value Length (7+) | +---+---------------------------+ | Value String (Length octets) | +-------------------------------+ 这种情况与第 2 种情况类似,只是由于头部名称不在字典中,所以第一个字节固定为 01000000; 接着申明名称是否使用哈夫曼编码及长度,并放上名称的具体内容; 再申明值是否使用哈夫曼编码及长度,最后放上值的具体内容。 例如下图中名称的长度是 5(0000101),值的长度是 6(0000110)。 对其具体内容进行哈夫曼解码后,可得 pragma: no-cache。 客户端或服务端看到这种格式的头部键值对,会将其添加到自己的动态字典中。 后续传输这样的内容,就符合第 1 种情况了。
支持基于静态哈夫曼码表的哈夫曼编码(Huffman Coding) 使用字典可以极大地提升压缩效果,其中静态字典在首次请求中就可以使用。 对于静态、动态字典中不存在的内容,还可以使用哈夫曼编码来减小体积。 HTTP/2 使用了一份静态哈夫曼码表,也需要内置在客户端和服务端之中。 这个哈夫曼代码是根据大量HTTP头文件获得的统计信息生成的。
举个🌰吧
如果服务端收到了一个对文档的请求,该文档包含内嵌的指向多个图片文件的链接,
且服务端选择向客户端推送那些额外的图片,
那么在发送包含图片链接的 DATA 帧之前发送 PUSH_PROMISE 帧可以确保客户端在发现内嵌的链接之前,
能够知道有一个资源将要被推送过来。