Skip to content

HTTP 版本演进史

基于 HTTP 现代标准 · 核于 2026-06

速查

  • HTTP/0.9(1991):单行协议,只有 GET,无头部、无状态码、无版本号,只能传 HTML。
  • HTTP/1.0(RFC 1945,1996):引入版本号、状态码、HTTP 头部、Content-Type(可传任意类型);默认非持久连接,每个资源一条新 TCP 连接。
  • HTTP/1.1(RFC 2068/1997 → 9110~9112/2022)持久连接成默认Host 头必填(支持虚拟主机)、管线化(默认关)、分块传输 Transfer-Encoding: chunked、缓存控制完善、范围请求 Range
  • 2022 标准重组:RFC 9110 = 语义(跨版本共用)、9111 = 缓存、9112 = HTTP/1.1 报文、9113 = HTTP/2、9114 = HTTP/3;HTTP/1.1 升格为 Internet Standard(STD 97)。
  • 三大版本并存不互相废弃:1.1 / 2 / 3 各有适用场景,共享同一套语义(方法、状态码、头部含义)。
  • 演进驱动力:网页从单文档变为「HTML + 几十个 CSS/JS/图片」,串行请求 + 反复建连成为性能瓶颈,倒逼连接复用与并发。
  • 版本号规则:首位(主版本)决定报文「线格式」,次位决定该主版本下最高兼容的次版本。
  • 协商方式:明文场景用 Upgrade 头(h2c,实践罕见);HTTPS 用 ALPN 在 TLS 握手内一次性选定(h2 的主流路径);Alt-Svc 头让服务端广播「我还支持 HTTP/3」,客户端后续升级。
  • 为何还要 2 与 3:1.1 的队头阻塞与建连开销治标不治本——HTTP/2 用二进制分帧 + 多路复用,HTTP/3 换 QUIC/UDP 消除 TCP 层队头阻塞(细节见后续页)。

一切的起点:HTTP 的设计哲学

HTTP(HyperText Transfer Protocol)诞生于 1989-1991 年,由 Tim Berners-Lee 在 CERN 提出。它从第一天起就锚定了三条几乎从未改变的设计原则——理解它们,才能看懂后续每一次演进「改了什么、没改什么」:

  • 文本协议(人类可读):请求与响应是纯文本,开发者用 telnet 就能手敲一个请求。这一特性极大降低了调试与互操作成本,一直保留到 HTTP/1.1;HTTP/2 起改为二进制,是性能取舍的结果。
  • 请求-响应模型:客户端发一个请求、服务端回一个响应,一来一回。客户端根据响应的状态码与内容决定下一步。
  • 无状态(stateless):RFC 9110 的原话是「每条请求报文的语义都能被独立理解,连接与其上报文的关系不影响报文的解释」。服务端默认不记得上一个请求是谁发的——状态靠 Cookie、Token 等机制「额外补」上去。

语义 vs 语法:贯穿全史的一条暗线

2022 年的 RFC 把 HTTP 语义(方法、状态码、头部的含义)和各版本的线格式语法彻底拆开(RFC 9110 管语义,9112/9113/9114 各管一个版本的报文格式)。这解释了一个关键事实:从 1.1 到 2 再到 3,变的只是「字节怎么排在网络上」,GET200Content-Type 这些语义始终一致。 前端写代码时几乎感知不到版本切换,正源于此。

HTTP/0.9(1991):单行协议

最初的 HTTP 简单到没有版本号(「0.9」是后来追认的)。一个请求就是一行:

GET /my-page.html

响应直接就是 HTML 文档本身,传完即断开连接:

html
<html>
  A text-only web page
</html>

它的「不存在」清单比「存在」清单更长:只有 GET 一个方法,没有 HTTP 头部,没有状态码,没有版本号,只能传 HTML。出错了也只能回一段给人看的 HTML,程序无从判断成败。

对前端意味着什么

