跳到主要内容

HTTP 版本演进

HTTP(HyperText Transfer Protocol,超文本传输协议)是互联网上应用最广泛的协议之一。从 1991 年诞生至今,HTTP 经历了多次重大升级,每一次升级都是为了解决上一版本的痛点。本文将带你了解 HTTP 从 1.0 到 3.0 的演进历程。

HTTP/1.0:一次请求一次连接

背景

HTTP/1.0 于 1996 年发布(RFC 1945),是第一个被广泛使用的 HTTP 版本。在此之前的 HTTP/0.9 只能传输 HTML 文本,功能非常有限。

核心特性

  • 请求/响应模型:客户端发送请求,服务器返回响应
  • 支持多种内容类型:通过 Content-Type 头部支持图片、视频等多媒体
  • 状态码:引入 200、404、500 等状态码
  • 请求头和响应头:可以传递元信息

主要问题:短连接

HTTP/1.0 默认使用短连接,每次请求都需要经历完整的 TCP 三次握手和四次挥手:

sequenceDiagram
participant 客户端
participant 服务器

Note over 客户端,服务器: 第一次请求
客户端->>服务器: TCP 三次握手
客户端->>服务器: GET /index.html
服务器->>客户端: 200 OK + HTML内容
客户端->>服务器: TCP 四次挥手

Note over 客户端,服务器: 第二次请求
客户端->>服务器: TCP 三次握手
客户端->>服务器: GET /style.css
服务器->>客户端: 200 OK + CSS内容
客户端->>服务器: TCP 四次挥手

请求示例

GET /index.html HTTP/1.0
Host: www.example.com
User-Agent: Mozilla/5.0
Accept: text/html
HTTP/1.0 200 OK
Content-Type: text/html
Content-Length: 1234

<!DOCTYPE html>
<html>...

痛点总结

问题影响
每次请求都要建立新连接延迟高,TCP 握手开销大
无法复用连接服务器资源消耗大
队头阻塞必须等上一个请求完成才能发下一个

HTTP/1.1:持久连接与管道化

背景

HTTP/1.1 于 1997 年发布(RFC 2068,后更新为 RFC 2616),是目前仍被广泛使用的版本。它主要解决了 HTTP/1.0 的连接效率问题。

核心改进

1. 持久连接(Keep-Alive)

HTTP/1.1 默认启用持久连接,一个 TCP 连接可以发送多个请求:

sequenceDiagram
participant 客户端
participant 服务器

客户端->>服务器: TCP 三次握手

Note over 客户端,服务器: 复用同一连接
客户端->>服务器: GET /index.html
服务器->>客户端: 200 OK + HTML
客户端->>服务器: GET /style.css
服务器->>客户端: 200 OK + CSS
客户端->>服务器: GET /script.js
服务器->>客户端: 200 OK + JS

客户端->>服务器: TCP 四次挥手

2. 管道化(Pipelining)

客户端可以不等响应就连续发送多个请求:

sequenceDiagram
participant 客户端
participant 服务器

客户端->>服务器: GET /a.html
客户端->>服务器: GET /b.css
客户端->>服务器: GET /c.js
服务器->>客户端: 响应 a.html
服务器->>客户端: 响应 b.css
服务器->>客户端: 响应 c.js

⚠️ 注意:管道化要求响应必须按请求顺序返回,实际应用中因为队头阻塞问题,大多数浏览器默认禁用了管道化。

3. 分块传输编码(Chunked Transfer)

服务器可以边生成边发送内容,不需要预先知道内容总长度:

HTTP/1.1 200 OK
Transfer-Encoding: chunked

7\r\n
Hello, \r\n
6\r\n
World!\r\n
0\r\n
\r\n

4. 其他改进

  • Host 头部:支持虚拟主机,一个 IP 可以托管多个网站
  • 缓存控制:引入 Cache-ControlETag 等缓存机制
  • 断点续传:通过 Range 头部支持部分内容请求

请求示例

