---
description: 《HTTP/2 in Action》by Barry Pollard 的读书笔记。
---

# HTTP/2 in Action

## Part 1. 向 HTTP/2 靠拢

### Chapter 1. 万维网与 HTTP

#### 1.1 万维网的原理

因特网使用 IP（Internet Protocol）协议，万维网（World Wide Web）有三项核心技术，HTTP、URL、HTML。

![](https://3232244687-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LYZow-MmROshIrkwdtE%2F-MlIGagTqjBX1fD5Esui%2F-MlIHdZtesyFp_NeKiFi%2Fimage.png?alt=media\&token=ce51b2d3-1702-4084-a4b7-e77e54d7d819)

#### 1.2 什么是 HTTP

HTTP 代表超文本传输协议。基于可靠的网络连接，通常为 TCP/IP。是一个请求-响应协议。

#### 1.3 HTTP 的语法和历史

* HTTP/0.9: 没有 header、body，每个请求关闭连接，错误用 HTML 响应文本。
* HTTP/1.0: 更多请求方法，添加版本号字段，首部，整数响应状态码
* HTTP/1.1: 对 1.0 的标准化和完善。强制添加 Host header，持久连接（默认），管道（不流行，且支持较差），cookie。

#### 1.4 HTTPS

HTTP 的安全版本，加密、完整性校验、身份验证。使用 TLS（Transport Layer Security）加密，TLS 的前身是 SSL（Secure Sockets layer）。

HTTPS的一个重大问题是，它只保证你正在连接到该服务器，而不能保证服务器值得信任。

#### 1.5 查看、发送和接收 HTTP 消息的工具

浏览器开发者。

Postman，curl，wget。

### Chapter 2. 通向 HTTP/2 之路

#### 2.1 HTTP 和当前的万维网

在一个连接中，请求 1-> 响应 1 -> 请求 2 -> 响应 2 的工作方式是 HTTP 慢的根本原因，即为了等待响应会阻塞发送，在当前请求完成之前，无法发送另一个请求。

HTTP/1.1 引入管道化，连续发出多个请求，并按照顺序接收响应。但是很难实现，没有一个主流的 Web 浏览器支持管道化技术。而且存在队头（HOL）阻塞问题。

#### 2.2 解决 HTTP/1.1 性能问题的方案

大致分为两类：

* 使用多个 HTTP连接
* 合并 HTTP 请求

使用多连接与管道化相比，不会有 HOL 阻塞问题。多数浏览器可以为每个域名分配 6 个连接。域名分片技术可以开启更多连接。但是也有缺点，建立连接需要时间，维护连接需要 CPU 和内存，TCP 的慢启动算法导致每个连接初始的拥塞窗口都很小。

发送更少的请求，精灵图，CSS 和 JavaScript 文件合并，最小化文件。缺点是引入了复杂度；合并文件会导致浪费，因为不是所有的网站都需要合并文件的所有内容；缓存。

#### 2.3 HTTP/1.1 的其他问题

请求和首部是文本形式，对人类友好，但对机器不友好。

首部内容重复，尤其是 cookie。

#### 2.5 从 HTTP/1.1 到 HTTP/2

SPDY：流多路利用，请求优先级，HTTP 首部压缩，服务器推送。二进制协议。

HTTP/2 是基于 SPDY 的标准化版本。

#### 2.6 HTTP/2 对 Web 性能的影响

大多数网站可以带来很大性能提升。但不能解决所有的性能问题。

### Chapter 3. 升级到 HTTP/2

#### 3.1 HTTP/2 的支持

几乎每个现代浏览器都支持 HTTP/2，仅支持基于 HTTPS 的 HTTP/2 是浏览器厂商遵循的事实上的标准。但是中间代理若不支持，可能降级为 1.1。

几乎所有的服务器也都支持 HTTP/2 了，但是服务器升级比较麻烦，且升级 SSL/TLS 库更麻烦。

如果中间有一环不能支持 HTTP/2，可以降级到 1.1。

#### 3.2 网站开启 HTTP/2 的方法

Web 服务器开启 HTTP/2。

在 Web 服务器前放置一个 HTTP/2 的反向代理服务器，将 HTTP/2 转换为 HTTP/1.1 发送给 Web 服务器。

通过 CDN 实现 HTTP/2。

#### 3.3 常见问题

Web 服务器未启用 ALPN 支持，有些浏览器仅支持 ALPN。ALPN 是 TLS 的扩展。

## Part 2. 使用 HTTP/2

### Chapter 4. HTTP/2 协议基础

#### 4.1 为什么是 HTTP/2 而不是 HTTP/1.2

因为增加了很多概念，是根本上的变化，不向前兼容。

* 二进制的、基于数据包的协议，而 HTTP/1 是完全基于文本的。
* 多路复用替代同步请求，每个帧分配一个流标识符表名属于哪个流。客户端发起的请求使用奇数流 ID，服务器使用偶数流 ID。ID 为 0 的流为管理连接的控制流。

![](https://3232244687-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LYZow-MmROshIrkwdtE%2F-MlIe2vkK-xYZ0-U7U1h%2F-MlIfjpTETDnWDVhaqzC%2Fimage.png?alt=media\&token=77d965d5-ceb7-45f3-952c-7178bba27aa4)

* 流有优先级。
* 流可以流量控制。TCP 在连接层限流，HTTP/2 在流的层面限流。
* 首部压缩，cookie、user-agent、host、accept-encoding 等基本不变。HTTP/1 仅允许压缩 body。HTTP/2 还能跨请求压缩首部。
* 服务端推送。

#### 4.2 如何创建一个 HTTP/2 连接

浏览器仅支持基于 HTTPS(h2) 建立 HTTP/2，服务器之间可以基于 HTTP(h2c) 的 HTTP/2。

* 使用 HTTPS 协商，在 HTTPS 握手过程中，同时完成 HTTP/2 协商。ALPN 不会引入额外的消息往返。

![](https://3232244687-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LYZow-MmROshIrkwdtE%2F-MlIh8qZI5A08PMfNNVZ%2F-MlIiowpvZ2ZbWBeqz0-%2Fimage.png?alt=media\&token=b2c7f5d2-1f34-43f6-b9b9-bb3f5bb28b26)

* 使用 HTTP Upgrade header，仅适用于 h2c。客户端发起升级请求，服务端响应是否支持，客户端决定是否升级。
* 使用先验知识。

不管使用哪种方法启用HTTP/2连接，在HTTP/2连接上发送的第一个消息必须是HTTP/2连接前奏，或者说是“魔法”字符串。

#### 4.3 HTTP/2 帧

每个HTTP/2帧由一个固定长度的头部和不定长度的负载组成。

![HTTP/2 帧头部格式](https://3232244687-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LYZow-MmROshIrkwdtE%2F-MlJQsEwWJoabcgE4p0T%2F-MlJRosgkeF3T6UZ86ld%2Fimage.png?alt=media\&token=63db019e-0c76-40bb-97d4-cc85b23cd31c)

标志位的含义和值取决于帧类型。

* SETTING 帧：服务器和客户端必须发送的第一个帧（除了魔法字符串），只包含键值对。流 ID 为 0。标志位 0 表示发起设置，标志位 1 表示确认设置。
* WINDOW\_UPDATE 帧：用于流量控制。流量控制仅用于 DATA 帧。
* PRIORITY 帧：优先级设定。
* HEADERS 帧：冒号开头是严格定义的伪首部，不能自己随便添加。可以自定义非冒号开头的普通 header。有四个标志位：END\__STREAM, END\__&#x48;EADERS, PADDED, PRIORITY
* DATA 帧：消息体。
* GOAWAY 帧：错误或关闭连接。
* CONTINUATION 帧：太大的首部需要。必须紧跟在 HEADERS 帧后面。降低了多路复用性。
* PING 帧：计算消息往返时间。保持一个不使用的连接。
* PUSH\_PROMISE 帧：服务端通知客户端要发起推送。
* RST\_STREAM 帧：直接取消一个流。
* ALTSVC 帧：允许服务端宣告获取资源时可用的其他服务。
* ORIGIN 帧：服务器使用它来宣告自己可以处理哪些源（比如域名）的请求。
* CACHE\_DIGEST 帧：客户端可以使用这个帧来表明自己缓存了哪些资源。

### Chapter 5. 实现 HTTP/2 推送

#### 5.1 什么是 HTTP/2 服务端推送

HTTP/2服务端推送（以下称为HTTP/2推送）允许服务器发回客户端未请求的额外资源。

若服务端知道此页面需要哪些关键资源，不需要等客户端请求而是直接推送，则能减少往返次数。

![](https://3232244687-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LYZow-MmROshIrkwdtE%2F-MlJZaTrrptDEjhM49iJ%2F-MlJ_i7SCtP_yt9lsE8d%2Fimage.png?alt=media\&token=a86a962d-eee9-40a8-9b97-a8ea7dc22d77)

需要注意的一个关键点是，仅在响应初始请求时才会发送推送的资源。HTTP/2中不是真正的双向流。推送资源是为了响应初始请求而做出的额外响应。完成该初始请求后，流会被关闭，除非客户端发起其他请求，否则不能推送其他资源。

#### 5.2 如何推送

* 使用HTTP link首部通知Web服务器推送资源。

可以从下游系统推送：

![](https://3232244687-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LYZow-MmROshIrkwdtE%2F-MlJZaTrrptDEjhM49iJ%2F-MlJbNf3ilZ3BBm_G8Ff%2Fimage.png?alt=media\&token=b2c1c7de-e9f9-45a4-9e0b-259eb8d79feb)

![](https://3232244687-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LYZow-MmROshIrkwdtE%2F-MlJZaTrrptDEjhM49iJ%2F-MlJbTOX3m2TLKxe7eub%2Fimage.png?alt=media\&token=521a6f92-52cb-4d34-8b66-a612b1cd5e8a)

可以更早推送：

![](https://3232244687-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LYZow-MmROshIrkwdtE%2F-MlJZaTrrptDEjhM49iJ%2F-MlJbiOY4nK5fgz3VDnG%2Fimage.png?alt=media\&token=f91fa136-90a4-4b40-95ca-52afe405baae)

不需要在整个链路中都支持推送。

#### 5.3 HTTP/2 推送在浏览器中如何运作

资源不是被直接推到网页中，而是被推到缓存中。

![](https://3232244687-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LYZow-MmROshIrkwdtE%2F-MlJZaTrrptDEjhM49iJ%2F-MlJcJMx5XBheoZgCoTN%2Fimage.png?alt=media\&token=c96e4ea4-ebc1-4f2e-8412-f92ac383c2d2)

HTTP/2推送缓存被绑定到连接，而不是页面。当资源从连接的推送缓存中被“认领”并拿出后，就不能再从推送缓存中使用它了。推送缓存还有一点和HTTP缓存不同，那就是不缓存的资源（在HTTP cache-control首部中设置了no-cache和no-store）也可以被推送，并可以从推送缓存中读取它们。

客户端可以通过发送RST\_STREAM帧来拒绝推送资源。

#### 5.4 如何实现条件推送

* 服务器可以记录它在一个客户端的连接上推送过哪些资源。
* 客户端发送一个if-modified-since或者etag首部。
* 在客户端记录哪些资源已经被推送。cookie是做这个的理想载体。
* 缓存摘要是一个提议，浏览器用它来告诉服务器缓存里有什么内容。

#### 5.5 推送什么

推送需要的最少资源，来“填充空闲的网络时间，不要做多余的操作。

#### 5.6 HTTP/2 推送的常见问题

很多原因可能导致没有 HTTP/2 推送。

#### 5.7 HTTP/2 推送对性能的影响

高效使用HTTP/2推送的关键是利用连接未被使用时的空闲带宽。

#### 5.8 对比推送和预加载

预加载没有HTTP/2推送那么快，但是它是一个浏览器发起的请求，有一些优势：

* 浏览器知道缓存里有什么，然后它就知道是否要发出对应的请求。
* 没有那么多复杂性。
* 可以从其它域名加载资源。

#### 5.9 HTTP/2 推送的其它应用场景

### Chapter 6. HTTP/2 优化

#### 6.1 HTTP/2 对 Web 开发者的影响

HTTP/2在设计上是向后兼容的。

#### 6.2 一些 HTTP/1.1 中的优化方法是否成了反模式

压缩大文件比压缩小文件效果好。总的来说，HTTP/2可能不需要像HTTP/1.1那样有那么多的资源合并，但是在拆分文件之前要考虑一下带来的压缩率下降问题。

在HTTP/2的世界里，域名分片的意义没有那么大，并且设置和管理这些额外的基础设施也是一种负担。

HTTP/2推送要消除对内联资源的需求。

#### 6.3 在 HTTP/2 下依然有效的性能优化技术

不管HTTP/2提供了哪些方法来优化请求和响应，少发送数据总是好的。比如使用合适的文件格式和大小，压缩文本数据。

使用缓存防止重复发送数据。

Service Worker可以大幅减少网络加载。

#### 6.4 同时对 HTTP/1.1 和 HTTP/2 做优化

连接合并。HTTP/2规范允许多个域名使用同一个HTTP/2连接，这些域名被解析到同一个IP地址。

## Part 3. HTTP/2 进阶

### Chapter 7. 高级 HTTP/2 概念

#### 7.1 流状态

流不会被重用。流是一个虚拟的概念，它是在每个帧上标示的一个数字，也就是流ID。

![](https://3232244687-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LYZow-MmROshIrkwdtE%2F-MlJhC10VVCpLPsodxOw%2F-MlJi9TEU9TcPcwJN-0U%2Fimage.png?alt=media\&token=60e8113b-2166-4718-92eb-3a842b971f52)

#### 7.2 流量控制

在HTTP/1.1下流量控制不是必需的，因为不管何时最多只会有一个消息在传输。因此，可以使用连接层的TCP流量控制。

在HTTP/2下，使用由多个流组成的多路复用的连接，所以只使用连接层的流量控制就不够了。不仅需要连接层的控制，还需要流层级的控制。

#### 7.3 流优先级

优先级是一种建议或提示，完全由响应方决定要不要忽略优先级。

有两种方式设置优先级：

* 流依赖
* 流权重

使用流优先级的目的是尽量高效利用连接，而不是作为一种阻塞机制。

新增的流还可以是排他的。

![](https://3232244687-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LYZow-MmROshIrkwdtE%2F-MlJhC10VVCpLPsodxOw%2F-MlJimJmBmCSLaIRjqMj%2Fimage.png?alt=media\&token=ba177222-9e45-4d14-9b2d-c31ab16c51c4)

为了使优先级模型更简单，一些客户端会使用PRIORITY帧预设置一些假流，并为它们设置对应的优先级，然后将请求挂在这些流下面。在HTTP/2标准制定的后期才添加了支持假PRIORITY帧的概念，它是更灵活的方案，支持更轻量的优先级模型。这些假流仅用于优先级排序，永远不会被用来直接发送请求。

规定一定要支持流优先级策略，很多客户端和服务端的实现都选择不实现优先级策略。

#### 7.4 HTTP/2 一致性测试

H2spec是一个HTTP/2一致性测试工具，它发送不同的消息到HTTP/2服务器，并检查服务器是否正确遵守规范。

很多HTTP/2的实现不严格遵守规范。

### Chapter 8. HPACK 首部压缩

#### 8.1 为什么需要首部压缩

首部重复很多。有些首部很长，如 cookie。

#### 8.2 压缩的运作方式

有损压缩常用于媒体。

无损压缩常见三种方法：

* 查表法。
* 更高效的编码技术：Huffman编码是更进一步的可变长度编码。它的工作原理是根据每个值的使用频率为其分配一个唯一代码，并且保证没有一个代码是其他代码的前缀。
* lookback(反查)压缩。

#### 8.3 HTTP 正文压缩

浏览器通过accept-encodingHTTP首部告诉服务器它所支持的压缩算法。HTTP正文压缩通常用于文本数据，gzip。

#### 8.4 HTTP/2 的 HPACK 首部压缩

HPACK（不是简写），它基于查询表和Huffman编码，但（关键）不是基于反查的压缩方法。

HPACK格式被有意保持简单且死板。这两个特征都会减少因为实现错误所带来的互操作风险，或者安全问题。没有定义扩展机制，如要改变当前格式，只能使用一个完整的替代品。

HPACK有一个静态表，包含61个常见的HTTP首部名称（有些还有值）。对于表中首部名/首部值组合（比如:method: GET）这种条目，可以只使用其中的首部名部分（如:method）。

除了静态表以外，HPACK还有一个连接级的动态表，从位置62开始（跟在静态表之后），在会话过程中创建。

没有引用索引表的首部值可以使用ASCII编码或者Huffman编码来传输。

Huffman编码通常占用更少的空间。

#### 8.5 HPACK 压缩实例

#### 8.6 客户端和服务端对 HPACK 的实现

在HPACK中可以使用多种方法来发送HTTP首部，浏览器可能使用不同的方式来编码HTTP首部。

#### 8.7 HPACK 的价值

## Part 4. HTTP 的未来

### Chapter 9. TCP、QUIC 和 HTTP/3

#### 9.1 TCP 的低效因素，以及 HTTP

TCP 连接和 HTTPS 的建立导致延时。

TCP 慢启动算法，初始的窗口很小。

![](https://3232244687-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LYZow-MmROshIrkwdtE%2F-MlJnyzb7jsczcKl-FCs%2F-MlJpkBQ2u6A9NrTu_ep%2Fimage.png?alt=media\&token=6376cc00-33ef-4aff-8d26-49eaa036d4e1)

实际上，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重连时使用，而不能在初次连接时使用。

![](https://3232244687-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LYZow-MmROshIrkwdtE%2F-MlJnyzb7jsczcKl-FCs%2F-MlJr7JZ4RMC-6A3-cbh%2Fimage.png?alt=media\&token=5bea26e3-ca31-421a-8148-dce213885ab5)

TFO带来的提升真的很显著。Google曾经表示：“通过对网络流量的分析和网络仿真，我们得出TFO能够将HTTP网络延迟降低15％，网页整页加载时间平均降低10％，有时候能降低40％。

使用拥塞控制算法，PRR和BBR。BBR（Bottleneck Bandwidth and Round-trip propagation time，瓶颈带宽和往返时间），已经有数据表明它可以大幅度提升性能\[17]，特别是对于HTTP/2连接。

#### 9.2 QUIC

QUIC（发音同quick）是Google（又是Google）发明的一个基于UDP的协议。

FEC（Forward Error Correction，前向纠错）试图通过在邻近的数据包中添加一个QUIC数据包的部分数据来减少数据包重传的需求。这个想法是，如果只丢了一个数据包，那应该可以从成功传送的数据包中重新组合出该数据包。这个方法可以类比于“网络世界的RAID 5。

连接迁移旨在减少连接创建的开销，它通过支持连接在网络之间迁移来实现。

多路径的技术同时使用Wi-Fi和移动网络进行一次QUIC连接。

![](https://3232244687-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LYZow-MmROshIrkwdtE%2F-MlJnyzb7jsczcKl-FCs%2F-MlJrXodUg99YbVO1WXa%2Fimage.png?alt=media\&token=58afac5a-7597-44a5-80ff-9d489f39936e)

QUIC取代了TCP提供的大多数功能（创建连接、可靠性和拥塞控制部分），取代了HTTPS的全部（降低了创建延迟），甚至还有HTTP/2的一部分（流量控制和首部压缩）功能。

QUIC不会替代HTTP/2，但它会接管传输层的一些工作，在上层运行较轻的HTTP/2实现。

UDP是一种基础协议，也在内核中实现。在它之上的任何东西都需要在应用层中构建，也就是所说的用户空间。

UDP并非没有问题。它是一种常见的协议，但不像TCP使用那么普遍。UDP的另一个问题是，用户空间并不总是与高度优化的内核空间一样高效。

QUIC旨在成为一种通用的协议，HTTP只是它的一种用途。虽然HTTP目前是QUIC的主要用例，也是工作组目前正在关注的焦点，但该协议的设计考虑了潜在的其他应用场景。

HTTPS内置于QUIC中，与HTTP/2不同，QUIC不能用于未加密的HTTP连接。

### Chapter 10. HTTP 将何去何从

#### 10.1 关于 HTTP/2 的争议，以及它没有解决的问题

HTTP/2的隐私问题饱受争议。

#### 10.2 HTTP/2 的实际应用

#### 10.3 HTTP/2 的未来版本，HTTP/3 或者 HTTP/4 会带来什么

#### 10.4 将 HTTP 当做一个更通用的传输协议

基于HTTPS的DNS（DoH）。

