HTTP 2
http2.0 是一种安全高效的下一代 http 传输协议。安全是因为 http2.0 建立在 https 协议的基础上,高效是因为它是通过二进制分帧来进行数据传输。
HTTP 1.x 问题
在 http 1.0 中,每此消息传输(请求/响应)都需要建立一次 TCP 链接。为了减少 TCP 链接的时间消耗,http 1.1 中加入了 connection: keep-alive,来持久化 TCP 链接,进而使一个 TCP 链接可以重复使用。
但是 TCP 链接中的 HTTP 请求只能串行执行,一个 TCP 链接同一时间内只能处理一个 HTTP 请求。为了解决串行的阻塞问题,浏览器(chrome)会为同一个 host 建立多个(6 个)TCP 链接,如果 6 个 TCP 链接依然不够用,那就要在不同的 host 中分布资源,以尽可能多的并行请求资源。
HTTP 2 优化内容
二进制分帧(Binary Format)
突破 http1.X 标准的性能限制,改进传输性能,实现低延迟和高吞吐量
帧 (frame) 包含部分:类型 Type, 长度 Length, 标记 Flags, 流标识 Stream 和 frame payload 有效载荷。
消息 (message):一个完整的请求或者响应,比如请求、响应等,由一个或多个 Frame 组成。
流是连接中的一个虚拟信道,可以承载双向消息传输。每个流有唯一整数标识符。为了防止两端流 ID 冲突,客户端发起的流具有奇数 ID,服务器端发起的流具有偶数 ID。
流标识是描述二进制 frame 的格式,使得每个 frame 能够基于 http2 发送,与流标识联系的是一个流,每个流是一个逻辑联系,一个独立的双向的 frame 存在于客户端和服务器端之间的 http2 连接中。一个 http2 连接上可包含多个并发打开的流,这个并发流的数量能够由客户端设置。
多路复用 (Multiplexing) / 连接共享
多路复用允许同时通过单一的 http/2 连接发起多重的请求 - 响应消息。有了新的分帧机制后,http/2 不再依赖多个 TCP 连接去实现多流并行了。每个数据流都拆分成很多互不依赖的帧,而这些帧可以交错(乱序发送),还可以分优先级,最后再在另一端把它们重新组合起来。
http 2.0 连接都是持久化的,而且客户端与服务器之间也只需要一个连接(每个域名一个连接)即可。http2 连接可以承载数十或数百个流的复用,多路复用意味着来自很多流的数据包能够混合在一起通过同样连接传输。当到达终点时,再根据不同帧首部的流标识符重新连接将不同的数据流进行组装。
头部压缩(Header Compression)
http/2 使用 encoder 来减少需要传输的 header 大小,通讯双方各自缓存一份头部字段表,对于相同的数据,通信期间只需发送一次,如果首部发生了变化,则只需将变化的部分加入到 header 帧中,改变的部分会加入到头部字段表中,首部表在 http 2.0 的连接存续期内始终存在,由客户端和服务器共同渐进地更新。
压缩原理
用 header 字段表里的索引代替实际的 header。
http/2 的 HPACK 算法使用一份索引表来定义常用的 http Header,把常用的 http Header 存放在表里,请求的时候便只需要发送在表里的索引位置即可。
例如 :method=GET 使用索引值 2 表示,:path=/index.html 使用索引值 5 表示
只要给服务端发送一个 Frame,该 Frame 的 Payload 部分存储 0x8285,Frame 的 Type 设置为 Header 类型,便可表示这个 Frame 属于 http Header,请求的内容是:
GET /index.html为什么是 0x8285,而不是 0x0205?这是因为高位设置为 1 表示这个字节是一个完全索引值(key 和 value 都在索引中)。
类似的,通过高位的标志位可以区分出这个字节是属于一个完全索引值,还是仅索引了 key,还是 key 和 value 都没有索引 (参见:HTTP/2 首部压缩的 OkHttp3 实现 ④)。
因为索引表的大小的是有限的,它仅保存了一些常用的 http Header,同时每次请求还可以在表的末尾动态追加新的 http Header 缓存,动态部分称之为 Dynamic Table。Static Table 和 Dynamic Table 在一起组合成了索引表
HPACK 不仅仅通过索引键值对来降低数据量,同时还会将字符串进行霍夫曼编码来压缩字符串大小。
以常用的 User-Agent 为例,它在静态表中的索引值是 58,它的值是不存在表中的,因为它的值是多变的。第一次请求的时候它的 key 用 58 表示,表示这是一个 User-Agent,它的值部分会进行霍夫曼编码(如果编码后的字符串变更长了,则不采用霍夫曼编码)。
服务端收到请求后,会将这个 User-Agent 添加到 Dynamic Table 缓存起来,分配一个新的索引值。客户端下一次请求时,假设上次请求 User-Agent 的在表中的索引位置是 62,此时只需要发送 0xBE(同样的,高位置 1),便可以代表:User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.146 Safari/537.36。
最终,相同的 Header 只需要发送索引值,新的 Header 会重新加入 Dynamic Table。
请求优先级(Request Priorities)
通过流的形式进行数据传输时,每个流都可以携带 31 比特的优先值:0 表示最高优先级;2 的 31 次方 -1 表示最低优先级。
服务器可以根据流的优先级,控制资源分配(CPU、内存、带宽),但这不是绝对的,也会根据实际的情况进行处理,否则又会出现阻塞的问题:高优先级请求慢阻塞低优先级资源。
- 优先级最高:主要的 html
- 优先级高:CSS 文件
- 优先级中:js 文件
- 优先级低:图片
服务端推送(Server Push)
服务器可以对一个客户端请求发送多个响应,服务器向客户端推送资源无需客户端明确地请求。