第 18 章 HTTP 演进与 HTTPS
本章回答:为什么 HTTP/1.1 必须靠"多开连接"续命,而 HTTPS 又凭什么在 1~2 个 RTT 内谈完密钥。
学习目标
读完本章,你应当能够:
- 推导 HTTP/1.1 队头阻塞(head-of-line blocking, HOL)的成因,说明"浏览器 6 连接"与"域名分片"是同一原因的两种表现;
- 区分应用层 HOL 与 TCP 层 HOL,指出后者为什么是 HTTP/3 的唯一动机;
- 画出 TLS 1.2(2-RTT)与 TLS 1.3(1-RTT、0-RTT)握手时序,逐条说出 TLS 1.3 砍掉了什么、为什么能省一个 RTT;
- 判断
301/302/307/308、401/403、502/504的语义差异,并指出"是否允许改变请求方法"这一判定轴; - 解释
SNI、HSTS、OCSP Stapling各自解决的具体问题,而不是背缩写。
前置知识检查
| 你需要知道 | 在哪一节补齐 |
|---|---|
TCP 三次握手、TIME_WAIT、半连接队列 | 见第 16 章 16.1~16.4 节 |
cwnd/ssthresh 演化、快速重传与 SACK | 见第 17 章 17.4~17.6 节 |
| MTU/MSS 与分层封装解封装 | 见第 15 章 15.3 节 |
| TLS 记录层与密码套件命名读法 | 本章 18.7 节给出最小必要背景 |
18.1 HTTP 的定位与无状态性
HTTP(HyperText Transfer Protocol)是应用层的请求-响应协议,只约定报文的语义与格式,不约定连接怎么建、怎么复用、怎么保证可靠——这些全部下推给传输层。本章的版本演进都发生在这条分界线的夹缝里。
**无状态(stateless)**要说得比课本精确:服务端处理一个请求时,不依赖此前任何请求留下的、协议层自带的状态,每个请求必须自带全部上下文。好处是工程性的:
| 好处 | 机制上的原因 |
|---|---|
| 服务端可任意水平扩缩 | 任一请求可落到任意实例,无需 sticky 路由 |
| 单点故障不影响会话 | 实例崩溃不丢协议层状态,天然可重启 |
| 中间层可缓存、可重放、可重试 | 请求自含语义,代理无需回放历史 |
| 协议实现简单 | 无需在服务端维护每连接的会话状态机 |
代价是应用状态必须外置。Cookie(RFC 6265)是客户端存储加自动回带(按域、路径、Max-Age、Secure、HttpOnly、SameSite 约束),本身不含语义;Session 才是真正的状态,放在服务端内存/Redis/DB,Cookie 里只放一个不可猜测的 ID。于是无状态协议上重新引入了状态,代价立刻显现:会话粘滞(sticky session)、会话存储成为新单点、跨机房同步延迟、SameSite 与 CSRF 的耦合。
重要
面试标准落点:无状态是协议设计的正确取向,Cookie/Session 是应用层补偿,代价是"服务端又变成有状态了"。 只说"HTTP 无状态所以用 Cookie"是半句话,面试官会追问"Session 存哪里、跨机房怎么办"。
18.1.1 HTTP 报文格式
报文格式必须能默画,因为它解释了后面几乎所有演进:HTTP/1.1 的每个请求都要在同一条字节流上与前后请求首尾相接,格式本身携带了边界信息。
| |
四个部分:起始行(start-line)+ 头部字段 + 空行(CRLF)+ 正文(body)。三个会被追问的点:
- 空行是格式的一部分,不是排版。没有它接收方无法知道头部在哪结束;这也是头部能逐行解析、而正文只能用长度或分块定界的原因。
- 正文定界有三条路:
Content-Length定长、Transfer-Encoding: chunked分块(长度未知时)、由连接关闭隐式定界。第三条正是流水线无法工作的直接原因之一。 Host是 HTTP/1.1 的强制头,它是"一个 IP 承载多域名"的基础;HTTPS 里同样的需求由SNI在 TLS 层完成(见 18.9.3)。
18.2 HTTP/1.1:持久连接、连接池与管线化
18.2.1 持久连接与连接池
HTTP/1.0 默认每请求一条 TCP 连接,一次页面几十个资源就要几十次"三次握手 + 慢启动"。成本是实打实的:同机房 RTT 约 0.10.5 ms,跨大洲 RTT 约 150 ms,而 cwnd 只从 110 个 MSS 起跳。
HTTP/1.1 默认持久连接(persistent connection)(HTTP/1.1 中 Connection: keep-alive 其实冗余,缺省即持久;Connection: close 才是否定)。工程上真正的重点是连接池:客户端按"每源 6 条"限流;服务端把建连成本摊销到每 N 个请求;而 NAT 网关、四层负载均衡通常有 60~900 s 的空闲回收,于是 keepalive_timeout 必须小于上游的回收时间,否则会出现"连接看着是活的、一写就 RST"的半开(half-open)现象——这是线上最常见的 502 来源之一。
提示
keepalive_timeout 的取值原则是"比谁都短":让服务端成为主动关闭方——只有主动关闭方才走完整 FIN 流程,而不是撞上对端已消失的 RST(TIME_WAIT 的归属见第 16 章 16.5 节)。
18.2.2 管线化为什么在实践中失败
**管线化(pipelining)**的设想很自然:既连接是持久的,客户端为什么不能一次把请求 A、B、C 全写出去,再按顺序读响应?三个原因必须分开讲:
- 响应必须严格按请求顺序返回(RFC 7230 要求)。服务端不能先回 B 再回 A,因为响应里没有请求 ID;于是 A 慢(比如一次重查询),B、C 早就处理完了也只能等——这就是应用层队头阻塞。
- 中间设备兼容性:早期代理对"一个连接里连续到达多个请求"处理不一致,有的直接关连接,有的串行化,有的缓存出错。协议允许不等于部署环境支持。
- 出错后无法确定应答归属:一旦服务端提前关连接或某个响应出错,客户端无法判断剩余字节属于哪个请求,只能整条连接作废重发。这是"缺少请求标识符"的直接后果,也解释了 HTTP/2 为什么必须引入
Stream ID。
| |
18.3 队头阻塞:应用层 vs 传输层
“队头阻塞"被过度使用,面试最容易被抓的是不区分层次。
HTTP/1.1 的 HOL 在应用层:同一连接上请求-响应严格串行,它甚至不需要管线化就已经存在——不开管线化时客户端必须等上一个响应读完才发下一个,串行度 100%。绕过的办法是开更多连接:浏览器对每个源(origin)默认限制 6 条并发连接(HTTP/1.1 时代的事实标准),并发度从 1 变成 6。这是绕过而非解决:第 7 个请求仍要排队。
**域名分片(domain sharding)**正是顺着这个限制做的工程妥协:把 static.example.com 拆成 s1..s2.example.com,因为限流单位是"源”,2 个域名就能拿到 2 × 6 = 12 条连接。代价同样具体:
| 代价 | 机制 |
|---|---|
| 多出 DNS 解析 | 每个新域名一次解析,冷启动额外 RTT(见第 19 章 19.2 节) |
| 多出 TLS 握手 | 每域名独立证书与握手,HTTPS 下代价翻倍 |
| 连接池碎片化 | 每域名连接利用率下降,空闲连接仍占内存与 fd |
| 破坏拥塞控制效率 | 24 条连接各自从慢启动爬,总吞吐与丢包敏感度都更差 |
| 与 HTTP/2 直接冲突 | 分片会破坏单连接多路复用前提,属负优化 |
小心
把"HTTP/1.1 的队头阻塞"和"HTTP/2 的队头阻塞"混为一谈,是这章最容易把面试答崩的地方。前者是应用层串行、可用多连接绕过;后者是TCP 字节流的有序交付、无法用多连接绕过(HTTP/2 的本意就是不许多开连接)。层次说错,HTTP/3 的动机就全错了。
18.4 HTTP/2:二进制分帧与多路复用
HTTP/2(RFC 7540)没有改语义(方法、状态码、头部字段含义全部沿用 HTTP/1.1),只改了两件事:报文怎么编码、报文怎么在连接上复用。
18.4.1 二进制分帧层
HTTP/2 把报文切成帧(frame),每帧带 9 字节固定头;Length 24 位决定单帧载荷上限,默认 SETTINGS_MAX_FRAME_SIZE 是 16 384(2¹⁴)字节;Stream Identifier 31 位是复用的全部秘密——接收方靠它把交错的帧归位到各自的响应上,这正是 HTTP/1.1 缺的那个"请求标识符"。
| |
二进制而非文本的收益有两块:解析不必再处理文本边界与大小写变体;帧可以在同一连接上交错(interleave),因为边界判定不再依赖"行"和"顺序"。
18.4.2 流、消息与帧的三层关系
必考的结构题:
| |
关键在于这个等待是 RTT 量级的:同机房 RTT 约 0.1~0.5 ms 尚可忍,跨大洲 RTT 约 150 ms 时一次丢包就让所有流各停 150 ms 以上。丢包率越高、RTT 越大,HTTP/2 相对 HTTP/1.1 越吃亏——HTTP/1.1 时代一次丢包只影响其中一条连接上的一个请求,另外 5 条照常前进。
重要
一句话标准答案:HTTP/2 用一条 TCP 连接承载所有流,TCP 的按序交付使任一报文段丢失阻塞全部流;多开连接救不了,因为多路复用与 HPACK 的收益都建立在"单连接"上。 要解决它,必须把"流的独立重传"下沉到传输层——这就是 QUIC。
18.6 HTTP/3 与 QUIC
QUIC(RFC 9000)是跑在 UDP 之上的用户态传输协议,HTTP/3(RFC 9114)就是把 HTTP 语义映射到 QUIC 上。三个问题必须答清。
18.6.1 为什么基于 UDP
不是因为 UDP 更好,而是因为中间设备僵化(ossification):大量 NAT、防火墙、负载均衡只认 TCP/UDP 的既有行为,新传输协议若占用新的 IP 协议号(如 SCTP)会被直接丢弃;封装成 UDP 载荷则能以很高概率穿过这些设备。既然用 UDP,TCP 提供过的一切都得在用户态重建,而重建带来两个副产品:可快速迭代(不必等内核升级与操作系统普及,随浏览器/服务更新即可上线新特性),以及连接内的流独立可控。
18.6.2 QUIC 的流与拥塞控制
| 能力 | TCP + TLS(HTTP/2 栈) | QUIC(HTTP/3 栈) |
|---|---|---|
| 丢包影响范围 | 阻塞整条连接的所有流 | 只阻塞该流,其他流照常交付 |
| 握手 | TCP 三次握手 + TLS 握手串行叠加 | 传输握手与 TLS 握手合并 |
| 加密范围 | TLS 只加密应用数据,TCP 头明文 | 除少数字段外连传输层头也加密 |
| 连接标识 | 四元组 (源IP, 源端口, 目的IP, 目的端口) | Connection ID |
| 拥塞控制位置 | 内核 | 用户态,可插拔 |
| 队头阻塞层次 | 应用层已解决,传输层仍在 | 跨流 HOL 消除 |
QUIC 内部有独立的流级流控与连接级流控,流的字节偏移独立。但要说准一个点:同一流内部仍然有序交付,所以 QUIC 消除的是"跨流 HOL",不是"流内 HOL"。头部压缩在 HTTP/3 中换成 QPACK(RFC 9204),专门为"跨流乱序到达"重新设计——这正是 HPACK 那种"一个头部帧丢了就卡住所有流"的机制不能在 QUIC 上直接复用的原因。
18.6.3 0-RTT、重放风险与连接迁移
1-RTT 建连:首次连接时 QUIC 的传输握手(含 TLS 1.3 密钥协商)在一个往返内完成;而 TCP + TLS 1.2 通常要 3 个往返(1 建连 + 2 握手)。
0-RTT 与会话复用:客户端用上次会话的票据(session ticket)直接加密并发送应用数据,做到"第一个包就带业务数据"。代价是 0-RTT 数据没有前向保密且可被重放——攻击者抓到这段密文就能重复发送。因此 0-RTT 只适用于幂等且无副作用的请求(如
GET静态资源),绝不能用于支付、提交订单;服务端还必须自行做重放防护(一次性票据、时间窗、请求去重)。连接迁移(connection migration):
Connection ID取代四元组作为连接身份,所以手机从 Wi-Fi 切到 4G、或 NAT 重新映射端口时连接不中断,也不必重新握手。弱网优势的量化直觉:设 RTT = 150 ms、丢包率 2%。HTTP/2 下每次丢包让所有并发流各等一个 RTT 量级;HTTP/3 下只有丢包的那个流等。这就是"弱网下 HTTP/3 的 P99 明显好于 HTTP/2"的来源——不是带宽更高,而是尾延迟抖动更小。
警告
常见错误回答:“QUIC 用 UDP 所以更快,因为 UDP 不握手、不重传。“QUIC 自己实现了可靠性、重传、拥塞控制与流控,比 TCP 更复杂,不是退回不可靠。另一个错误是把 0-RTT 当纯收益——0-RTT 有重放风险,必须限制在幂等请求。
18.7 HTTPS 与 TLS 的位置:四种密码学原语的分工
TLS(Transport Layer Security)位于传输层之上、应用层之下,给应用层留下一个"看起来像可靠字节流、但已加密并校验过完整性"的接口,因此 HTTP 本身完全不必改就能被保护——这也是 HTTPS 只是 “HTTP over TLS” 的原因。四种原语解决四个不同问题,最容易混:
| 原语 | 解决什么 | 在 TLS 中的用途 | 性能量级(相对) |
|---|---|---|---|
| 对称加密(symmetric) | 大批量数据的机密性 | AES-GCM/ChaCha20-Poly1305 加密应用数据 | 极快,有硬件加速时可达 GB/s |
| 非对称加密(asymmetric) | 密钥在不安全信道上的分发、身份鉴别 | ECDHE 密钥交换、RSA/ECDSA 签名 | 慢 2~3 个数量级 |
| 消息认证码(MAC/AEAD) | 完整性:数据被改过没有、来源是否持有密钥 | GCM 的认证标签、TLS 1.2 CBC 套件的 HMAC | 快,通常与加密一起 |
| 数字签名(digital signature) | 不可否认的身份:证明消息确由持私钥者发出 | 证书签名、CertificateVerify、ServerKeyExchange 签名 | 慢,但只用几次 |
为什么非对称只用来协商密钥、数据用对称传:非对称是模幂/椭圆曲线点乘这类大数运算,比 AES 慢 2~3 个数量级,一次响应几百 KB 用 RSA 逐块加密不可行。所以握手阶段用非对称(或基于 DH 的密钥交换)只协商出一个小秘密,之后全部用对称密钥。
注意
面试官会追问的下一层:“对称密钥不能直接用非对称加密传过去吗?” TLS 1.2 的 RSA 密钥交换套件确实如此——客户端生成预主密钥,用服务端 RSA 公钥加密后发过去。TLS 1.3 删掉了静态 RSA 密钥交换,全部改为 (EC)DHE 的前向保密(forward secrecy):即使服务端私钥日后泄露,也无法解密此前录下的流量,因为会话密钥由双方临时私钥共同决定、用完即弃。
18.8 TLS 1.2 与 TLS 1.3 握手对比
18.8.1 完整握手时序
| |
为什么 TLS 1.3 能少一个 RTT:TLS 1.2 的客户端必须先看到服务端的 ServerHello 才知道用哪个组,DH 公钥只能放到第 2 个往返;TLS 1.3 让客户端在 ClientHello 里先猜一个组并带上 key_share(通常是 X25519),服务端若接受该组立刻就能算出共享秘密,于是第 1 个往返里服务端就能把证书与 Finished 一起加密发回。猜错时服务端回 HelloRetryRequest,退回 2-RTT——这是 TLS 1.3 唯一的额外往返。
TLS 1.3 砍掉了什么:
| 被砍掉的东西 | 砍掉的原因 |
|---|---|
| 静态 RSA 密钥交换 | 无前向保密:服务端私钥泄露即可解密历史流量 |
| 重协商(renegotiation) | 历史漏洞多(重协商前缀攻击),且与 PSK/0-RTT 模型冲突 |
| 压缩(compression) | CRIME 类攻击利用压缩率泄露明文 |
CBC 模式套件 | 填充预言(padding oracle)类攻击反复出现,只保留 AEAD |
| 自定义 DH 参数 | 小群/非安全群导致降级;只允许几个标准命名组 |
ChangeCipherSpec | 握手状态可隐式推导,少一个可被利用的消息 |
出口级弱套件、RC4、SHA-1 | 已知不安全 |
TLS 1.3 还把证书本身也加密(放在 EncryptedExtensions 之后),顺带修掉了"证书明文暴露访问域名"的隐私问题。
18.8.2 会话复用与 0-RTT
| 模式 | 往返数 | 机制 | 风险 |
|---|---|---|---|
| TLS 1.2 session ID / ticket | 1-RTT | 服务端缓存或客户端持票据,复用主密钥 | 票据泄露即可解密;无前向保密 |
TLS 1.3 PSK 复用(pre_shared_key) | 1-RTT | 票据承载 PSK,跳过证书与密钥交换 | 依赖票据保密性 |
| TLS 1.3 0-RTT(early data) | 0-RTT | 第一个包就用 PSK 加密并携带应用数据 | 可重放;0-RTT 数据无前向保密 |
| |
0-RTT 的适用边界:只允许幂等、无副作用、可重放的请求(服务端应拒绝非 GET/HEAD 的 0-RTT,或只用于静态资源,并做一次性票据校验)。把"0-RTT 快"和"0-RTT 可被重放"一起说,才完整。
18.9 证书与验证链
18.9.1 证书里有什么
X.509 证书的关键字段:使用者(Subject,含 CN 与 SAN)、颁发者(Issuer)、有效期、公钥、签名算法、签名值及若干扩展。两个必考点:SAN(Subject Alternative Name)已取代 CN,现代实现只认 SAN,多域名靠 SAN 列表或通配符 *.example.com(只匹配一级子域);另外证书本身不加密通信,它只绑定"公钥 ↔ 域名/组织”,并用 CA 签名让任何人可验证这一绑定,真正的加密在握手后由对称密钥完成。
18.9.2 CA 层级与链式验证
| |
服务端必须发送完整链(叶子 + 中间 CA),但不发根证书(根在客户端本地已有);“链不完整(missing intermediate)“是线上最常见的 TLS 报错之一,浏览器可能靠 AIA 扩展自动补链,但移动 SDK、Java 客户端、curl 等往往不会,于是直接失败。
18.9.3 SNI、吊销与 HSTS
SNI(Server Name Indication)是 ClientHello 中的扩展,明文携带客户端想访问的域名,解决一个 IP 上部署多个 HTTPS 域名的问题:没有 SNI,服务端在握手时必须先决定出示哪张证书,而此时它还不知道客户端要访问哪个域名(HTTP 的 Host 还没发过来,且整个 HTTP 层都在加密之后)。SNI 的隐私缺陷也很实际:它是明文,暴露你要访问的域名;ECH(Encrypted Client Hello)就是在补这个洞。
| 吊销机制 | 做法 | 问题 |
|---|---|---|
| CRL | 定期下载 CA 的全量吊销列表 | 列表可能很大、更新有延迟,客户端可能干脆不检查(fail-open) |
| OCSP | 客户端向 CA 实时查询单张证书状态 | 多一次网络往返;CA 可见用户访问行为;CA 不可用时可用性下降 |
| OCSP Stapling | 服务端定期取回带时间戳的 OCSP 响应,握手时"钉"给客户端 | 省掉客户端往返并保护隐私;缺点是响应有有效期,服务端必须能成功刷新 |
HSTS(HTTP Strict Transport Security)用 Strict-Transport-Security: max-age=31536000; includeSubDomains; preload 告诉浏览器"此后一段时间内对我只用 HTTPS”。它解决降级攻击:用户输入 example.com 或点某个 http:// 链接时首次请求是明文的,中间人可劫持并阻止跳转(SSL Strip);HSTS 让浏览器在本地就把该域名的 http:// 改写成 https://,不发明文请求。preload 进一步把域名硬编码进浏览器,补上"第一次访问还没收到 HSTS 头"的窗口。
中间人攻击(MITM)与自签证书:MITM 的本质是"攻击者用自己的证书冒充服务端”。若客户端信任库里有攻击者的根证书(企业内网代理、被装过根证书的设备),验证就会通过——TLS 无法防御"客户端自己信任了错误根”。自签证书靠同一机制失效:验证链找不到本地信任锚,于是浏览器报警;用 curl -k 跳过校验"解决",在生产环境等于放弃身份鉴别。
小心
不要说"HSTS 加密了请求"或"OCSP Stapling 帮客户端省了握手"。HSTS 只做强制跳转与降级防护(前提是已有 HTTPS);OCSP Stapling 省的是吊销状态查询的那次往返,与握手的密码学无关。
18.10 常见状态码的语义与陷阱
状态码分 5 类(1xx 信息、2xx 成功、3xx 重定向、4xx 客户端错、5xx 服务端错),本科都讲过;这里只写面试会追问语义差异的部分。
18.10.1 重定向:永久/临时 × 是否允许改方法
| 状态码 | 语义 | 方法是否可变 | 缓存 | 典型用途 |
|---|---|---|---|---|
301 Moved Permanently | 永久移动 | 历史实现允许改成 GET | 可缓存(浏览器常长期缓存) | 换域名、http→https |
302 Found | 临时移动 | 历史实现允许改成 GET | 不缓存 | 登录后跳首页、临时活动页 |
303 See Other | 临时,且明确要求用 GET 取 | 必须改为 GET | 不缓存 | POST 提交后跳转(PRG 模式) |
307 Temporary Redirect | 临时移动 | 必须保持原方法 | 不缓存 | 需要保方法的临时跳转 |
308 Permanent Redirect | 永久移动 | 必须保持原方法 | 可缓存 | 需要保方法的永久跳转 |
记忆骨架:301/302 是老规范留下的模糊地带,307/308 是把 302/301 的语义说清楚的版本,判定轴只有一个——是否允许把 POST 改成 GET;303 则反过来,明确要求改成 GET。
警告
陷阱在 301/302 的历史行为:RFC 7231 允许(但不强制)客户端收到 301/302 后把非 GET 方法改写为 GET。所以 POST 到 301 的接口很可能被浏览器以 GET 重放,而 GET 没有 body——参数丢失、语义错误,且极难排查。要保方法就用 307/308。
18.10.2 认证与授权、网关错误的归属
401 Unauthorized(名字起错了,语义是 Unauthenticated):你没有证明你是谁,响应必须带WWW-Authenticate头告诉客户端如何认证。403 Forbidden(语义是 Unauthorized):我们知道你是谁,但你无权访问;再认证一次也没用。一句话区分:401是"去登录",403是"登录了也没用"。502 Bad Gateway:网关从上游拿到了无效响应(连上了但响应不合法、上游拒绝、连接被 RST),典型如后端进程崩溃、协议错乱、keepalive半开连接。504 Gateway Timeout:网关没等到上游响应就超时,典型如后端慢查询、下游级联超时。一句话区分:502是"上游的回答不对",504是"上游根本没回答",排查方向分别是后端进程/协议 与 超时链路/下游耗时。
18.10.3 200/204/206 与 Connection: close
| 状态码 | 语义要点 | 陷阱 |
|---|---|---|
200 OK | 有正文 | 用 200 + 业务码表达错误(“HTTP 200 但 code != 0")会让缓存、监控、重试全部失真 |
204 No Content | 成功但无正文 | 若真带了 body,部分客户端/代理行为不一致;REST 中 DELETE/PUT 常用 |
206 Partial Content | 响应范围请求 | 必须配 Content-Range;断点续传、视频拖动、多线程下载靠它 |
206 的前提:客户端发 Range: bytes=0-1023,服务端回 206 + Content-Range: bytes 0-1023/5000;若服务端不支持范围请求,应回 200 + 全量正文而不是 206,否则客户端会拼出错误文件。
Connection: close 在请求/响应结束后关闭连接:连接池复用失效,每请求重付一次握手与慢启动;更关键的是正文定界方式改变——close 语义下的响应可以不带 Content-Length,由"连接关闭"暗示正文结束。这也解释了为什么它与连接复用不能共存、为什么流水线在 close 下彻底不可行。
1xx 的现代用法:100 Continue(先发头、服务端同意后再发大 body)与 101 Switching Protocols(WebSocket 升级);103 Early Hints 在最终响应前提示客户端预加载资源,也是 HTTP/3 里替代服务端推送的主要手段。
注意
这里的追问通常是”502 和 504 你都怎么排查?“标准答法要落到可观测面:502 先确认后端进程是否存活、是否在重启、代理与后端的协议是否一致(HTTP/1.1 vs h2c、keepalive 超时不匹配导致的半开);504 先看网关超时阈值与后端 P99,再顺调用链看是哪一跳变慢。只答"重启一下"就是放弃这题。
本章小结
| 关键结论 | 一句话表述 | 常见错误 |
|---|---|---|
| HTTP/1.1 的队头阻塞在应用层 | 请求-响应同连接串行,浏览器用 6 连接绕过,域名分片是这一限制的产物 | 说成"TCP 层丢包阻塞” |
| 管线化失败的根本原因是缺少请求标识 | 响应必须有序且无 ID,一错就整条连接作废 | 只说"因为队头阻塞",说不出为什么队头阻塞 |
HTTP/2 的复用靠 Stream ID | 帧头 31 位流标识让帧可交错可重组;HPACK 靠静态表+动态表+哈夫曼省字节 | 以为 HTTP/2 改了 HTTP 语义 |
| HTTP/2 仍有 TCP 层 HOL | 一个包丢失阻塞同连接所有流,等待时间是一个 RTT 量级 | 认为多开连接能解决,或认为 HOL 已彻底消除 |
TLS 1.3 省一个 RTT 的关键是 key_share 前置 | 客户端在 ClientHello 里先猜 DH 组,服务端即可在同一往返算出共享秘密 | 说"因为算法更快所以省 RTT" |
307/308 保方法,301/302 可变方法 | 判定轴是"是否允许把 POST 改成 GET" | 认为 301 和 308 只差永久/临时 |
自测题
Q1(推导)一个页面需要加载 30 个同源静态资源。说明 HTTP/1.1 下最多能并发几个,域名分片为什么能提高这个数字、代价是什么。
答案与解析
HTTP/1.1 下浏览器按**源(origin = scheme + host + port)**限流,事实标准是每源 6 条并发连接,因此最多 6 个资源在传;域名分片把资源拆到 s1..s4.example.com,因为限流按源计算,4 个域名 → 4 × 6 = 24 条连接。
代价:每新增域名一次 DNS 解析与一次 TLS 握手(HTTPS 下尤其贵);连接池碎片化、空闲连接占用 fd 与内存;多条连接各自从慢启动开始,拥塞控制效率下降;HTTP/2 下分片会让资源分散到多条连接,破坏单连接多路复用与 HPACK 跨请求压缩收益,是负优化。
错误回答长什么样:答"因为浏览器限制 6 个连接,分片能开更多连接",但说不出"限流单位是源而不是 IP",也说不出 HTTP/2 下分片有害。面试官会追问"HTTP/2 时代还需要分片吗"——不需要,且应把 CDN 的 s1/s2 合并回主域。
Q2(辨析)请分别说明 HTTP/1.1 与 HTTP/2 的队头阻塞在哪一层、由什么造成,以及 HTTP/2 为什么不能靠"多开连接"缓解。
答案与解析
- HTTP/1.1 的 HOL 在应用层:同一连接上请求-响应必须严格串行(响应按请求顺序返回,且响应里没有请求 ID 可用于重排),一个慢响应阻塞其后所有响应;启用管线化仍然存在,管线化甚至因为缺少请求标识而无法在出错时正确恢复。
- HTTP/2 的 HOL 在传输层(TCP):所有流共享一条 TCP 连接,TCP 按序交付,因此一个报文段丢失后,即使后续字节已到达接收缓冲区,内核也不会交给应用层,所有流一起等待重传(RTT/RTO 量级);HPACK 解码是有序的,头部帧丢失还会连带阻塞后续头部解析。多开连接也救不了:HTTP/2 的设计前提就是单连接多路复用,多开连接会把 HPACK 的连接级动态表拆成多份(跨请求去重失效),流控与优先级也无法统一调度,等于退回 HTTP/1.1 的思路。
错误回答:“HTTP/2 也是队头阻塞,因为请求要排队。"——把两者混为一谈,层次说错后 HTTP/3 的动机就完全推不出来。面试官会追问"HTTP/3 彻底消除 HOL 了吗”——QUIC 消除的是跨流 HOL,同一流内部仍有序交付,流内丢包照样阻塞该流。
Q3(画图与解释)画出 TLS 1.2 与 TLS 1.3 完整握手的主要消息,标出各自往返数,并解释 TLS 1.3 为什么能少一个 RTT。
答案与解析
TLS 1.2(2-RTT):第 1 个往返 ClientHello → ServerHello + Certificate + ServerKeyExchange + ServerHelloDone;客户端此时才拿到服务端选定的组与参数,于是第 2 个往返发 ClientKeyExchange + ChangeCipherSpec + Finished,服务端回 ChangeCipherSpec + Finished。
TLS 1.3(1-RTT):ClientHello 里就带 key_share(猜定的 DH 组公钥);服务端 ServerHello 带回自己的 key_share,双方即可算出握手密钥,随后的 EncryptedExtensions + Certificate + CertificateVerify + Finished 全部加密发出;客户端紧接着就能发应用数据与自己的 Finished。
少一个 RTT 的原因:TLS 1.2 的密钥交换材料只能放在第 2 个往返,因为客户端必须先知道服务端选了哪个组;TLS 1.3 让客户端先猜一个组并把 key_share 放进 ClientHello,只要服务端接受该组,同一个往返就能确定共享秘密。猜错时服务端回 HelloRetryRequest,退回 2-RTT。
TLS 1.3 砍掉的东西:静态 RSA 密钥交换(改为 (EC)DHE,获得前向保密)、重协商、压缩、CBC 模式、自定义 DH 参数、ChangeCipherSpec。
错误回答:“TLS 1.3 用的加密算法更快,所以少一个 RTT。"——省 RTT 是协议消息编排的改变,不是算力问题。面试官会追问”Certificate 为什么加密"——服务端在 ServerHello 之后已能算出握手密钥,把证书放在密钥之后既修掉了"证书明文暴露访问域名"的隐私问题,也减少了被中间设备干预的面。
Q4(计算)链路跨大洲,RTT 约 150 ms;DNS 已缓存;服务端支持 TLS 1.3。计算从 TCP 建连到收到 HTTP 响应首字节的最少往返数(忽略丢包与慢启动),并说明改用 0-RTT 能省多少。
答案与解析
逐段计:TCP 三次握手 1 RTT(约 150 ms)→ TLS 1.3 完整握手 1 RTT(约 150 ms)→ HTTP 请求到首字节 1 RTT(约 150 ms),共 3 RTT ≈ 450 ms。
若已持有有效会话票据并用 0-RTT:TLS 握手不占额外往返,客户端可在 ClientHello 后立刻附带应用数据,变成 TCP 1 RTT + 应用数据 1 RTT = 2 RTT ≈ 300 ms,省约 150 ms。
对比 TLS 1.2(2-RTT 握手):总计 1 + 2 + 1 = 4 RTT ≈ 600 ms,即 TLS 1.3 相对 1.2 省约 150 ms,0-RTT 相对 1.2 省约 300 ms。
错误回答:把 DNS 也算进去(题目已说明已缓存),或把 TCP 建连与 TLS 握手合并成 1 个 RTT。另一个常见错误是认为 0-RTT 可用于任何请求——它可被重放且对 0-RTT 数据无前向保密,只能用于幂等请求。面试官会追问"PSK 复用(1-RTT)和 0-RTT 差在哪"——1-RTT 复用仍要等一个往返才发数据,但票据不承担"提前发送"的风险;0-RTT 把数据提前到第一个包,代价是可重放。
Q5(辨析)四个场景分别选哪个状态码并说明理由:① POST /orders 提交后跳转订单详情页;② 迁到新域名,以后都访问新域名,且该接口只接受 POST;③ 用户带合法 token 但无权访问该资源;④ 网关等待后端 3 s 无响应,自身超时阈值 2 s。
答案与解析
① 303 See Other。典型 PRG(Post/Redirect/Get)模式:POST 成功后要让浏览器用 GET 取结果页,避免刷新重复提交表单;303 明确要求改成 GET,语义最贴切。
② 308 Permanent Redirect。要求"永久"且"保持方法";301 虽永久但历史实现允许把 POST 改成 GET,会丢 body;307 保方法但只是临时。
③ 403 Forbidden。身份已由 token 证明,服务端知道"你是谁",只是该身份无权访问,再认证也无济于事。若返回 401 会误导客户端去刷新 token 或重新登录,形成无意义循环。
④ 504 Gateway Timeout。网关在 2 s 就超时放弃等待,属于"上游没有(及时)回答";与 502(上游的回答无效)区分——这里根本还没拿到响应。
错误回答:① 答 302——虽能工作,但 302 的方法语义是模糊的,规范不保证改成 GET;② 答 301——最典型的踩坑,POST 会被改写成 GET,参数全丢;③ 答 401——混淆"未认证"与"未授权";④ 答 502——混淆"回答不对"与"没回答"。面试官会追问"401 响应里必须有哪个头"——WWW-Authenticate,它告诉客户端应该用哪种认证方案;缺少它,401 的语义是不完整的。
面试高频问题与陷阱
| 面试问法 | 考查角度 | 陷阱 / 加分点 | 对应本章小节 |
|---|---|---|---|
| HTTP/1.1 的队头阻塞到底是什么?为什么浏览器要开 6 个连接? | 应用层串行的成因 | 错误回答:“因为 TCP 丢包要重传,所以后面的请求被堵住。"——把传输层机制当成应用层问题。加分点:指出响应无请求 ID,无法重排 | 18.3 |
| HTTP/1.1 的管线化看起来能提升性能,为什么实际没人用? | 协议约束与部署现实 | 错误回答:“因为服务端不支持。"——太笼统。要答出响应必须按序返回、中间设备兼容性差、缺少请求标识导致出错无法恢复 | 18.2.2 |
| 域名分片能提升并发,为什么 HTTP/2 下反而要避免? | 限流单位与版本协同 | 错误回答:“HTTP/2 也会分片,只是不需要了。"——没说出分片会让 HPACK 动态表与多路复用失效。加分点:答出应把域名收敛回主域 | 18.3 |
| HTTP/2 的多路复用是怎么实现的?帧、消息、流是什么关系? | 分帧层的数据结构 | 错误回答:“就是把多个请求打包一起发。"——没有 Stream ID 这个核心。要答出 9 字节帧头、31 位流标识、帧属于流、消息由帧组成 | 18.4.1、18.4.2 |
| HTTP/2 已经多路复用了,为什么还需要 HTTP/3? | TCP 层 HOL | 错误回答:“因为 HTTP/2 兼容性不好。"——完全答偏。核心是单条 TCP 连接上一个包丢失会阻塞所有流,且多开连接会破坏多路复用前提 | 18.5 |
| QUIC 为什么要基于 UDP?它不怕不可靠吗? | 中间设备僵化与用户态实现 | 错误回答:“UDP 更快、不用握手。"——QUIC 自己实现了重传、拥塞控制与流控,比 TCP 更复杂。加分点:答出协议号被中间设备丢弃的僵化问题 | 18.6.1 |
| 0-RTT 有什么风险?什么请求可以用? | 重放攻击与前向保密 | 错误回答:“0-RTT 没有风险,直接用就行。“要答出可重放、对 0-RTT 数据无前向保密,只能用于幂等无副作用请求 | 18.6.3、18.8.2 |
| SNI 是干什么的?它有什么隐私问题? | TLS 扩展与虚拟主机 | 错误回答:“就是告诉服务端用哪张证书。"——方向对但没说到"HTTP 的 Host 在加密之后,握手时看不到域名”,也没提 SNI 明文泄露访问域名、需要 ECH | 18.9.3 |
延伸阅读
- RFC 7230 / 7231 / 7232 / 7233 / 7234 / 7235:HTTP/1.1 报文语法、语义、条件请求、范围请求、缓存与认证(
301/302/307/308、206、401的权威定义)。 - RFC 7540(HTTP/2)与 RFC 7541(HPACK):二进制分帧、流与优先级、头部压缩。
- RFC 9114(HTTP/3)、RFC 9000(QUIC)、RFC 9002(QUIC 丢包检测与拥塞控制)、RFC 9204(QPACK)。
- RFC 8446(TLS 1.3)、RFC 5246(TLS 1.2)、RFC 6066(SNI)、RFC 6797(HSTS)、RFC 6960(OCSP)。
- RFC 6265(Cookie)、RFC 9218(HTTP 可扩展优先级)、RFC 8297(
103 Early Hints);《HTTP/2 in ACTION》与《Bulletproof SSL and TLS》分别是帧与流、证书链与吊销机制的实务参考。 - 第 16 章 16.116.5 节(TCP 建连与关闭)、第 17 章 17.417.6 节(重传与拥塞控制)、第 19 章 19.2 节(DNS 递归与迭代、
curl -w分层排查)。