HTTP/1.1 、HTTPS 、 HTTP/2 、 HTTP/3 协议结构

HTTP/3 是 HTTP 协议的第三个主要版本,于 2022 年正式标准化(RFC 9114)。它最大的变革是弃用 TCP,转而基于 QUIC 协议(Quick UDP Internet Connections,原为 Google 的 gQUIC),从根本上解决了 HTTP/2 中遗留的传输层队头阻塞问题,并进一步优化了连接建立与迁移。
QUIC 协议
QUIC 是一个基于 UDP 的、多路复用 且安全的传输层协议。它将 TLS 1.3 的加密直接融入,并内置了流量控制、丢包恢复等特性。HTTP/3 本质上就是"HTTP 语义 + QUIC 传输"。
数据包结构
QUIC 数据包分为 长头部 (Long Header)和 短头部(Short Header)。长头部用于连接建立(Initial、Handshake、Retry、Version Negotiation 等),短头部用于数据传输阶段。
长头部 (用于建立连接)
首字节最高位为 1:表示长头部。

长头部的数据包有:Initial、0-RTT、Handshake、Retry、Version Negotiation。
Initial 包
- 用途:客户端发送的第一个包,用于启动连接。包含 TLS 1.3 的 ClientHello 以及 QUIC 传输参数。
- 特点 :
- 必须携带令牌(Token):首次连接时令牌为空;若服务端要求验证地址(如防止放大攻击),会通过 Retry 包下发令牌,客户端重发 Initial 时需携带。
- 使用初始密钥加密(基于连接 ID 推导)。
- 负载中包含 CRYPTO 帧 (承载 ClientHello)和可能的 ACK 帧。
0-RTT 包
- 用途 :客户端在连接恢复 时,无需等待握手完成即可发送应用数据。这些数据使用之前会话派生的 0-RTT 密钥加密。
- 特点 :
- 仅在客户端拥有服务器下发的 PSK (预共享密钥)或 Session Ticket 时使用。
- 数据可能被重放攻击,因此服务端通常会限制 0-RTT 只用于幂等请求(如 GET)。
- 0-RTT 包不能单独发送,必须与 Initial 包一同(或紧随其后)发送。
Handshake 包
- 用途:用于携带 TLS 1.3 的 ServerHello、加密扩展、证书等握手消息。在客户端收到 Initial 包的响应后交换。
- 特点 :
- 使用 握手密钥(Handshake Key)加密,该密钥由初始密钥派生。
- 通常由服务器首先发送,客户端随后也可能发送 Handshake 包(如 Finished 消息)。
- 负载中包含 CRYPTO 帧 和可能的 ACK 帧。
Retry 包
- 用途 :服务器用于地址验证 ,要求客户端重新发送 Initial 包并携带服务器提供的令牌(Token)。这可以防御 放大攻击(攻击者伪造源 IP 发送小请求,诱使服务器发送大量响应)。
- 特点 :
- 不加密,负载中仅包含一个令牌(Token)和可选的连接 ID 重选信息。
- 客户端收到 Retry 包后,必须丢弃之前的 Initial 包,使用新的令牌重新构造 Initial 包。
- 仅由服务器发送,且只在连接建立的初始阶段使用。
Version Negotiation 包
- 用途:当客户端发送的 QUIC 版本不被服务器支持时,服务器回复 Version Negotiation 包,列出自己支持的版本列表。
- 特点 :
- 无加密,仅包含版本列表(一个或多个 32 位版本号)。
- 客户端收到后,从列表中选择一个支持的版本,或放弃连接。
- 包中不包含连接 ID 字段(但包含客户端提供的连接 ID 用于路由)。
短头部 (用于数据传输)

