强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

HTTP 协议详解教程 / 第 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 ...            |
+-----------------------------------------------+
字段长度说明
Length24 位帧负载长度(最大 16MB)
Type8 位帧类型
Flags8 位标志位
R1 位保留位
Stream Identifier31 位流 ID
Frame Payload可变帧数据

帧类型

类型说明
DATA0x0传输数据
HEADERS0x1头部帧
PRIORITY0x2优先级设置
RST_STREAM0x3终止流
SETTINGS0x4连接设置
PUSH_PROMISE0x5服务器推送
PING0x6心跳
GOAWAY0x7关闭连接
WINDOW_UPDATE0x8流量控制
CONTINUATION0x9头部延续

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.1HTTP/2
格式文本二进制
多路复用不支持支持
头部压缩HPACK
服务器推送不支持支持
队头阻塞仅 TCP 级别
连接数多个一个

14.9 性能优化建议

优化项说明
合并域名不再必要HTTP/2 多路复用,单域名即可
减少内联资源HTTP/2 推送或并发请求效率更高
合理使用优先级关键资源设置高优先级
避免服务器推送滥用推送不当反而降低性能
开启 TLS浏览器要求 HTTP/2 + TLS

⚠️ 注意事项

  1. HTTP/2 不解决 TCP 队头阻塞:丢包仍会影响所有流
  2. 服务器推送效果有限:大多数场景下预加载更优
  3. 需要 TLS:浏览器不支持明文 HTTP/2
  4. 降级到 HTTP/1.1:确保服务器支持协议降级
  5. 测试工具:使用 curl --http2 或 Wireshark 验证

🔗 扩展阅读


下一章第 15 章:HTTP/3 与 QUIC — 基于 UDP 的传输、连接迁移、0-RTT、性能优势