一问HTTP就露馅?别怪面试官让你挂...

5 阅读9分钟

一问HTTP就露馅?别怪面试官让你挂...

面试官:“请讲一下 HTTP 协议的演进过程。”

候选人:“从 1.0 到 1.1 加了长连接,到 2.0 加了多路复用,到 3.0 换了 QUIC。”

面试官:“好的,回去等通知吧。”

在高级前端或后端开发的面试中,HTTP 协议是必考题。但绝大多数候选人只能背出表面的特性变更,一旦深挖“为什么这么改”、“解决了什么根本痛点”,往往就会败下阵来。

本文从网络底层原理出发,结合实际工程场景,为你梳理一条硬核且逻辑严密的 HTTP 演进主线。

一、 奠基与野蛮生长:HTTP/0.9 与 HTTP/1.0

1. HTTP/0.9:极简主义的学术产物

1991 年诞生的 HTTP/0.9 纯粹是为了满足学术界传输纯文本 HTML 的需求。没有 Header,没有 Status Code,只有一个 GET 方法。客户端发请求,服务器回数据,连接断开。它根本不具备支撑现代复杂互联网应用的能力。

2. HTTP/1.0:协议规范的初步建立

1996 年发布的 HTTP/1.0 开始引入了我们熟知的很多基础概念:

  • 方法扩展:增加了 POST 和 HEAD(仅获取响应头,常用于检测资源是否存在或获取文件更新时间)。

  • Header 机制:引入了请求头和响应头,协议开始变得可扩展。

    • Content-Type:解决了“你发给我的是什么格式数据”的问题。
    • Cookie:在无状态的 HTTP 协议上硬生生砸出了一个状态管理的补丁。
    • Authorization:携带 JWT 等鉴权信息。
  • User-Agent 的历史遗留问题:UA 字段原本用于标识浏览器和设备信息。早期为了做 PC/移动端适配,服务端会根据 UA 返回不同页面。虽然现在前端有 @media query 和 rem 等响应式方案,但国内很多财大气粗的厂商依然分 PC 和 H5 两条业务线。更有趣的是,由于早期的“浏览器大战”,如今几乎所有浏览器的 UA 都以 Mozilla/5.0 开头,成了互联网史上最大的“伪装现场”。

3. 核心痛点:短连接的极大浪费

HTTP/1.0 默认是短连接。每个请求都要经历 TCP 三次握手和四次挥手。
在早期网页只有少量文本时,这没什么问题。但随着网页开始引入图片、CSS、JS,加载一个页面可能需要发起几十个请求。每次都重建 TCP 连接,导致极高的网络延迟和socket 资源浪费。

二、 补丁大师:HTTP/1.1 的辉煌与绝症

1997 年发布的 HTTP/1.1 是目前服役时间最长的版本,它打出了几个极其关键的补丁,但也留下了无法根治的绝症。

1. 长连接:解决建连开销

默认开启 Connection: Keep-Alive。一个 TCP 连接可以复用,大大减少了同域加载多个资源时的握手开销。

2. Host 头:拯救枯竭的 IP 地址(面试核心考点)

背景痛点:在 HTTP/1.0 中,一个 IP 地址的 80 端口只能绑定一个域名。随着网站激增,IPv4 地址根本不够用。
解决方案:HTTP/1.1 强制要求请求必须携带 Host 头。
底层逻辑:当请求到达服务器的某个 IP 时,Web 服务器(如 Nginx)会根据 Host 头的值,将请求反代到后端不同的业务进程。这就是虚拟主机技术的基石,让一台服务器、一个 IP 能够挂载成百上千个网站。
面试深坑:很多候选人搞不清 Host 头与端口号的关系。端口号属于 TCP 层,在发送 HTTP 报文之前就已经确定了连接去向;而 Host 头属于 HTTP 层,是在连接建立后用于应用层路由分发的标识。  Host 头不能决定数据去哪个端口,它只是在到达指定端口后,告诉服务器你要找哪个虚拟主机。
安全扩展:如果后端代码盲目信任并直接拼接 Host 头生成重置密码链接,就会引发 Host 头注入攻击,导致正规服务器生成指向钓鱼网站的链接。

3. 管道化与“应用层队头阻塞”绝症

为了解决长连接下必须“等上一个响应回来才能发下一个请求”的阻塞问题,HTTP/1.1 提出了 Pipeline(管道化) :允许客户端连续发送多个请求而不需要等待响应。

为什么这是个失败的补丁?
因为 HTTP/1.1 依然是明文文本传输,响应没有编号。多个请求在同一个 TCP 连接里就像排成的一字长蛇阵。如果第一个请求处理慢,或者响应在传输中丢失了一部分,后续哪怕已经处理好的响应也必须排队等待。前面的堵死,后面的全废。
这就是 HTTP/1.1 的绝症——应用层的队头阻塞。由于中间代理服务器往往无法正确处理管道化的乱序响应,现代浏览器(如 Chrome)实际上默认禁用了管道化。

4. 其他重要补丁

  • 分块传输(Chunked) :使用 Transfer-Encoding: chunked,服务器无需知道响应体总长度,边生成边发送,浏览器边收边解析。这是 SSE(Server-Sent Events)流式传输的底层支撑。
  • 方法扩展:增加了 OPTIONSPUTPATCHDELETE,标志着 RESTful 架构的成熟。