主要特性
1、多路复用5️无阻塞,彻底解决队头阻塞
HTTP/2 的问题 :虽然请求级队头阻塞被解决,但 TCP 是可靠有序的,一个 TCP 分片丢失会阻塞该连接上所有后续流(直到重传完成),这叫 TCP 队头阻塞。
HTTP/3 的方案 :QUIC 在单连接上提供独立的流,丢失某个流的数据包只影响该流,其他流可以继续交付。因为 UDP 本身无序,QUIC 在应用层实现了每流的可靠性和有序性。
2、0-RTT 快速握手
TCP + TLS 1.3 需要至少 1 个 RTT(往返时间)完成握手和加密协商。
QUIC 集成了 TLS 1.3,在首次连接时 1-RTT ,但如果之前连接过,可以立即发送数据(0-RTT) ,极大减少延迟(尤其适合移动设备频繁切换网络)。
3、连接无缝迁移
在 TCP 中,连接由(IP,端口)四元组唯一标识。当设备从 WiFi 切换到蜂窝网络时,IP 改变,所有 TCP 连接必须重新建立。
QUIC 使用 连接 ID(Connection ID)标识连接,不依赖 IP。因此即使 IP 或端口变化,连接仍可继续,无需重新握手。这对移动端体验提升巨大。
4、更好的丢包恢复
TCP 的丢包恢复依赖经典的快速重传和 SACK,而 QUIC 使用更精细的包头保护 和独立的包编号空间(每个流独立),并允许更灵活的拥塞控制算法(甚至可在用户空间实现)。
5、加密成为强制且默认
HTTP/2 允许明文(h2c),但主流实现强制 TLS。HTTP/3 要求必须使用 QUIC 的加密(相当于 TLS 1.3),没有明文版本。所有头部和载荷全程加密,包括原来明文传输的部分(如 HTTP/2 的伪头部)。
示例 利用 @currentspace/http3
js
import { createSecureServer } from '@currentspace/http3';
import { readFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// 加载 SSL 证书和密钥
const key = readFileSync(join(__dirname, '../ssl/key.pem'));
const cert = readFileSync(join(__dirname, '../ssl/cert.pem'));
const PORT = 8444;
// 创建 HTTP/3 服务器
const server = createSecureServer({
key,
cert,
allowHTTP1: false, // 禁用 HTTP/1.1
// 禁用重试机制
disableRetry: true,
});
// 监听会话事件
server.on('session', (session) => {
console.log(`新会话: ${session.remoteAddress}:${session.remotePort} [ALPN: ${session.alpnProtocol}]`);
});
// 监听流事件
server.on('stream', (stream, headers) => {
const path = headers[':path'] || '/';
const method = headers[':method'] || 'GET';
const protocol = headers[':scheme'] || 'https';
console.log(`收到请求: ${method} ${path} [协议: ${protocol}]`);
// 路由处理
if (path === '/' || path === '/index.html') {
// 返回测试页面
const html = readFileSync(join(__dirname, '../public/index.html'));
stream.respond({
':status': '200',
'content-type': 'text/html; charset=utf-8',
'alt-svc': `h3=":${PORT}"; ma=86400; persist=1`,
});
stream.end(html);
} else if (path === '/api/user') {
stream.respond({
':status': '200',
'content-type': 'application/json',
'access-control-allow-origin': '*',
'alt-svc': `h3=":${PORT}"; ma=86400; persist=1`,
});
stream.end(
JSON.stringify({
code: 200,
data: {
id: 1,
name: '张三',
email: 'zhangsan@example.com',
age: 28,
},
message: '获取用户信息成功',
protocol: protocol,
})
);
} else if (path === '/api/logo') {
stream.respond({
':status': '200',
'content-type': 'application/json',
'alt-svc': `h3=":${PORT}"; ma=86400; persist=1`,
});
stream.end(
JSON.stringify({
code: 200,
data: {
url: 'https://example.com/logo.png',
width: 200,
height: 100,
alt: '公司 Logo',
},
message: '获取 Logo 信息成功',
protocol: protocol,
})
);
} else {
stream.respond({
':status': '404',
'content-type': 'application/json',
'alt-svc': `h3=":${PORT}"; ma=86400; persist=1`,
});
stream.end(
JSON.stringify({
code: 404,
message: 'Not Found',
})
);
}
});
// 监听服务器启动事件
server.on('listening', () => {
const addr = server.address();
console.log('HTTP/3 + HTTP/2 服务已启动');
console.log(`监听地址: https://localhost:${addr?.port ?? PORT}`);
console.log('\n可用接口:');
console.log(' - GET /api/user 获取用户信息');
console.log(' - GET /api/logo 获取 Logo 信息');
console.log('\n浏览器访问方式:');
console.log(` 1. 首次访问: https://localhost:${PORT}/api/user (可能使用 HTTP/2)`);
console.log(` 2. 刷新页面或再次访问,浏览器会自动升级到 HTTP/3`);
console.log('\n提示:');
console.log(' - 需要接受自签名证书警告');
console.log(' - 清除浏览器缓存后重新访问');
console.log(' - Chrome 可在 chrome://net-internals/#http3 查看HTTP/3连接状态');
});
// 监听错误事件
server.on('error', (err) => {
console.error('服务器错误:', err);
});
// 启动服务
server.listen(PORT, '0.0.0.0');
为什么不能调用两次 stream.respond()?
在 HTTP/2 和 HTTP/3 协议中, 流(Stream)是单向的 ,一旦开始发送响应,就不能修改响应头部。
流生命周期 ?客户端请求 → 服务器接收 → stream.respond() → stream.end() → 流关闭
检查浏览器是否支持 h3
chrome://flags/#enable-quic
- HTTP/2 (TCP+TLS):证书不可信 → 浏览器弹出"不安全"警告 → 你点击"继续前往" → 可以访问
- HTTP/3 (QUIC):证书不可信 → 直接静默失败,没有任何提示,也没有"继续前往"按钮
vite 代理
HTTP/3 基于 QUIC 协议(UDP) ,而 HTTP/1.1 和 HTTP/2 都基于 TCP 。当 http-proxy 尝试通过 TCP 连接到一个仅支持 HTTP/3 的服务器时,连接会失败,Vite 就会返回 502 Bad Gateway。
curl 工具
检查当前是否已经安装过 curl, 如果已安装,需要先卸载。
bash
# 查看路径
which curl
# /usr/bin/curl
安装支持 http3 的 curl
bash
# 添加 Cloudflare 的 tap(提供优化版 curl)
brew tap cloudflare/cloudflare
# 安装 curl(此版本已集成 HTTP/3 支持)
brew install curl
部分提示
bash
If you need to have curl first in your PATH, run:
echo 'export PATH="/opt/homebrew/opt/curl/bin:$PATH"' >> ~/.zshrc
For compilers to find curl you may need to set:
export LDFLAGS="-L/opt/homebrew/opt/curl/lib"
export CPPFLAGS="-I/opt/homebrew/opt/curl/include"
For pkgconf to find curl you may need to set:
export PKG_CONFIG_PATH="/opt/homebrew/opt/curl/lib/pkgconfig"
配置 PATH,让新 curl 优先于系统版本
bash
echo 'export PATH="/opt/homebrew/opt/curl/bin:$PATH"' >> ~/.zshrc
source ~/.zshrc
再次查看路径
bash
which curl
# /opt/homebrew/opt/curl/bin/curl
curl 测试
bash
curl -k https://localhost:8444/api/user
新会话: 127.0.0.1:58984 [ALPN: h2]
收到请求: GET /api/user [协议: https]
bash
curl --http3-only -k https://localhost:8444/api/user
新会话: 127.0.0.1:55838 [ALPN: h3]
收到请求: GET /api/user [协议: https]
检查服务器是否监听了 TCP 8444 端口
bash
lsof -i :8444

杀掉 进程
ba
lsof -ti :8444 | xargs kill -9
用 telnet 或 nc 测试 TCP 连接,用 telnet 或 nc 测试 TCP 连接
bash
nc -zv localhost 8444
使用 -v 获取详细输出,curl试图连接的IP地址、端口、TLS握手过程。
bash
curl -v https://localhost:8443/api/user

mkcert 证书
bash
# 1. 安装 mkcert (macOS)
brew install mkcert
# 2. 在本地系统安装 CA(证书颁发机构)
mkcert -install
# 3. 为 localhost 生成证书文件,会在当前工作目录下生成证书文件
mkcert localhost 127.0.0.1 ::1

bash
mkcert -uninstall
-uninstall 命令只移除了系统信任,并不会删除 mkcert 的本地 CA 文件
删除本地 CA 根证书和私钥文件
bash
mkcert -CAROOT
支持浏览器用 h3 访问 后端项目
Chrome/Firefox 对 QUIC 连接的证书验证比 TCP TLS 更严格。
| TCP TLS (h2) | QUIC (h3) | |
|---|---|---|
| 自签名证书 | ⚠️ 弹出警告,用户可跳过 | ❌ 静默失败,无法跳过 |
| CT (证书透明度) 日志 | 不强制要求 | Chrome 强制要求 |
| mkcert 证书 | ✅ 可用 | ❌ 没有 CT 日志,QUIC 连接被拒 |
mkcert 自签名证书没有 CT 日志,浏览器在后台尝试 QUIC 连接时会静默失败,所以永远回退到 h2。
强制 Chrome 允许 QUIC 使用自签名证书?
关闭chrome浏览器,然后在 终端执行
bash
# 关闭 Chrome,然后用以下命令启动
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome \
--ignore-certificate-errors \
--origin-to-force-quic-on=localhost:8444
--origin-to-force-quic-on 会强制 Chrome 对指定 origin 使用 QUIC,跳过证书验证。

访问 https://localhost:8443/api/user
新会话: ::1:57543 [ALPN: h3]
HTTP3 收到请求: GET /api/user [协议: https]
请求头、响应头

抓包

客户端发送 Initial 包,包含加密扩展(CRYPTO),开始 QUIC 握手。
QUIC IETF 是标准的 IETF QUIC 协议,HTTP/3 就运行在它之上。

Handshake , 服务端回复 Handshake 包,完成 TLS 1.3 握手。
Protected Payload (KPO),握手完成后,双方开始传输加密的应用数据(即 HTTP/3 请求与响应)。