0.9 是「能跑就行」的最小可用协议。它教给我们的是底线:一个超文本传输协议,最少只需要「指定资源 + 取回内容」。后续所有头部、状态码、缓存都是在这个内核上叠加的可扩展性。

HTTP/1.0(1996,RFC 1945):引入可扩展性

随着 Mosaic 等浏览器普及、网页要嵌图片,0.9 的窟窿很快被补上。HTTP/1.0 一次性引入了今天我们习以为常的几大件:

http
GET /my-page.html HTTP/1.0
User-Agent: NCSA_Mosaic/2.0 (Windows 3.1)

HTTP/1.0 200 OK
Date: Tue, 15 Nov 1994 08:12:31 GMT
Server: CERN/3.0 libwww/2.17
Content-Type: text/html

<HTML>
A page with an image
  <IMG SRC="/my-image.gif">
</HTML>

对比 0.9,新增了四样关键东西:

  • 版本号:请求行末尾追加 HTTP/1.0,从此协议可以「声明自己是哪一版」。
  • 状态码:响应首行 200 OK,程序终于能判断请求成败(404500……)。
  • HTTP 头部:请求和响应都能携带元数据(User-AgentServerDate……),可扩展性的真正入口。
  • Content-Type:声明正文类型,从此能传图片、视频、JSON——不再局限于 HTML。

1.0 的致命短板:默认非持久连接

HTTP/1.0 默认每个资源都新建一条 TCP 连接,用完即关。一个嵌了图片的页面就要建连两次(HTML 一次、图片一次)。当时网页还简单尚可忍受,但随着页面里 CSS/JS/图片越来越多,反复的 TCP 三次握手 + 慢启动成了肉眼可见的性能黑洞。1.0 时期可通过非标准的 Connection: keep-alive 实验性复用连接,但并未标准化。

HTTP/1.1(1997 至今):标准化与持久化

HTTP/1.1 在 1.0 发布仅几个月后就到来(RFC 2068, 1997),随后经历 RFC 2616(1999)、RFC 7230-7235(2014)多轮修订,最终在 2022 年由 RFC 9110-9112 重组并升格为 Internet Standard(STD 97)。它是迄今生命周期最长、最稳定的 HTTP 版本,至今仍是大量服务的兜底协议。

相比 1.0,1.1 解决的核心是「连接效率」与「一机多站」:

特性HTTP/1.0HTTP/1.1
连接默认非持久(一资源一连接)默认持久(一连接复用多请求)
Host必填(支持虚拟主机)
管线化支持(但浏览器默认关闭)
分块传输Transfer-Encoding: chunked
缓存控制简单(Expires完善(Cache-ControlETag
范围请求Range / 206 Partial Content

持久连接成为默认

1.1 默认 Connection: keep-alive:一条 TCP 连接建立后保持开启,连续承载多个请求-响应,省掉了反复握手与慢启动的代价。服务端可用 Keep-Alive: timeout=5, max=100 告知保活策略。这是 1.1 最大的性能红利。

http
GET /en-US/docs/ HTTP/1.1
Host: developer.mozilla.org
Accept-Encoding: gzip, deflate, br
Connection: keep-alive

Host 头:必填背后是「一台服务器托管多个域名」

1.1 强制每个请求带 Host 头,看似小改动,实则是**虚拟主机(virtual hosting)**的基石:一个 IP 上能跑成百上千个网站,服务器靠 Host 区分该返回哪个站点的内容。没有它,现代共享主机与 CDN 架构无从谈起。

管线化与它埋下的坑

1.1 提出管线化(pipelining):在同一持久连接上,不等前一个响应回来就连续发后续请求,理论上能进一步降延迟。但它有硬伤——响应必须按请求顺序返回,一旦队首响应慢,后面全被堵住(队头阻塞);叠加大量代理实现有 bug,浏览器最终默认关闭管线化。这个痛点正是 HTTP/2 多路复用要根治的(详见下一页)。

其它完善

  • 分块传输编码Transfer-Encoding: chunked 让服务端不必预先知道正文总长,可边生成边发送(流式响应、动态内容)。
  • 缓存控制Cache-ControlETagLast-Modified 等让缓存策略精细可控。
  • 范围请求Range 头 + 206 Partial Content,支持断点续传与视频拖拽播放。

时间线与驱动力

版本年份关键 RFC一句话特征
0.91991单行 GET,无头部无状态码
1.01996RFC 1945头部 / 状态码 / Content-Type;默认非持久
1.11997~2068→2616→7230~7235→9110~9112持久连接、Host 必填、分块、缓存、范围请求
22015RFC 9113(前身 Google SPDY)二进制分帧、多路复用、头部压缩
32022RFC 9114基于 QUIC/UDP,消除 TCP 层队头阻塞

贯穿这条时间线的驱动力只有一个词——复杂度。网页从「一篇文档」长成「一个 HTML + 几十上百个 CSS/JS/字体/图片」的工程,串行请求与反复建连的成本被急剧放大。每一次大版本升级,本质都在回答同一个问题:如何在一条(或一组)连接上,更快、更并发地搬运越来越多的资源。

为何还需要 HTTP/2 与 HTTP/3?

1.1 的持久连接缓解了「建连贵」,但没解决「同一连接上请求只能排队」——这就是队头阻塞。浏览器靠「每个域名开 6 条连接」+「域名分片」硬扛,治标不治本(域名分片在 HTTP/2 下反而有害)。于是:HTTP/2 用二进制分帧 + 多路复用,让一条 TCP 连接真正并发跑多个请求;HTTP/3 更进一步把传输层从 TCP 换成基于 UDP 的 QUIC,连 TCP 层的队头阻塞也一并消除。技术细节分别见本叶第 3-5 页,这里只引出脉络。

版本如何协商

客户端与服务端怎么「商量」用哪个版本?三条主流机制:

  • Upgrade 头(明文升级):HTTP/1.1 请求带 Upgrade: h2c + Connection: Upgrade,请求在明文 TCP 上升级到 HTTP/2(即 h2c)。实践中几乎不用——主流浏览器只在加密连接上启用 HTTP/2。
  • ALPN over TLS(主流):在 TLS 握手的 ClientHello 里,客户端用 ALPN 扩展列出支持的协议(如 h2http/1.1),服务端选定其一,在握手内一次性完成协商,零额外往返。这是 HTTPS 下 HTTP/2 的标准协商方式。
  • Alt-Svc 头(指向新版本):服务端在响应里广播 Alt-Svc: h3=":443",告诉客户端「同一服务也可用 HTTP/3 访问」。客户端记下后,后续请求改走 HTTP/3。由于 HTTP/3 跑在 UDP 上、无法靠 TLS-ALPN 直接协商,Alt-Svc(及后来的 HTTPS DNS 记录)是其主要发现路径。

版本号语义别搞混

RFC 9110 规定:版本号首位(主版本)代表报文线格式,次位代表该主版本下发送方兼容的最高次版本。所以「HTTP/2」「HTTP/3」严格说是新的主版本(新线格式),而非 1.1 的小升级——但它们共享 RFC 9110 的同一套语义。

小结

HTTP 三十余年的演进是一条清晰的主线:内核(请求-响应、无状态、统一语义)几乎不变,外层不断为「性能」与「可扩展性」加码。0.9 给出最小可用内核;1.0 补上头部、状态码、内容类型,让协议可扩展;1.1 用持久连接、Host 头、分块与缓存把「连接效率」和「一机多站」做到位,稳坐主力近三十年。但 1.1 的请求排队(队头阻塞)与建连开销,靠开多连接、域名分片只能硬扛,于是 HTTP/2、HTTP/3 接力登场。理解这条历史脉络,是看懂后续每一项性能优化「为何存在」的前提。

下一页我们把放大镜对准 1.1 的痛点,看看队头阻塞究竟卡在哪、浏览器又用了哪些权宜之计:HTTP/1.1 瓶颈与队头阻塞