HTTP/2
Homepage: https://http2.github.io/
官方文档:
RFC7540,HTTP2
RFC7541,HPACK - HTTP/2 的头压缩
服务器推送,server push
故名思意,服务器主动将资源发送到浏览器端,称为推送!
我们以 nginx 的实现为例,来了解一下服务器推送!
http2_push 推送指令
这个推送不是任意资源都推送,必须是被依赖的资源才可以推送,也需满足同源策略。例如:
1<!-- index.html -->
2<head>
3 <link rel="stylesheet" href="dist/css/adminlte.min.css">
4</head>
5<body>
6 <script src="dist/js/adminlte.js"></script>
7</body>
此时 nginx 配置,指令 http2_push
用于设置推送资源:
1location / {
2 root /usr/share/nginx/html;
3 index index.html index.htm;
4 http2_push /dist/css/adminlte.min.css;
5 http2_push /dist/js/adminlte.js;
6 }
http2_push 需要使用带有绝对目录的相对地址。说白了,就是以 / 开头,表示从 root 目录开始。
当浏览器针对该服务器请求 Index.html 资源时,就会将 adminlte.min.css
和 adminlte.js
推送到浏览器端,如图:

这个流程的思路如图所示:

自动推送 http2_push_preload
http2_push
指令推送时,需要配置大量的静态资源依赖关系。这个操作在很多情况下会不方便,或者受到限制。缘于此,Nginx 也支持基于响应头中的 Link preload 字段完成自动推送。响应头格式为:
1Link: </css/styles.css>; rel=preload; as=style
2Link: </css/styles.css>; rel=preload; as=style, </js/scripts.js>; rel=preload; as=script
3
自动推送使用配置 http2_push_preload on|off
:
1location / {
2 root /usr/share/nginx/html;
3 index index.html index.htm;
4 proxy_pass http://upstream;
5 http2_push_preload on;
6}
nginx 服务器虽开启了自动推送,但 nginx 不能确定哪些资源要自动推送。这个需要后端程序来完成,nginx 通过 upstream
指令与后端程序交互,例如 Go, PHP 等:
PHP:
1header("Link: </dist/css/adminlte.min.css>; rel=preload; as=style");
2header("Link: </dist/js/adminlte.js>; rel=preload; as=script");
Go
1mux := http.NewServeMux()
2mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
3 w.Header().Add("Link", "</dist/css/adminlte.min.css>; rel=preload; as=style");
4 w.Header().Add("Link", "</dist/js/adminlte.js>; rel=preload; as=script");
5 })
如果服务器或者浏览器不支持 HTTP/2,那么浏览器就会按照 preload 来处理这个头信息,预加载指定的资源文件。
选择性推送
服务器推送一个小 bug 是,服务器不确定浏览器是否真正需要该资源。例如,浏览器已经缓存了 /style.css
,那么就不需要推该资源了。一个示例,在 cookie 中有 session=1
时,不推送,没有该字段时推送,示例来自 https://www.nginx.com/blog/nginx-1-13-9-http2-server-push/:
1server {
2 http2_push_preload on;
3
4 location = /demo.html {
5 add_header Set-Cookie "session=1";
6 add_header Link $resources;
7 }
8}
9
10map $http_cookie $resources {
11 "~*session=1" "";
12 default "</style.css>; as=style; rel=preload, </image1.jpg>; as=image; rel=preload, </image2.jpg>; as=image; rel=preload";
13}
该逻辑通常在后端应用处理。
数据帧,Frame
HTTP/2 数据传输最小的单位是帧,Frame,所有的数据包括,head、body 都会打包到 Frame 发送。
Frame 是在数据传输流中的每个数据切片,为了标识 Frame 属于哪个流 Stream,Frame 结构会有 Stream ID 字段标识所属的数据流。
基于传输内容不同,Frame 分为很多类型,例如 Header、Data、PRIORITY 等。
帧格式,format
帧由 Header 和 Payload 组成,Header 长度固定,9 Bytes,72 bits。
格式如下:
1 +-----------------------------------------------+
2 | Length (24) |
3 +---------------+---------------+---------------+
4 | Type (8) | Flags (8) |
5 +-+-------------+---------------+-------------------------------+
6 |R| Stream Identifier (31) |
7 +=+=============================================================+
8 | Frame Payload (0...) ...
9 +---------------------------------------------------------------+
10
字段说明如下:
Length,荷载数据长度,24 位无符号整型可表示最大数 16,384(2^14),意味着 PayLoad 数据最大长度为 16,384。
Type,帧类型
Flags,标志位
R,保留位
Stream Identifier,流 ID,标识 Frame 所属的流
Frame Payload,荷载数据
帧类型,type
| Frame Type | Code | |
|---|---|---|
| DATA | 0x0 | 数据帧,请求或响应主体帧 |
| HEADERS | 0x1 | 头帧,请求或响应头 |
| PRIORITY | 0x2 | 优先级帧,发送方对流优先级权重的建议值 |
| RST_STREAM | 0x3 | 强制停止帧,关闭对应流,接收者不能够在此流上发送任何帧 |
| SETTINGS | 0x4 | 设置帧,接收者向发送者通告己方设定,服务器端在连接成功后必须第一个发送的帧 |
| PUSH_PROMISE | 0x5 | 推送准备帧,服务器端通知对端准备推送数据 |
| PING | 0x6 | Ping 帧,测试响应时间,心跳机制用于检测空闲连接是否有效 |
| GOAWAY | 0x7 | 优雅停止帧,通知对方关闭流,但需要完成未完成的任务 |
| WINDOW_UPDATE | 0x8 | 窗口升级帧,用于流量控制 |
| CONTINUATION | 0x9 | 延续帧,一个HEADERS/PUSH_PROMISE 帧后面会跟随零个或多个 CONTINUATION,只要上一个帧没有设置END_HEADERS 标志位 |
更详细的信息请参考:https://datatracker.ietf.org/doc/html/rfc7540#section-6
列举几个类型的帧载荷结构
DATA 类型的 PayLoad:
1+---------------+
2|Pad Length? (8)|
3+---------------+-----------------------------------------------+
4| Data (*) ...
5+---------------------------------------------------------------+
6| Padding (*) ...
7+---------------------------------------------------------------+
字段说明:
Pad Length,填充长度
Data,应用数据
Padding,填充
HEADER 类型的 PayLoad:
1+---------------+
2|Pad Length? (8)|
3+-+-------------+-----------------------------------------------+
4|E| Stream Dependency? (31) |
5+-+-------------+-----------------------------------------------+
6| Weight? (8) |
7+-+-------------+-----------------------------------------------+
8| Header Block Fragment (*) ...
9+---------------------------------------------------------------+
10| Padding (*) ...
11+---------------------------------------------------------------+
字段说明:
Pad Length,填充长度
E,是否依赖专用位
Stream Dependency,流依赖
Weight,优先级权重
Header Block Fragment,头块分段
Padding,填充
SETTING 类型的 PayLoad:
1+-------------------------------+
2| Identifier (16) |
3+-------------------------------+-------------------------------+
4| Value (32) |
5+---------------------------------------------------------------+
字段说明:
Identifier,配置项标识
Value,配置项值
标志位,flag
下表展示了 Frame 类型和标志可能的组合关系,引用自 https://metacpan.org/release/CRUX/Protocol-HTTP2-0.14/view/lib/Protocol/HTTP2/Frame.pm:
1Table represent possible combination of frame types and flags.
2Last column -- Stream ID of frame types (x -- sid >= 1, 0 -- sid = 0)
3
4
5 +-END_STREAM 0x1
6 | +-ACK 0x1
7 | | +-END_HEADERS 0x4
8 | | | +-PADDED 0x8
9 | | | | +-PRIORITY 0x20
10 | | | | | +-stream id (value)
11 | | | | | |
12| frame type\flag | V | V | V | V | V | | V |
13| --------------- |:-:|:-:|:-:|:-:|:-:| - |:---:|
14| DATA | x | | | x | | | x |
15| HEADERS | x | | x | x | x | | x |
16| PRIORITY | | | | | | | x |
17| RST_STREAM | | | | | | | x |
18| SETTINGS | | x | | | | | 0 |
19| PUSH_PROMISE | | | x | x | | | x |
20| PING | | x | | | | | 0 |
21| GOAWAY | | | | | | | 0 |
22| WINDOW_UPDATE | | | | | | | 0/x |
23| CONTINUATION | | | x | x | | | x |
几个标志位含义为:
END_STREAM 0x1,结束流
ACK 0x1,确认
END_HEADERS 0x4,结束头
PADDED 0x8,填充
PRIORITY 0x20,优先




