Blog
Blog copied to clipboard
HTTP2 协议
概述
- 1989年,HTTP0.9面世;
- 1996年,HTTP1.0被推出;
- 由于早期的HTTP每次交互都要建立一次TCP连接,所以1999年发布了HTTP1.1,用于支持长连接;
- 长连接虽然优化了TCP建立连接的次数,但是同一个TCP连接上的资源还是会排队来加载,于是15年后,起草对HTTP/2的预期。
可以说,HTTP/2任重而道远,虽然现在还没有普及开来,但是HTTP/2是兼容HTTP1的语义的。
需要注意的是,HTTP/2 仍是对之前 HTTP 标准的扩展,而非替代。 HTTP 的应用语义不变,提供的功能不变,HTTP 方法、状态代码、URI 和标头字段等这些核心概念也不变。
HTTP/2的目标
与 HTTP/1.1 相比,HTTP/2 的主要变化在于性能提升。
HTTP/1.x 客户端需要使用多个连接才能实现并发和缩短延迟;HTTP/1.x 不会压缩请求和响应标头,从而导致不必要的网络流量;HTTP/1.x 不支持有效的资源优先级,致使底层 TCP 连接的利用率低下;等等。
这些限制并不是致命的,但是随着网络应用的范围、复杂性以及在我们日常生活中的重要性不断增大,它们对网络开发者和用户都造成了巨大负担,而这正是 HTTP/2 要致力于解决的:
HTTP/2 通过支持标头字段压缩和在同一连接上进行多个并发交换,让应用更有效地利用网络资源,减少感知的延迟时间。具体来说,它可以对同一连接上的请求和响应消息进行交错发送并为 HTTP 标头字段使用有效编码。 HTTP/2 还允许为请求设置优先级,让更重要的请求更快速地完成,从而进一步提升性能。出台的协议对网络更加友好,因为与 HTTP/1.x 相比,可以使用更少的 TCP 连接。
通信过程
当客户端发起请求的时候,服务器会通过ALPN来协商最终使用HTTP2还是HTTP1来通信。这有利于当服务器或者客户端有一方不支持HTTP2时回退到HTTP1。
ALPN允许应用层协商应该在安全连接上实行哪个协议,以避免额外且独立于应用层协议的往返协商通信。ALPN 是客户端发送该列表,由服务端选择。
之后双方在建立的TCP上进行通信,是全双工传输方式,而且请求不会被阻塞排队。
特征细说
二进制分帧
HTTP/2 所有性能增强的核心在于新的二进制分帧层,它定义了如何封装 HTTP 消息并在客户端与服务器之间传输。
HTTP/1.x 协议以换行符作为纯文本的分隔符,而 HTTP/2 将所有传输的信息分割为更小的消息和帧,并采用二进制格式对它们编码。
HTTP 的语义(包括各种动词、方法、标头)都不受影响,不同的是传输期间对它们的编码方式变了。
HTTP/2帧结构
所有的帧都以一个固定的9字节首部开始,其后紧跟一个可变长度的载荷。
- Length,3字节,表示帧负载的长度
- Type,1字节,表示当前帧的类型
- Flags,1字节,表示具体帧类型的标识
- R,1位,保留位
- Stream Identifier,31位,每个流的唯一ID
- Frame Payload,长度可变,真实的帧内容,长度是在Length字段中设置的
帧类型:
类型 | ID | 描述 |
---|---|---|
DATA | 0x0 | 传输流的核心内容 |
HEADERS | 0x1 | 包含HTTP手部,和可选的优先级参数 |
PRIORITY | 0x2 | 指示或者更改流的优先级和依赖 |
RST_STREAM | 0x3 | 允许一端停止流(通常由于错误导致的) |
SETTINGS | 0x4 | 协商连接级参数 |
PUSH_PROMISE | 0x5 | 提示客户端,服务器要推送些东西 |
PING | 0x6 | 测试连接可用性和往返时延(RTT) |
GOAWAY | 0x7 | 告诉另一端,当前端已结束 |
WINDOW_UPDATE | 0x8 | 协商一端将要接收多少字节 |
CONTINUATION | 0x9 | 用以扩展HEADER数据块 |
HTTP/2二进制分帧机制
三个相关的概念:
- 流:已建立的连接内的双向字节流,用来传输一对请求/响应消息。
- 消息:与逻辑请求或响应消息对应的完整的一系列帧。
- 帧:HTTP/2 通信的最小单位,每个帧都包含帧头,至少也会标识出当前帧所属的数据流。
它们的关系:
- 所有通信都在一个 TCP 连接上完成,此连接可以承载任意数量的双向数据流。
- 每个数据流都有一个唯一的标识符和可选的优先级信息,用于承载双向消息。
- 每条消息都是一条逻辑 HTTP 消息(例如请求或响应),包含一个或多个帧。
- 帧是最小的通信单位,承载着特定类型的数据,例如 HTTP 标头、消息负载,等等。 来自不同数据流的帧可以交错发送,然后再根据每个帧头的数据流标识符重新组装。
请求与响应复用
在 HTTP/1.x 中,如果客户端要想发起多个并行请求以提升性能,则必须使用多个 TCP 连接。
HTTP/2 中新的二进制分帧层突破了这些限制,实现了完整的请求和响应复用:客户端和服务器可以将 HTTP 消息分解为互不依赖的帧,然后交错发送,最后再在另一端把它们重新组装起来。
拓展一下,联级文件(如Webpack打包模块),image sprites(雪碧图),域名分片等等都是为了绕过 HTTP/1.x 限制而做的工作。
所以HTTP/2 中的新二进制分帧层解决了 HTTP/1.x 中存在的队首阻塞问题,也消除了并行处理和发送请求及响应时对多个连接的依赖。结果,应用速度更快、开发更简单、部署成本更低。
数据流优先级
TTP/2 标准允许每个数据流都有一个关联的权重和依赖关系:
- 可以向每个数据流分配一个介于 1 至 256 之间的整数表明权重
- 每个数据流与其他数据流之间可以存在显式依赖关系
流控制
流控制是一种阻止发送方向接收方发送大量数据的机制,以免超出后者的需求或处理能力:发送方可能非常繁忙、处于较高的负载之下,也可能仅仅希望为特定数据流分配固定量的资源。
HTTP/2 提供了一组简单的构建块,这些构建块允许客户端和服务器实现其自己的数据流和连接级流控制:
- 流控制具有方向性。每个接收方都可以根据自身需要选择为每个数据流和整个连接设置任意的窗口大小。
- 流控制基于信用。每个接收方都可以公布其初始连接和数据流流控制窗口(以字节为单位),每当发送方发出 DATA 帧时都会减小,在接收方发出 WINDOW_UPDATE 帧时增大。
- 流控制无法停用。建立 HTTP/2 连接后,客户端将与服务器交换 SETTINGS 帧,这会在两个方向上设置流控制窗口。流控制窗口的默认值设为 65,535 字节,但是接收方可以设置一个较大的最大窗口大小(2^31-1 字节),并在接收到任意数据时通过发送 WINDOW_UPDATE 帧来维持这一大小。
- 流控制为逐跃点控制,而非端到端控制。即,可信中介可以使用它来控制资源使用,以及基于自身条件和启发式算法实现资源分配机制。
扩展一下,如果这个功能足够强大的话是可以取代现在的懒加载模式的。
压缩头字段
每个 HTTP 传输都承载一组标头,这些标头说明了传输的资源及其属性。 在 HTTP/1.x 中,此元数据始终以纯文本形式,通常会给每个传输增加 500–800 字节的开销。如果使用 HTTP Cookie,增加的开销有时会达到上千字节。
为了减少此开销和提升性能,HTTP/2 使用 HPACK 压缩格式压缩请求和响应标头元数据,这种格式采用两种简单但是强大的技术:
- 这种格式支持通过静态 Huffman 代码对传输的标头字段进行编码,从而减小了各个传输的大小。
- 这种格式要求客户端和服务器同时维护和更新一个包含之前见过的标头字段的索引列表(换句话说,它可以建立一个共享的压缩上下文),此列表随后会用作参考,对之前传输的值进行有效编码。
zlib 压缩算法被 HPACK 替代,后者经过专门设计,可以解决发现的安全问题、实现起来也更高效和简单,当然,可以对 HTTP 标头元数据进行良好压缩。
HPACK 压缩上下文包含一个静态表和一个动态表:静态表在规范中定义,并提供了一个包含所有连接都可能使用的常用 HTTP 标头字段(例如,有效标头名称)的列表;动态表最初为空,将根据在特定连接内交换的值进行更新。因此,为之前未见过的值采用静态 Huffman 编码,并替换每一侧静态表或动态表中已存在值的索引,可以减小每个请求的大小。
服务器推送
HTTP/2 新增的另一个强大的新功能是,服务器可以对一个客户端请求发送多个响应。 换句话说,除了对最初请求的响应外,服务器还可以向客户端推送额外资源,而无需客户端明确地请求。
推送资源可以进行以下处理:
- 由客户端缓存
- 在不同页面之间重用
- 与其他资源一起复用
- 由服务器设定优先级
- 被客户端拒绝(如果资源已经位于缓存中,可能会发生这种情况)
从开发者的视角
如果是Web前端开发者的话,其实还算友好,因为浏览器几乎都支持了HTTP/2,我们只需要像往常一样调用Web API,浏览器会通过ALPN和服务器协商使用HTTP/2还是HTTP/1。
Android和iOS可以通过相关的库支持HTTP/2。
对于后端开发者来说,工作量就相对有点大了。因为HTTP/2改变了传输方式,所以几乎所有编程语言会提供新的API,也就意味着需要重构项目。我们可以看看NodeJS是怎么构建一个HTTP/2服务器的:
1. 创建RSA
因为HTTP/2是依赖TLS/SSL,所以我们需要创建RSA,下面的指令是Unix环境下的,win10系统可以通过Microsoft Store下载Ubuntu 18来执行:
openssl req -x509 -newkey rsa:2048 -nodes -sha256 -subj '/CN=localhost' \
-keyout localhost-privkey.pem -out localhost-cert.pem
win10系统可以通过下面的路径找到生成的两个文件:
C:\Users\{user_name}\AppData\Local\Packages\CanonicalGroupLimited.Ubuntu18.04onWindows_79rhkp1fndgsc\LocalState\rootfs\home\{user_name}
2. 构建HTTP/2服务器
创建HTTP/2服务器的时候需要传入第一步创建的RSA。
还有HTTP/2服务器不再是监听data事件,而是stream事件。
const http2 = require('http2');
const fs = require('fs');
const server = http2.createSecureServer({
key: fs.readFileSync('localhost-privkey.pem'),
cert: fs.readFileSync('localhost-cert.pem')
});
server.on('error', (err) => console.error(err));
server.on('stream', (stream, headers) => {
// stream is a Duplex
stream.respond({
'content-type': 'text/html',
':status': 200
});
stream.end('<h1>Hello World</h1>');
});
server.listen(8443);
如果HTTP/2服务器不是基于TLS/SSL,那么浏览器将会访问不到,这应该是某种保护机制,不过现在还暂时不知道是为了什么
参考
HTTP/2 简介 HTTP/2 幕后原理 HTTP Alternative Services 介绍 HTTP2学习笔记 《HTTP/2基础教程》
cool~