三、 换汤不换药:HTTP/2 的二进制革命

2015 年,为了彻底解决 HTTP/1.1 的效率问题,HTTP/2 诞生。它没有改变 HTTP 的语义(方法、状态码、URI),而是改变了传输格式

1. 二进制分帧:打破文本的限制

将原来的明文文本格式全部替换为二进制帧。帧是 HTTP/2 中最小通信单位,不仅 Header 被分帧,Body 也被分帧。

2. 多路复用:彻底解决应用层队头阻塞(面试核心)

这是 HTTP/2 最伟大的革新。
每个被分配到的请求/响应流都会被赋予一个唯一的 Stream ID(流标识符) 。在同一个 TCP 连接里,不同请求的二进制帧可以乱序交错发送。到达对端后,根据 Stream ID 重新组装。
跑车(小图片请求)可以直接超过大卡车(大 JSON 数据请求),互不影响。至此,HTTP 层面的队头阻塞被彻底消灭。浏览器不再需要针对同一个域名建立 6 个 TCP 连接,1 个连接即可搞定所有并发。

3. 其他优化

  • 头部压缩(HPACK) :1.1 中每次请求都要携带大量相同的 Header(如 Cookie、UA)。2.0 引入静态表和动态表,使用哈夫曼编码压缩,极大减少了头部体积。
  • 服务器推送:浏览器请求 HTML 时,服务器可以主动将 CSS 和 JS 帧推送到客户端缓存,减少了一次往返延迟(RTT)。(注:由于实现复杂且容易引发缓存混乱,目前部分现代浏览器如 Chrome 已计划移除 Server Push 功能)。

4. 遗留的致命伤:TCP 层的队头阻塞

HTTP/2 虽然在应用层实现了多路复用,但其底层依然基于 TCP
TCP 是一个严格保证顺序的字节流协议。如果在一个 TCP 连接中发生了丢包(哪怕这个包属于一个不重要的流),TCP 层会暂停向上层交付所有数据,直到丢失的包被重传成功。
现象:你在看 B 站,视频流和弹幕流复用同一个 TCP 连接。视频数据丢了一个包,导致弹幕数据虽然到了客户端,但也必须卡在 TCP 缓冲区里等待,直到视频包重传完毕。这就是 TCP 层队头阻塞带来的无奈。

四、 掀翻底座:HTTP/3 与 QUIC 的降维打击

既然 TCP 的刚性机制无法修改(中间设备太多,改不动 TCP 协议栈),Google 选择掀翻桌子:抛弃 TCP,基于 UDP 重写传输层逻辑。  这就是 HTTP/3 的核心:HTTP/3 = HTTP/2 语义 + QUIC 协议

1. 彻底解决队头阻塞

QUIC 基于 UDP(无连接、不可靠),在用户态自己实现了可靠传输机制。在 QUIC 中,不同的 Stream 是相互独立的。如果 Stream 1 丢包了,只影响 Stream 1 的重传,Stream 2 和 Stream 3 的数据可以毫无阻碍地交给上层应用。真正实现了从传输层到应用层的全链路无阻塞。

2. 极致的建连速度:0-RTT

在 HTTP/2 (TLS 1.2) 时代,建立安全连接需要 TCP 三次握手 (1-RTT) + TLS 握手 (2-RTT),总共 3-RTT。
QUIC 直接将 TLS 1.3 加密内嵌到了传输协议中:

  • 首次连接:将 TCP 和 TLS 的参数合并,只需 1-RTT。
  • 重连:客户端缓存了服务端的配置参数,甚至可以实现 0-RTT,发送数据的同时携带连接建立信息,瞬间建连。

3. 连接迁移:无缝切换网络

TCP 通过“四元组(源IP、源端口、目的IP、目的端口)”标识一个连接。当手机从 WiFi 切换到 4G 时,本地 IP 改变,TCP 连接必然断开,导致页面白屏重新加载。
QUIC 废弃了四元组,使用一个随机生成的 Connection ID 来标识连接。无论底层的 IP 和端口怎么变,只要携带正确的 Connection ID,服务器就能识别这是同一个连接,实现了无缝的网络切换。

总结:面试如何高情商回答?

如果在面试中遇到这个问题,建议采用 “痛点驱动演进” 的逻辑线来回答:

“HTTP 的演进史,本质上是一部对抗阻塞、提升传输效率的历史。

  1. HTTP/1.0 时代的主要矛盾是建连开销大,于是 1.1 引入了长连接。
  2. 1.1 引入长连接后,暴露出应用层队头阻塞(管道化失败)和 IP 资源枯竭(通过 Host 头解决虚拟主机问题)的矛盾。
  3. 为了解决 1.1 的应用层阻塞,HTTP/2 通过二进制分帧和 Stream ID 实现了多路复用,并引入头部压缩。但 2.0 的地基依然是 TCP,导致了无法逾越的 TCP 层队头阻塞
  4. 最终,HTTP/3 抛弃 TCP,基于 UDP 构建 QUIC 协议,通过独立的 Stream 彻底解决丢包阻塞,通过内嵌 TLS 实现 0-RTT 建连,通过 Connection ID 实现连接迁移,完成了传输层的终极进化。”

记住这条从“应用层阻塞”打到“传输层阻塞”的逻辑链,你就掌握了 HTTP 演进的核心灵魂。