GET /api/users HTTP/1.1
Host: api.example.com
Connection: keep-alive
Accept: application/json
Cache-Control: no-cache

仍存在的问题

问题说明
队头阻塞(HOL Blocking)响应必须按顺序返回,前面的慢会阻塞后面的
头部冗余每次请求都要发送完整的头部,很多是重复的
文本协议解析效率不如二进制协议
单向通信服务器不能主动推送数据

HTTP/2:多路复用与二进制分帧

背景

HTTP/2 于 2015 年发布(RFC 7540),基于 Google 的 SPDY 协议演化而来。它在保持 HTTP/1.1 语义的同时,彻底改变了数据传输方式。

核心改进

1. 二进制分帧层

HTTP/2 在应用层和传输层之间引入了二进制分帧层,将所有通信分割成更小的帧:

+------------------+
| HTTP 语义层 | ← 请求方法、头部、状态码(与 HTTP/1.1 兼容)
+------------------+
| 二进制分帧层 | ← HTTP/2 新增
+------------------+
| TLS | ← 加密(通常必需)
+------------------+
| TCP |
+------------------+

2. 多路复用(Multiplexing)

在一个 TCP 连接上可以并行传输多个请求和响应,彻底解决了应用层的队头阻塞:

sequenceDiagram
participant 客户端
participant 服务器

Note over 客户端,服务器: 单一 TCP 连接,并行传输

客户端->>服务器: Stream 1: GET /index.html
客户端->>服务器: Stream 3: GET /style.css
客户端->>服务器: Stream 5: GET /script.js

服务器->>客户端: Stream 3: CSS 帧 1
服务器->>客户端: Stream 1: HTML 帧 1
服务器->>客户端: Stream 5: JS 帧 1
服务器->>客户端: Stream 1: HTML 帧 2
服务器->>客户端: Stream 3: CSS 帧 2
服务器->>客户端: Stream 5: JS 帧 2

关键概念:

  • 流(Stream):一个独立的双向数据流,承载一对请求/响应
  • 帧(Frame):HTTP/2 通信的最小单位,每个帧属于某个流

3. 头部压缩(HPACK)

使用 HPACK 算法压缩头部,维护一个头部字段表,只发送差异部分:

第一次请求头部(完整发送):
:method: GET
:path: /index.html
:authority: example.com
user-agent: Mozilla/5.0
accept: text/html

第二次请求头部(只发送差异):
:path: /style.css ← 只有路径变了
accept: text/css ← 只有 accept 变了

压缩效果:头部大小可减少 85%-90%

4. 服务器推送(Server Push)

服务器可以主动推送客户端可能需要的资源:

sequenceDiagram
participant 客户端
participant 服务器

客户端->>服务器: GET /index.html
服务器->>客户端: PUSH_PROMISE: /style.css
服务器->>客户端: PUSH_PROMISE: /script.js
服务器->>客户端: 响应 /index.html
服务器->>客户端: 推送 /style.css
服务器->>客户端: 推送 /script.js

5. 流优先级

客户端可以指定流的优先级,让重要资源(如 CSS)优先传输。

使用示例

使用 curl 查看 HTTP/2 连接:

# 查看 HTTP/2 请求详情
curl -v --http2 https://www.google.com

# 输出会显示:
# * Using HTTP2, server supports multi-use
# * Connection state changed (HTTP/2 confirmed)
# > GET / HTTP/2

仍存在的问题

问题说明
TCP 队头阻塞虽然解决了 HTTP 层的队头阻塞,但 TCP 层仍存在
TCP 握手延迟仍需要 TCP 三次握手 + TLS 握手
连接迁移困难切换网络(如 WiFi 到 4G)需要重新建立连接

💡 TCP 队头阻塞:TCP 保证数据按序到达,如果一个数据包丢失,后续所有数据包都要等待重传,即使它们属于不同的 HTTP 流。


HTTP/3:基于 QUIC 的革新

背景

