记录一下HTTP/2的底层原理,帮助理解协议实现细节。
连接
每个端点都需要发送一个连接作为最终确认使用的协议,并建立http/2连接的初始设置。客户端和服务器各自发送不同的连接前导(preface)。
客户端的连接前导以24位的序列开头,下面是16进制表示:
0x505249202a20485454502f322e300d0a0d0a534d0d0a0d0a
也就是下面字符串:
这个序列后面必须跟着 SETTINGS 帧,其有可能为空。
服务端的连接前导由一个可能为空的 SETTINGS 帧组成,它在 HTTP/2 的连接中必须是第一个帧。
一旦连接前导交换过之后,连接就认为已经建立。端点可利用它进行通讯。
帧
建立连接之后,就可以交换帧。所有帧的格式如下:
前面9个字节是固定的,代表整个帧的大小。
下面解释一下各个字段的含义:
名称 | 长度 | 描述 |
---|---|---|
Length | 3字节 | 帧负载的长度 |
Type | 1字节 | 当前帧类型 |
Flags | 1字节 | 具体帧类型的标识 |
R | 1位 | 保留位,不要设置 |
Stream Identifier | 31位 | 每个流的唯一ID |
Frame Payload | 长度可变 | 真实的帧内容 |
h2中有10种不同类型的帧,如下表:
名称 | ID | 描述 |
---|---|---|
DATA | 0x0 | 传输流的核心内容 |
HEADERS | 0x1 | 包含HTTP首部和可选的优先级参数 |
PRIORITY | 0x2 | 指示或更改流的优先级和依赖 |
RST_STREAM | 0x3 | 允许一端停止流(通常由于错误导致) |
SETTINGS | 0x4 | 协商连接级参数 |
PUSH_PROMISE | 0x5 | 提示客户端,服务器要推送些东西 |
PING | 0x6 | 测试连接可用性和往来时延(RTT) |
GOWAY | 0x7 | 告诉另一端,当前端已结束 |
WINDOW_UPDATE | 0x8 | 协商一端将要接受多少字节(用户流量控制) |
CONTINUATION | 0x9 | 用以扩展HEADER数据块 |
流
“流”是在http/2连接中客户端和服务端之间交换的一个独立的、双向的帧序列。流包含很多重要的特性:
消息
HTTP消息泛指HTTP请求或响应。流是用来传输一对请求/响应消息的。一个消息至少由 HEADERS 帧组成,并且可以另外包含 CONTINUATION 和 DATA帧,以及其他的 HEADERS 帧。
下面是普通GET请求:
下面展示POST请求:
流量控制
不同h1,h2提供客户端调整传输速度的能力,服务器也可以控制。 WINDOW_UPDATE 帧用来指示流量控制信息。客户端需要流量控制的理由:
目前为止流量控制没有提供开发控制。
优先级
h2使用流的依赖关系来解决服务器同时收到很多请求不知道如何处理的问题。客户端明确地和服务端沟通需要的资源以及它们的顺序。通过声明依赖关系树和树里的相对权重:
服务端推送
提升单个对象性能的最佳方式,就是在它被用到之前就放到浏览器的缓存里。服务端推送同时伴随着一些安全问题。
推送对象
若服务器决定推送一个对象,会构造一个PUSH_PROMISE帧:
客户端设置的流从1开始,使用奇数,而服务端开启的流使用偶数,从2开始。这种设计避免了客户端和服务器之间流ID冲突,也可以轻松判断哪些对象是由服务端推送的。0是保留数字,用于连接级控制消息,不能用于创建新的流。
客户端使用RST_STREAM或PROTOCOL_ERROR(专门留给PUSH_PROMISE涉及的协议层面问题)来拒收。值得注意的是,服务器可以在PUSH_PROMISE发送后立即启动推送流,因此拒收推送仍然无法避免推送大量资源,所以推送正确的资源时不够的,还需要只推送正确的资源。
PUSH_PROMISE 中指明所属流的ID:
首部压缩
现在网页平均包含140个请求,这些请求之间通常几乎没有新的或不同的内容,造成很大浪费,急需压缩方法。经过思考和讨论提出了HPACK,它是一种表查找压缩方案,利用霍夫曼编码获得接近GZIP的压缩率,同时能抵御CRIME。
如上两个请求,只有红框中的不同其余都是重复的。HPACK是为减少传输相同部分而设计出来的。简化原理如下:
假设客户端发送如下请求首部:
Header1:foo
Header2:bar
Header3:bat
同时客户端会创建一张表:
索引 | 首部名称 | 值 |
---|---|---|
62 | Header1 | foo |
63 | Header2 | bar |
64 | Header3 | bat |
服务端读取到请求首部,照样会创建一张表。客户端发送下一个请求时,若首部相同,可直接发送如下首部块:
62 63 64
服务器会查找先前的表格,把数字还原成索引对应的完整首部。
HPCK实现比上面的复杂得多,提供如下线索更深理解: