第 14 章:HTTP/2
第 14 章:HTTP/2
HTTP/2 是 HTTP 协议的重大升级,通过二进制分帧、多路复用、头部压缩等技术,显著提升了 Web 性能。
14.1 HTTP/2 概述
为什么需要 HTTP/2
HTTP/1.1 存在的问题:
| 问题 | 说明 |
|---|
| 队头阻塞 | 前一个请求阻塞后面所有请求 |
| 连接数限制 | 浏览器对同域名限制 6-8 个连接 |
| 头部冗余 | 每个请求重复发送相同头部 |
| 无法主动推送 | 服务器不能主动向客户端推送 |
HTTP/2 核心特性
| 特性 | 说明 |
|---|
| 二进制分帧 | 不再是纯文本协议 |
| 多路复用 | 单连接上并发多个请求 |
| 头部压缩 | HPACK 算法减少头部开销 |
| 服务器推送 | 主动推送资源 |
| 流优先级 | 客户端指定请求优先级 |
14.2 二进制分帧
帧结构
+-----------------------------------------------+
| Length (24) |
+---------------+---------------+---------------+
| Type (8) | Flags (8) |
+-+-------------+---------------+---------------+
|R| Stream Identifier (31) |
+-+---------------------------------------------+
| Frame Payload ... |
+-----------------------------------------------+
| 字段 | 长度 | 说明 |
|---|
| Length | 24 位 | 帧负载长度(最大 16MB) |
| Type | 8 位 | 帧类型 |
| Flags | 8 位 | 标志位 |
| R | 1 位 | 保留位 |
| Stream Identifier | 31 位 | 流 ID |
| Frame Payload | 可变 | 帧数据 |
帧类型
| 类型 | 值 | 说明 |
|---|
| DATA | 0x0 | 传输数据 |
| HEADERS | 0x1 | 头部帧 |
| PRIORITY | 0x2 | 优先级设置 |
| RST_STREAM | 0x3 | 终止流 |
| SETTINGS | 0x4 | 连接设置 |
| PUSH_PROMISE | 0x5 | 服务器推送 |
| PING | 0x6 | 心跳 |
| GOAWAY | 0x7 | 关闭连接 |
| WINDOW_UPDATE | 0x8 | 流量控制 |
| CONTINUATION | 0x9 | 头部延续 |
14.3 流(Stream)与多路复用
核心概念
一个 TCP 连接
├── 流 1 (GET /index.html)
├── 流 3 (GET /style.css)
├── 流 5 (GET /app.js)
└── 流 7 (GET /image.png)
所有流并行传输,互不阻塞
| 概念 | 说明 |
|---|
| 连接(Connection) | TCP 连接 |
| 流(Stream) | 逻辑通道,双向字节流 |
| 消息(Message) | 请求或响应 |
| 帧(Frame) | 最小通信单位 |
多路复用优势
HTTP/1.1:
连接1: [请求1]──→[响应1]──→[请求2]──→[响应2]──→[请求3]──→[响应3]
(串行)
HTTP/2:
连接: [帧1][帧3][帧5][帧1][帧3][帧5][帧1][帧5]
(并行,交错传输)
14.4 头部压缩(HPACK)
问题
HTTP/1.1 中,每个请求都携带大量重复头部:
# 第一个请求
GET /api/users HTTP/1.1
Host: api.example.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36
Accept: application/json, text/html, */*
Accept-Language: zh-CN,en;q=0.9
Accept-Encoding: gzip, deflate, br
Cookie: session=abc123; theme=dark
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9...
# 第二个请求(大部分头部重复)
GET /api/posts HTTP/1.1
Host: api.example.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36
Accept: application/json, text/html, */*
Accept-Language: zh-CN,en;q=0.9
Accept-Encoding: gzip, deflate, br
Cookie: session=abc123; theme=dark
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9...
HPACK 机制
静态表(预定义 61 个常用头部)
├── :authority = ""
├── :method = GET
├── :method = POST
├── :path = /
├── :status = 200
├── accept = */*
├── accept-encoding = gzip, deflate
└── ...
动态表(连接内积累)
├── 用户代理字符串
├── Cookie
└── 自定义头部
| 机制 | 说明 | 压缩效果 |
|---|
| 静态表 | 预定义的常用头部 | 直接引用索引 |
| 动态表 | 连接内学习的头部 | 后续请求只传索引 |
| Huffman 编码 | 字符串压缩 | 额外 20-30% 压缩 |
14.5 服务器推送(Server Push)
工作原理
客户端 服务器
│ │
│── GET /index.html ─────→│
│ │── 发现需要 style.css 和 app.js
│ │
│←── PUSH_PROMISE ─────── │ (推送 style.css)
│←── PUSH_PROMISE ─────── │ (推送 app.js)
│←── HEADERS + DATA ───── │ (index.html)
│←── DATA ──────────────── │ (style.css)
│←── DATA ──────────────── │ (app.js)
Nginx 配置
# 服务器推送
location / {
http2_push /style.css;
http2_push /app.js;
try_files $uri /index.html;
}
📝 注意:服务器推送已被 Chrome 106+ 移除支持,实际使用率低,不推荐新项目使用。
14.6 流量控制
窗口机制
发送窗口 (WINDOW_UPDATE)
├── 连接级别:控制整个连接的发送量
└── 流级别:控制单个流的发送量
// 模拟流量控制
// 发送方不能发送超过窗口大小的数据
// 接收方通过 WINDOW_UPDATE 帧增加窗口
14.7 Node.js HTTP/2 实现
服务端
const http2 = require('http2');
const fs = require('fs');
const server = http2.createSecureServer({
key: fs.readFileSync('server.key'),
cert: fs.readFileSync('server.cert')
});
server.on('stream', (stream, headers) => {
const path = headers[':path'];
const method = headers[':method'];
console.log(`${method} ${path}`);
if (path === '/api/data') {
stream.respond({
':status': 200,
'content-type': 'application/json'
});
stream.end(JSON.stringify({ message: 'Hello HTTP/2' }));
} else if (path === '/') {
// 服务器推送
stream.pushStream({ ':path': '/style.css' }, (err, pushStream) => {
pushStream.respond({ ':status': 200, 'content-type': 'text/css' });
pushStream.end('body { margin: 0; }');
});
stream.respond({
':status': 200,
'content-type': 'text/html'
});
stream.end('<html><link rel="stylesheet" href="/style.css"><body>Hello</body></html>');
} else {
stream.respond({ ':status': 404 });
stream.end();
}
});
server.listen(8443, () => {
console.log('HTTP/2 服务器运行在 https://localhost:8443');
});
客户端
const http2 = require('http2');
const client = http2.connect('https://localhost:8443');
// 并发请求
const requests = ['/api/data', '/api/users', '/api/posts'].map(path => {
return new Promise((resolve, reject) => {
const req = client.request({ ':path': path });
let data = '';
req.on('response', (headers) => {
console.log(`${path}: ${headers[':status']}`);
});
req.on('data', chunk => data += chunk);
req.on('end', () => resolve(JSON.parse(data)));
req.on('error', reject);
req.end();
});
});
Promise.all(requests).then(results => {
console.log('所有请求完成:', results);
client.close();
});
使用 curl 测试
# 查看 HTTP/2 协议协商
curl -v --http2 https://localhost:8443/api/data
# 测试多路复用
curl --http2 \
https://localhost:8443/api/data \
https://localhost:8443/api/users \
https://localhost:8443/api/posts
14.8 HTTP/1.1 vs HTTP/2
| 特性 | HTTP/1.1 | HTTP/2 |
|---|
| 格式 | 文本 | 二进制 |
| 多路复用 | 不支持 | 支持 |
| 头部压缩 | 无 | HPACK |
| 服务器推送 | 不支持 | 支持 |
| 队头阻塞 | 有 | 仅 TCP 级别 |
| 连接数 | 多个 | 一个 |
14.9 性能优化建议
| 优化项 | 说明 |
|---|
| 合并域名不再必要 | HTTP/2 多路复用,单域名即可 |
| 减少内联资源 | HTTP/2 推送或并发请求效率更高 |
| 合理使用优先级 | 关键资源设置高优先级 |
| 避免服务器推送滥用 | 推送不当反而降低性能 |
| 开启 TLS | 浏览器要求 HTTP/2 + TLS |
⚠️ 注意事项
- HTTP/2 不解决 TCP 队头阻塞:丢包仍会影响所有流
- 服务器推送效果有限:大多数场景下预加载更优
- 需要 TLS:浏览器不支持明文 HTTP/2
- 降级到 HTTP/1.1:确保服务器支持协议降级
- 测试工具:使用
curl --http2 或 Wireshark 验证
🔗 扩展阅读
下一章:第 15 章:HTTP/3 与 QUIC — 基于 UDP 的传输、连接迁移、0-RTT、性能优势