HTTP/3 于 2022 年正式发布(RFC 9114),最大的变化是将传输层从 TCP 切换到 QUIC(Quick UDP Internet Connections)。QUIC 最初由 Google 开发,后被 IETF 标准化。

为什么需要 HTTP/3?

HTTP/2 的 TCP 队头阻塞问题无法在应用层解决,必须改变传输层协议:

HTTP/2 的问题:
┌─────────────────────────────────────┐
│ Stream 1 │ Stream 2 │ Stream 3 │ ← HTTP/2 多路复用
├─────────────────────────────────────┤
│ TCP 连接 │ ← 一个包丢失,全部阻塞
└─────────────────────────────────────┘

HTTP/3 的解决方案:
┌─────────────────────────────────────┐
│ Stream 1 │ Stream 2 │ Stream 3 │ ← QUIC 流
├────────────┼────────────┼───────────┤
│ 独立传输 │ 独立传输 │ 独立传输 │ ← 互不影响
└─────────────────────────────────────┘

核心特性

1. 基于 UDP + QUIC

QUIC 在 UDP 之上实现了可靠传输,同时避免了 TCP 的队头阻塞:

HTTP/3 协议栈:
+------------------+
| HTTP/3 |
+------------------+
| QUIC | ← 包含流控制、可靠传输、加密
+------------------+
| UDP |
+------------------+
| IP |
+------------------+

2. 0-RTT 连接建立

QUIC 将传输层握手和 TLS 握手合并,首次连接只需 1-RTT,重连可以实现 0-RTT:

sequenceDiagram
participant 客户端
participant 服务器

Note over 客户端,服务器: TCP + TLS 1.3(2-RTT)
客户端->>服务器: TCP SYN
服务器->>客户端: TCP SYN-ACK
客户端->>服务器: TCP ACK + TLS ClientHello
服务器->>客户端: TLS ServerHello + 证书
客户端->>服务器: TLS Finished + HTTP 请求

Note over 客户端,服务器: QUIC(1-RTT,重连 0-RTT)
客户端->>服务器: QUIC Initial + TLS ClientHello
服务器->>客户端: QUIC Handshake + TLS 完成
客户端->>服务器: HTTP/3 请求(可与握手同时)

3. 连接迁移

QUIC 使用连接 ID 而非 IP+端口 来标识连接,网络切换时无需重新建立连接:

场景:用户从 WiFi 切换到 4G

TCP/HTTP2:
WiFi 断开 → 连接中断 → 重新三次握手 → 重新 TLS 握手 → 继续传输

QUIC/HTTP3:
WiFi 断开 → IP 变化 → 连接 ID 不变 → 无缝继续传输

4. 改进的拥塞控制

QUIC 在用户空间实现拥塞控制,可以更快地迭代和优化算法,不受操作系统内核限制。

浏览器支持

目前主流浏览器和网站都已支持 HTTP/3:

# 检查网站是否支持 HTTP/3
curl -I --http3 https://www.cloudflare.com

# 或使用在线工具
# https://http3check.net

注意事项

  • HTTP/3 需要服务器和客户端都支持
  • 某些网络环境可能屏蔽 UDP,会自动降级到 HTTP/2
  • 调试工具支持还在完善中

版本对比总结

特性HTTP/1.0HTTP/1.1HTTP/2HTTP/3
发布年份1996199720152022
传输层TCPTCPTCPQUIC (UDP)
连接方式短连接持久连接多路复用多路复用
队头阻塞HTTP层无,TCP层有
头部格式文本文本二进制 + HPACK压缩二进制 + QPACK压缩
服务器推送不支持不支持支持支持
加密可选可选事实上必需强制
连接建立1-RTT1-RTT2-RTT (TCP+TLS)1-RTT / 0-RTT

演进路线图

graph LR
A[HTTP/1.0<br/>短连接] -->|解决连接复用| B[HTTP/1.1<br/>持久连接]
B -->|解决队头阻塞| C[HTTP/2<br/>多路复用]
C -->|解决TCP队头阻塞| D[HTTP/3<br/>QUIC]

延伸阅读