HTTP/2 in Action
《HTTP/2 in Action》by Barry Pollard 的读书笔记。
Last updated
《HTTP/2 in Action》by Barry Pollard 的读书笔记。
Last updated
因特网使用 IP(Internet Protocol)协议,万维网(World Wide Web)有三项核心技术,HTTP、URL、HTML。
HTTP 代表超文本传输协议。基于可靠的网络连接,通常为 TCP/IP。是一个请求-响应协议。
HTTP/0.9: 没有 header、body,每个请求关闭连接,错误用 HTML 响应文本。
HTTP/1.0: 更多请求方法,添加版本号字段,首部,整数响应状态码
HTTP/1.1: 对 1.0 的标准化和完善。强制添加 Host header,持久连接(默认),管道(不流行,且支持较差),cookie。
HTTP 的安全版本,加密、完整性校验、身份验证。使用 TLS(Transport Layer Security)加密,TLS 的前身是 SSL(Secure Sockets layer)。
HTTPS的一个重大问题是,它只保证你正在连接到该服务器,而不能保证服务器值得信任。
浏览器开发者。
Postman,curl,wget。
在一个连接中,请求 1-> 响应 1 -> 请求 2 -> 响应 2 的工作方式是 HTTP 慢的根本原因,即为了等待响应会阻塞发送,在当前请求完成之前,无法发送另一个请求。
HTTP/1.1 引入管道化,连续发出多个请求,并按照顺序接收响应。但是很难实现,没有一个主流的 Web 浏览器支持管道化技术。而且存在队头(HOL)阻塞问题。
大致分为两类:
使用多个 HTTP连接
合并 HTTP 请求
使用多连接与管道化相比,不会有 HOL 阻塞问题。多数浏览器可以为每个域名分配 6 个连接。域名分片技术可以开启更多连接。但是也有缺点,建立连接需要时间,维护连接需要 CPU 和内存,TCP 的慢启动算法导致每个连接初始的拥塞窗口都很小。
发送更少的请求,精灵图,CSS 和 JavaScript 文件合并,最小化文件。缺点是引入了复杂度;合并文件会导致浪费,因为不是所有的网站都需要合并文件的所有内容;缓存。
请求和首部是文本形式,对人类友好,但对机器不友好。
首部内容重复,尤其是 cookie。
SPDY:流多路利用,请求优先级,HTTP 首部压缩,服务器推送。二进制协议。
HTTP/2 是基于 SPDY 的标准化版本。
大多数网站可以带来很大性能提升。但不能解决所有的性能问题。
几乎每个现代浏览器都支持 HTTP/2,仅支持基于 HTTPS 的 HTTP/2 是浏览器厂商遵循的事实上的标准。但是中间代理若不支持,可能降级为 1.1。
几乎所有的服务器也都支持 HTTP/2 了,但是服务器升级比较麻烦,且升级 SSL/TLS 库更麻烦。
如果中间有一环不能支持 HTTP/2,可以降级到 1.1。
Web 服务器开启 HTTP/2。
在 Web 服务器前放置一个 HTTP/2 的反向代理服务器,将 HTTP/2 转换为 HTTP/1.1 发送给 Web 服务器。
通过 CDN 实现 HTTP/2。
Web 服务器未启用 ALPN 支持,有些浏览器仅支持 ALPN。ALPN 是 TLS 的扩展。
因为增加了很多概念,是根本上的变化,不向前兼容。
二进制的、基于数据包的协议,而 HTTP/1 是完全基于文本的。
多路复用替代同步请求,每个帧分配一个流标识符表名属于哪个流。客户端发起的请求使用奇数流 ID,服务器使用偶数流 ID。ID 为 0 的流为管理连接的控制流。
流有优先级。
流可以流量控制。TCP 在连接层限流,HTTP/2 在流的层面限流。
首部压缩,cookie、user-agent、host、accept-encoding 等基本不变。HTTP/1 仅允许压缩 body。HTTP/2 还能跨请求压缩首部。
服务端推送。
浏览器仅支持基于 HTTPS(h2) 建立 HTTP/2,服务器之间可以基于 HTTP(h2c) 的 HTTP/2。
使用 HTTPS 协商,在 HTTPS 握手过程中,同时完成 HTTP/2 协商。ALPN 不会引入额外的消息往返。
使用 HTTP Upgrade header,仅适用于 h2c。客户端发起升级请求,服务端响应是否支持,客户端决定是否升级。
使用先验知识。
不管使用哪种方法启用HTTP/2连接,在HTTP/2连接上发送的第一个消息必须是HTTP/2连接前奏,或者说是“魔法”字符串。
每个HTTP/2帧由一个固定长度的头部和不定长度的负载组成。
标志位的含义和值取决于帧类型。
SETTING 帧:服务器和客户端必须发送的第一个帧(除了魔法字符串),只包含键值对。流 ID 为 0。标志位 0 表示发起设置,标志位 1 表示确认设置。
WINDOW_UPDATE 帧:用于流量控制。流量控制仅用于 DATA 帧。
PRIORITY 帧:优先级设定。
HEADERS 帧:冒号开头是严格定义的伪首部,不能自己随便添加。可以自定义非冒号开头的普通 header。有四个标志位:END_STREAM, END_HEADERS, PADDED, PRIORITY
DATA 帧:消息体。
GOAWAY 帧:错误或关闭连接。
CONTINUATION 帧:太大的首部需要。必须紧跟在 HEADERS 帧后面。降低了多路复用性。
PING 帧:计算消息往返时间。保持一个不使用的连接。
PUSH_PROMISE 帧:服务端通知客户端要发起推送。
RST_STREAM 帧:直接取消一个流。
ALTSVC 帧:允许服务端宣告获取资源时可用的其他服务。
ORIGIN 帧:服务器使用它来宣告自己可以处理哪些源(比如域名)的请求。
CACHE_DIGEST 帧:客户端可以使用这个帧来表明自己缓存了哪些资源。
HTTP/2服务端推送(以下称为HTTP/2推送)允许服务器发回客户端未请求的额外资源。
若服务端知道此页面需要哪些关键资源,不需要等客户端请求而是直接推送,则能减少往返次数。
需要注意的一个关键点是,仅在响应初始请求时才会发送推送的资源。HTTP/2中不是真正的双向流。推送资源是为了响应初始请求而做出的额外响应。完成该初始请求后,流会被关闭,除非客户端发起其他请求,否则不能推送其他资源。
使用HTTP link首部通知Web服务器推送资源。
可以从下游系统推送:
可以更早推送:
不需要在整个链路中都支持推送。
资源不是被直接推到网页中,而是被推到缓存中。
HTTP/2推送缓存被绑定到连接,而不是页面。当资源从连接的推送缓存中被“认领”并拿出后,就不能再从推送缓存中使用它了。推送缓存还有一点和HTTP缓存不同,那就是不缓存的资源(在HTTP cache-control首部中设置了no-cache和no-store)也可以被推送,并可以从推送缓存中读取它们。
客户端可以通过发送RST_STREAM帧来拒绝推送资源。
服务器可以记录它在一个客户端的连接上推送过哪些资源。
客户端发送一个if-modified-since或者etag首部。
在客户端记录哪些资源已经被推送。cookie是做这个的理想载体。
缓存摘要是一个提议,浏览器用它来告诉服务器缓存里有什么内容。
推送需要的最少资源,来“填充空闲的网络时间,不要做多余的操作。
很多原因可能导致没有 HTTP/2 推送。
高效使用HTTP/2推送的关键是利用连接未被使用时的空闲带宽。
预加载没有HTTP/2推送那么快,但是它是一个浏览器发起的请求,有一些优势:
浏览器知道缓存里有什么,然后它就知道是否要发出对应的请求。
没有那么多复杂性。
可以从其它域名加载资源。
HTTP/2在设计上是向后兼容的。
压缩大文件比压缩小文件效果好。总的来说,HTTP/2可能不需要像HTTP/1.1那样有那么多的资源合并,但是在拆分文件之前要考虑一下带来的压缩率下降问题。
在HTTP/2的世界里,域名分片的意义没有那么大,并且设置和管理这些额外的基础设施也是一种负担。
HTTP/2推送要消除对内联资源的需求。
不管HTTP/2提供了哪些方法来优化请求和响应,少发送数据总是好的。比如使用合适的文件格式和大小,压缩文本数据。
使用缓存防止重复发送数据。
Service Worker可以大幅减少网络加载。
连接合并。HTTP/2规范允许多个域名使用同一个HTTP/2连接,这些域名被解析到同一个IP地址。
流不会被重用。流是一个虚拟的概念,它是在每个帧上标示的一个数字,也就是流ID。
在HTTP/1.1下流量控制不是必需的,因为不管何时最多只会有一个消息在传输。因此,可以使用连接层的TCP流量控制。
在HTTP/2下,使用由多个流组成的多路复用的连接,所以只使用连接层的流量控制就不够了。不仅需要连接层的控制,还需要流层级的控制。
优先级是一种建议或提示,完全由响应方决定要不要忽略优先级。
有两种方式设置优先级:
流依赖
流权重
使用流优先级的目的是尽量高效利用连接,而不是作为一种阻塞机制。
新增的流还可以是排他的。
为了使优先级模型更简单,一些客户端会使用PRIORITY帧预设置一些假流,并为它们设置对应的优先级,然后将请求挂在这些流下面。在HTTP/2标准制定的后期才添加了支持假PRIORITY帧的概念,它是更灵活的方案,支持更轻量的优先级模型。这些假流仅用于优先级排序,永远不会被用来直接发送请求。
规定一定要支持流优先级策略,很多客户端和服务端的实现都选择不实现优先级策略。
H2spec是一个HTTP/2一致性测试工具,它发送不同的消息到HTTP/2服务器,并检查服务器是否正确遵守规范。
很多HTTP/2的实现不严格遵守规范。
首部重复很多。有些首部很长,如 cookie。
有损压缩常用于媒体。
无损压缩常见三种方法:
查表法。
更高效的编码技术:Huffman编码是更进一步的可变长度编码。它的工作原理是根据每个值的使用频率为其分配一个唯一代码,并且保证没有一个代码是其他代码的前缀。
lookback(反查)压缩。
浏览器通过accept-encodingHTTP首部告诉服务器它所支持的压缩算法。HTTP正文压缩通常用于文本数据,gzip。
HPACK(不是简写),它基于查询表和Huffman编码,但(关键)不是基于反查的压缩方法。
HPACK格式被有意保持简单且死板。这两个特征都会减少因为实现错误所带来的互操作风险,或者安全问题。没有定义扩展机制,如要改变当前格式,只能使用一个完整的替代品。
HPACK有一个静态表,包含61个常见的HTTP首部名称(有些还有值)。对于表中首部名/首部值组合(比如:method: GET)这种条目,可以只使用其中的首部名部分(如:method)。
除了静态表以外,HPACK还有一个连接级的动态表,从位置62开始(跟在静态表之后),在会话过程中创建。
没有引用索引表的首部值可以使用ASCII编码或者Huffman编码来传输。
Huffman编码通常占用更少的空间。
在HPACK中可以使用多种方法来发送HTTP首部,浏览器可能使用不同的方式来编码HTTP首部。
TCP 连接和 HTTPS 的建立导致延时。
TCP 慢启动算法,初始的窗口很小。
实际上,TCP的拥塞避免阶段的增长更慢。6个连接的话,每个连接都要经历这个过程。
TCP比较小心谨慎,在闲置一段时间后,网络情况可能发生变化,所以TCP将拥塞窗口大小降低,重新进行慢启动流程。
TCP还把丢包当成极端事件。它认为这个事件是由容量限制造成的,因而它会做出激烈反应,直接将拥塞窗口大小减半。认为丢包完全是由拥堵造成的,然后大幅降低网速,这是不正确的。新的TCP拥塞控制算法对网络丢包的处理不像之前的示例中那么极端,它们可能将拥塞窗口调小一些(不到一半),在丢包之后让其快速增长(类似慢启动),或者使用其他指标来决定要不要调整容量(比如把平均的往返时间当作更好的指标)。
认为丢包完全是由拥堵造成的,然后大幅降低网速,这是不正确的。新的TCP拥塞控制算法对网络丢包的处理不像之前的示例中那么极端,它们可能将拥塞窗口调小一些(不到一半),在丢包之后让其快速增长(类似慢启动),或者使用其他指标来决定要不要调整容量(比如把平均的往返时间当作更好的指标)。
HTTP/2在HTTP层解决了队头阻塞(HOL)的问题,因为有多路复用,单个响应的延迟不会影响其他资源使用当前的HTTP连接。但是,在TCP层队头阻塞依然存在。一个流的丢包会直接影响到其他所有的流,尽管它们可能不需要排队。在严重的丢包情况下,HTTP/2的性能会更糟。
TCP有了很多优化和创新。然而不幸的是,TCP通常由底层操作系统控制,我们在操作系统之外很少有机会更改它。所以,最佳使用TCP的方法就是使用最新版本的操作系统。
CP可以使用SACK(Selective Acknowledgment,选择性响应)响应乱序的数据包,以避免丢包时重传。
可以配置禁止重启慢启动。
TFO(TCP Fast Open,TCP快速打开)允许使用TCP三次握手的初始SYN部分发送初始数据包。出于安全的原因,这个数据包只能在TCP重连时使用,而不能在初次连接时使用。
TFO带来的提升真的很显著。Google曾经表示:“通过对网络流量的分析和网络仿真,我们得出TFO能够将HTTP网络延迟降低15%,网页整页加载时间平均降低10%,有时候能降低40%。
使用拥塞控制算法,PRR和BBR。BBR(Bottleneck Bandwidth and Round-trip propagation time,瓶颈带宽和往返时间),已经有数据表明它可以大幅度提升性能[17],特别是对于HTTP/2连接。
QUIC(发音同quick)是Google(又是Google)发明的一个基于UDP的协议。
FEC(Forward Error Correction,前向纠错)试图通过在邻近的数据包中添加一个QUIC数据包的部分数据来减少数据包重传的需求。这个想法是,如果只丢了一个数据包,那应该可以从成功传送的数据包中重新组合出该数据包。这个方法可以类比于“网络世界的RAID 5。
连接迁移旨在减少连接创建的开销,它通过支持连接在网络之间迁移来实现。
多路径的技术同时使用Wi-Fi和移动网络进行一次QUIC连接。
QUIC取代了TCP提供的大多数功能(创建连接、可靠性和拥塞控制部分),取代了HTTPS的全部(降低了创建延迟),甚至还有HTTP/2的一部分(流量控制和首部压缩)功能。
QUIC不会替代HTTP/2,但它会接管传输层的一些工作,在上层运行较轻的HTTP/2实现。
UDP是一种基础协议,也在内核中实现。在它之上的任何东西都需要在应用层中构建,也就是所说的用户空间。
UDP并非没有问题。它是一种常见的协议,但不像TCP使用那么普遍。UDP的另一个问题是,用户空间并不总是与高度优化的内核空间一样高效。
QUIC旨在成为一种通用的协议,HTTP只是它的一种用途。虽然HTTP目前是QUIC的主要用例,也是工作组目前正在关注的焦点,但该协议的设计考虑了潜在的其他应用场景。
HTTPS内置于QUIC中,与HTTP/2不同,QUIC不能用于未加密的HTTP连接。
HTTP/2的隐私问题饱受争议。
基于HTTPS的DNS(DoH)。