HTTP 语义、缓存与版本演进
内容摘要:超文本传输协议(Hypertext Transfer Protocol,HTTP)是一组无状态的应用层请求/响应语义。GET、POST、状态码、字段和缓存语义由 RFC 9110/9111 定义;HTTP/1.1、HTTP/2、HTTP/3 只是不同的线上表达和传输映射。本章讲清方法安全性与幂等性、消息 framing、缓存新鲜度与条件请求、内容协商、Cookie/CORS、代理头,以及三个版本的持久连接、多路复用、流控和队头阻塞差异。
一、HTTP 语义与版本编码
HTTP 定义“客户端希望对资源做什么”以及“服务端结果是什么”,而不是规定后端如何实现。
同一个语义请求:
GET https://www.example.com/manual HTTP semantics在 HTTP/1.1 中表示为文本 request-line 和 Host;在 HTTP/2/3 中表示为 :method、:scheme、:authority、:path 伪头及 HEADERS/DATA 帧。应用看到的资源、方法与状态码不因版本而改变。
无状态不等于服务没有状态
“HTTP 无状态”表示每个请求的协议语义可以独立解释,不要求服务端由 HTTP 协议自动记住前一次请求。应用仍可通过:
- Cookie + 服务端 Session。
- Bearer Token/JWT。
- URL/请求体中的业务标识。
- TLS 客户端证书或其他认证上下文。
维持用户状态。状态安全边界由应用设计,不是 HTTP 自动保证。
二、URL、Origin 与请求目标
URL 示例:
https://www.example.com:443/docs/network?q=http#cache| 部分 | 值 | 是否发送给服务器 |
|---|---|---|
| scheme | https | 决定安全连接与默认端口 |
| host | www.example.com | 用于 DNS、SNI、证书和 HTTP authority |
| port | 443 | 建连目标;默认值可省略 |
| path | /docs/network | 是 |
| query | q=http | 是;可能进入日志/Referer,勿放秘密 |
| fragment | cache | 通常只在客户端处理,不随 HTTP 请求发送 |
Web 同源(origin)由:
scheme + host + port组成。路径不同仍可同源;http 与 https、不同端口或不同主机均为不同源。
三、HTTP/1.1 消息与 framing
RFC 9112 定义 HTTP/1.1 线格式:
HTTP-message = start-line CRLF
*(field-line CRLF)
CRLF
[message-body]请求:
GET /note HTTP/1.1
Host: www.example.com
Accept: text/html
Connection: close响应:
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Content-Length: 11
Connection: close
hello-http1、本机真实原始响应
本机 Python 3.9.6 在 127.0.0.1 启动一次性 HTTP/1.1 服务,socket 发送上述请求,真实输出:
HTTP/1.1 200 OK
Server: BaseHTTP/0.6 Python/3.9.6
Date: Mon, 07 Sep 2026 11:37:24 GMT
Content-Type: text/plain; charset=utf-8
Content-Length: 11
Connection: close
hello-http日期和临时端口随运行变化;这只验证 framing,不代表 Python 内置 server 适合生产。
2、消息体长度
HTTP/1.1 必须无歧义地确定消息边界,常见方式:
Content-Length: N:接收恰好 N 字节。Transfer-Encoding: chunked:每块带十六进制长度,以 0 长块结束。- 特定状态/方法无 body,如 HEAD 响应、1xx、204、304。
- 响应通过连接关闭定界,降低连接复用能力。
如果前端代理与后端对 Content-Length、Transfer-Encoding 或非法空白的解析不一致,可能产生 HTTP Request Smuggling(请求走私)。正确做法是严格按 RFC 9112 解析、拒绝歧义消息,并统一代理链实现。
四、方法:安全性、幂等性与可缓存性
| 方法 | 典型语义 | Safe | Idempotent | 通常是否带请求内容 |
|---|---|---|---|---|
| GET | 获取资源表示 | 是 | 是 | 不应依赖请求内容语义 |
| HEAD | 仅获取与 GET 类似的响应字段 | 是 | 是 | 通常否 |
| POST | 让资源按自身语义处理内容 | 否 | 否 | 是 |
| PUT | 用给定表示创建/替换目标资源状态 | 否 | 是 | 是 |
| DELETE | 删除目标资源关联 | 否 | 是 | 可有但互操作性谨慎 |
| OPTIONS | 查询通信选项 | 是 | 是 | 可选 |
| CONNECT | 建立隧道 | 否 | 否 | 按扩展语义 |
| TRACE | 环回诊断 | 是 | 是 | 否;生产常禁用 |
Safe 表示客户端不请求改变服务器状态,不代表服务器绝不记录日志或计费。Idempotent 表示同一意图重复执行的预期效果与执行一次相同,不代表响应字节或状态码相同。
重试规则:
- GET/HEAD/PUT/DELETE 在协议语义上幂等,但仍需考虑认证、时限和业务实现是否违规。
- POST 默认不可安全自动重试;支付/创建类接口应设计 Idempotency-Key、唯一业务键或事务去重。
- 客户端不能仅凭“没有收到响应”判断服务端没有执行(12)。
五、状态码
| 类别 | 含义 | 常见状态 | 注意 |
|---|---|---|---|
| 1xx | 临时信息 | 100、103 | 后续仍有最终响应 |
| 2xx | 请求成功处理 | 200、201、202、204、206 | 202 只表示已接受,不代表任务完成 |
| 3xx | 重定向/缓存验证 | 301、302、303、304、307、308 | 307/308 保留方法;304 不是普通重定向 |
| 4xx | 客户端请求问题 | 400、401、403、404、409、412、429 | 401 实际表示需认证,403 表示拒绝 |
| 5xx | 服务端/网关失败 | 500、502、503、504 | 502 与 504 是不同的上游失败阶段 |
常见网关错误边界
502 Bad Gateway:代理从上游收到无效响应或连接被重置等。503 Service Unavailable:服务当前不可用/过载,可配Retry-After。504 Gateway Timeout:网关等待上游响应超时。
状态码只能说明生成该响应的 HTTP 节点如何分类问题。定位时还需代理/LB/应用日志和各阶段时延。
六、字段、内容与内容协商
1、常见字段
| 字段 | 方向 | 用途 | 风险/误区 |
|---|---|---|---|
Host / :authority | 请求 | 目标 authority/虚拟主机 | Host 注入、错误信任代理值 |
| Content-Type | 双向 | 内容媒体类型 | 与实际内容不符导致嗅探风险 |
| Content-Length | 双向 | 内容长度 | 与 TE 冲突可能请求走私 |
| Accept | 请求 | 可接受媒体类型 | 无匹配可返回 406 |
| Accept-Encoding | 请求 | gzip/br 等内容编码 | 压缩侧信道与 CPU 开销 |
| Authorization | 请求 | 凭证 | 绝不能记录或转发到错误上游 |
| Location | 响应 | 重定向/新资源位置 | 开放重定向 |
| Retry-After | 响应 | 建议重试时间 | 客户端仍需重试预算和抖动 |
| Vary | 响应 | 扩充缓存键 | Vary: * 不可复用;过多维度降低命中率 |
2、内容协商
客户端可发送:
Accept: application/json, text/html;q=0.8
Accept-Encoding: br, gzip
Accept-Language: zh-CN, en;q=0.7服务端选择表示并用 Content-Type、Content-Encoding、Content-Language 描述。若响应会随请求字段变化,共享缓存需要正确的 Vary,否则可能把一种语言或压缩格式错误返回给其他客户端。
七、HTTP 缓存
缓存的目标是复用先前响应,降低时延和带宽。需要区分:
- Private cache:单用户浏览器/客户端缓存。
- Shared cache:CDN、反向代理或组织代理,供多个用户复用。
- Freshness:缓存响应是否足够新,可否不联系 origin 直接使用。
- Validation:过期后携带 validator 询问 origin 是否仍有效。
1、Cache-Control 易混指令
| 指令 | 含义 |
|---|---|
max-age=60 | 响应在 60 秒内可视为新鲜 |
s-maxage=300 | 共享缓存优先使用 300 秒 |
private | 可存于私有缓存,不得存于共享缓存 |
public | 显式允许共享缓存按规则存储 |
no-cache | 可存储,但复用前必须验证 |
no-store | 缓存不得存储该响应 |
must-revalidate | 过期后必须成功验证才可复用,除非其他规范明确允许 |
no-cache不等于“不缓存”。敏感响应通常需要同时评估no-store、private、认证语义以及应用/浏览器历史记录;单一字段不是所有泄露面的万能开关。
2、ETag 与条件请求
If-None-Match/ETag 通常优先于日期 validator。- 强 ETag 用于字节级一致性语义;弱 ETag
W/"..."表示语义等价但可能非字节相同。 Last-Modified+If-Modified-Since精度和时钟语义更弱。304不携带新的完整表示内容,客户端复用缓存 body。
3、本机缓存实验
本机 Python HTTP server 首次返回 ETag,第二次携带 If-None-Match:
first: 200 etag="kb-v1" cache=max-age=60 body=knowledge-v1
revalidate: 304 etag="kb-v1" body-bytes=0这验证条件请求协议,不代表浏览器/CDN 必然采用相同缓存策略。
八、HTTP/1.0、1.1、2 与 3
| 维度 | HTTP/1.0 | HTTP/1.1 | HTTP/2 | HTTP/3 |
|---|---|---|---|---|
| 主要规范 | RFC 1945 | RFC 9112 | RFC 9113 | RFC 9114 |
| 传输 | TCP | TCP | TCP,通常 TLS | QUIC/UDP,内建 TLS 1.3 |
| 线格式 | 文本 | 文本 | 二进制 frame | QUIC stream 上二进制 frame |
| 持久连接 | 非默认 | 默认 | 单连接长期复用 | 单 QUIC 连接长期复用 |
| 并发 | 多连接 | 多连接/管线化 | 多 stream 复用 | 多 QUIC stream 复用 |
| 头压缩 | 无 | 无 | HPACK | QPACK |
| 应用层 HOL | 单连接/管线化明显 | 管线化明显 | stream 间减少 | stream 间减少 |
| 传输层 HOL | TCP | TCP | 一个 TCP 丢包可阻塞所有 stream | 丢包通常只阻塞相关 QUIC stream |
| 连接迁移 | 无 | 无 | 无 | QUIC Connection ID 支持 |
1、HTTP/1.1
- 默认持久连接,减少 TCP/TLS 握手。
- Pipelining 可连续发送请求,但响应必须按请求顺序,应用层队头阻塞严重,部署有限。
- 浏览器常开多条 TCP 连接获得并发,增加拥塞状态与资源占用。
2、HTTP/2
- 帧交错传输,stream 逻辑独立。
- 连接级和 stream 级流量控制。
- HPACK 压缩重复字段。
- TCP 只看到一个有序字节流,任意 TCP 段丢失可让所有 stream 等待。
- RFC 9113 已弃用 RFC 7540 的原始优先级信令;新部署需核对实际客户端/服务端策略。
3、HTTP/3
- HTTP 语义映射到 QUIC stream。
- QUIC 在 UDP 上实现可靠传输、拥塞控制、TLS 1.3 和连接迁移。
- 单个 stream 丢包通常不阻塞其他 stream 的有序交付。
- 使用 QPACK,避免直接复制依赖全局有序交付的 HPACK。
- UDP 被阻断时,客户端应回退 TCP 版本;“支持 HTTP/3”不代表每次请求都实际用了 HTTP/3。
九、版本发现与协商
- HTTPS 上 HTTP/2 使用 TLS ALPN 标识
h2。 h2ccleartext upgrade 在 RFC 9113 中已弃用;受控内部 prior knowledge 仍是另一种机制。- HTTP/3 可通过
Alt-Svc: h3=":443"、HTTPS DNS RR 或客户端已知信息发现。 - ALPN 是双方能力交集,不是客户端单方面强制。
本机 curl 8.7.1(nghttp2 1.68.1)实测:
默认:https://example.com/ → http_version=2 code=200
强制 --http1.1 → http_version=1.1 code=200
TLS ALPN → h2本机 curl 未显示 HTTP/3 feature,故未执行 HTTP/3 实测,也未伪造对比输出。
十、Cookie、Session、同源与 CORS
1、Cookie 与 Session
服务端:
Set-Cookie: session_id=opaque-value; Secure; HttpOnly; SameSite=Lax; Path=/客户端后续自动携带:
Cookie: session_id=opaque-value安全属性:
Secure:仅通过安全传输发送。HttpOnly:脚本不能直接读取,降低部分 XSS 窃取风险。SameSite:限制跨站场景自动携带,降低 CSRF 风险。Domain/Path:定义发送范围,不是强认证或文件系统权限。- Session ID 应高熵、可撤销、轮换,服务端不得把敏感状态直接信任为未签名 Cookie。
2、同源策略与 CORS
同源策略(Same-Origin Policy,SOP)是浏览器安全模型;跨域资源共享(Cross-Origin Resource Sharing,CORS)是服务端用响应字段授权浏览器脚本读取跨源响应。
关键边界:
- CORS 不是防火墙,也不阻止非浏览器客户端发送请求。
- 简单请求可能不预检;浏览器仍根据响应字段决定脚本能否读取。
- 携带凭证时不能使用任意
Access-Control-Allow-Origin: *。 Origin校验必须精确匹配允许列表,不能用脆弱后缀/子串判断。
十一、代理与转发字段
每个中间节点同时扮演下游 server 和上游 client。常见字段:
- 标准
Forwarded: for=192.0.2.10;proto=https;host=www.example.com - 事实标准
X-Forwarded-For、X-Forwarded-Proto、X-Forwarded-Host - 分布式追踪
traceparent
信任规则:
- 边界代理删除客户端伪造的受信转发字段,再写入自己的值。
- 应用只信任明确代理网段/身份写入的 hop。
- 限制链长度与字段大小,日志脱敏。
- Host/authority、scheme 参与重定向、Cookie 和链接生成时必须校验,防 Host Header Poisoning。
十二、从 URL 到响应的完整链路与时延
curl 常用拆分:
curl -sS -o /dev/null \
-w 'dns=%{time_namelookup}s tcp=%{time_connect}s tls=%{time_appconnect}s ttfb=%{time_starttransfer}s total=%{time_total}s http=%{http_version}\n' \
https://example.com/本机单次真实观测:
dns=0.003894s
tcp=0.109261s
tls=0.222404s
ttfb=0.352832s
http_version=2阶段增量应计算:
TCP 增量 ≈ time_connect - time_namelookup
TLS 增量 ≈ time_appconnect - time_connect
服务等待/请求发送增量 ≈ time_starttransfer - time_appconnect这些字段是从请求起点累计时间,不应直接把 time_appconnect 全部叫“TLS 耗时”。单次样本也不能代表分布,生产需看 p50/p95/p99 与分地域基线。
十三、HTTP 版本对比实验
curl --http1.1 -sS -o /dev/null \
-w 'version=%{http_version} total=%{time_total}s\n' https://example.com/
curl --http2 -sS -o /dev/null \
-w 'version=%{http_version} total=%{time_total}s\n' https://example.com/本机真实输出:
HTTP/2:version=2,code=200
HTTP/1.1:version=1.1,code=200,单次 total=0.313980s这不是性能基准:两个请求缓存、连接、网络时刻不同,且每个版本只请求一个小对象。公平比较应:
- 同一地理位置、同一服务器内容和并发模型。
- 重复采样,区分冷连接/热连接。
- 记录 DNS/TCP/TLS/TTFB/下载、RTT、丢包和对象数。
- 用浏览器 waterfall 或
nghttp/支持 HTTP/3 的 curl 观察 stream。
抓包过滤:
http
http2
quic
tls.handshake.extensions_alpn_strHTTPS 下 HTTP/2 内容被 TLS 加密;需受控环境配会话密钥日志才能解密,禁止采集真实用户敏感流量。
十四、安全风险
| 风险 | 根因 | 防护 |
|---|---|---|
| 请求走私 | 代理链 framing 解析不一致 | 严格 RFC 9112、拒绝 CL/TE 歧义、统一代理 |
| Response Splitting | 未过滤 CR/LF 写入响应字段 | 结构化 API、禁止原始换行 |
| Host Header Poisoning | 信任未验证 Host/转发头 | authority allowlist、可信代理边界 |
| 敏感头泄露 | Authorization/Cookie 被日志或跨域转发 | 日志脱敏、重定向策略、最小转发 |
| 缓存投毒/越权 | cache key 缺少 Host/Vary/认证维度 | 明确缓存键、private/no-store、测试多租户 |
| 压缩侧信道 | 秘密与攻击者输入共同压缩 | 隔离秘密、禁用敏感场景压缩、随机化 |
| HTTP/2/3 资源耗尽 | 大量 stream、字段或 reset | stream/字段/连接限额、补丁与速率限制 |
HTTPS 只保护传输机密性、完整性和身份验证边界,不会自动防止 XSS、SQL 注入、SSRF、越权、恶意服务器或敏感日志(17)。
十五、排障清单
- 记录 URL、method、HTTP 版本、代理链和复现时刻。
- 拆分 DNS、TCP、TLS、TTFB 与下载时间。
- 明确状态码由 CDN、LB、网关还是 origin 生成。
- 查看
Via、Age、缓存状态字段、trace ID,但注意其可信边界。 - 缓存问题同时检查 cache key、
Vary、freshness、validator 和 purge。 - HTTP/2 问题检查 stream error、GOAWAY、flow-control window 与 TCP 重传。
- HTTP/3 问题检查 UDP 可达、QUIC/TLS、Alt-Svc 和 TCP 回退。
- 重试前确认方法幂等、预算、退避和服务端是否已经执行。
十六、版本与实现差异
- 当前 HTTP 核心规范是 RFC 9110/9111;HTTP/1.1/2/3 分别是 RFC 9112/9113/9114。
- 浏览器连接池、跨 origin coalescing、优先级、HTTP/3 race/fallback 与缓存分区策略随版本变化。
- CDN 私有缓存字段如
CF-Cache-Status、X-Cache不是统一标准。 - Server Push 在标准中存在,但浏览器/服务部署支持已显著收缩;使用前查目标版本。
- curl 构建功能取决于 TLS/HTTP 库;
curl -V是实验前置证据。
十七、关联章节
- 上一章:15 DNS 名称解析、缓存与安全
- 下一章:17 TLS、PKI 与 HTTPS
- 相关:13 TCP 状态机、14 TCP 性能、18 代理/LB/CDN、网络安全
检索关键词
HTTP 语义 RFC 9110 HTTP/1.1 报文 framing Content-Length chunked HTTP 方法 safe idempotent HTTP 状态码 502 503 504 Content-Type Content-Encoding Vary HTTP 缓存 max-age no-cache no-store ETag If-None-Match 304 HTTP/2 frame stream HPACK 多路复用 HTTP/2 TCP 队头阻塞 HTTP/3 QUIC QPACK ALPN h2 h3 Cookie Secure HttpOnly SameSite 同源策略 CORS Forwarded X-Forwarded-For HTTP 请求走私
权威来源
- RFC 9110:HTTP Semantics
- RFC 9111:HTTP Caching
- RFC 9112:HTTP/1.1
- RFC 9113:HTTP/2
- RFC 9114:HTTP/3
- RFC 9000/9001(QUIC)、RFC 9204(QPACK)、RFC 7301(ALPN)
- RFC 6265(Cookie)、WHATWG Fetch Standard(SOP/CORS)
- IANA HTTP Method、Status Code、Field Name 与 ALPN 注册表
HTTP 语义、缓存和各版本传输映射已于 2026-09-07 对照 RFC 9110–9114 核验。HTTP/1.1 原始消息、ETag/304、本机 HTTP/1.1 与 HTTP/2 协商已真实执行;本机 curl 不支持 HTTP/3,因此 HTTP/3 抓包与日志对比明确标记为未执行。
