HTTP 与 Web 通信 — 无状态协议上的状态工程

一句话:HTTP 本身什么都不记得,所有「登录态」「缓存命中」「幂等重试」都是你在它之上额外设计出来的;想清这一点,401/403、缓存不生效、重复提交这些问题就有统一解释。

🎯 这 20% 能解决哪些问题

你遇到的困惑本质原因马上能改的动作
用户明明登录了,接口还是 401凭证没带上或已过期,服务器无法记住状态用 curl -v 看请求头是否携带 Authorization/Cookie
401 和 403 分不清,前端提示乱语义没落到状态码上未认证 = 401,已认证但无权 = 403,写进团队约定
改了代码,用户还是看到老页面强缓存 max-age 未失效静态资源用内容哈希文件名 + 长缓存,HTML 用 no-cache
POST 重试造成重复下单POST 非幂等,客户端不知道能否重试给写接口加幂等键(Idempotency-Key 或业务唯一键)
Session 扩容后用户掉线状态存在单机内存状态外置到 Redis/JWT,服务器保持无状态
接口越来越慢,排查不到点没有分段耗时数据看 DNS/连接/TLS/首字节/下载五段,定位再优化

一、请求-响应:一次性的对话

flowchart LR
    subgraph S1["客户端"]
        B["构造请求<br/>方法 + 路径 + 头 + 体"]
    end
    subgraph S2["服务端"]
        S["处理并返回<br/>状态码 + 头 + 体"]
    end
    B -->|"Request"| S
    S -->|"Response"| B

三条核心规律:

  • HTTP 是无状态的:每次请求相互独立,服务器不记得上一次。
  • 状态是「叠加」出来的:Cookie / Session / Token 都是应用层设计。
  • 语义靠三样东西表达:方法(做什么)、状态码(结果如何)、Header(附加约定)。

二、方法与状态码:语义要能对齐实现

2.1 方法

方法安全(不改状态)幂等(重复同效果)用途
GET✅✅查询资源
POST❌❌创建 / 提交动作
PUT❌✅全量替换
PATCH❌❌(取决于实现)部分更新
DELETE❌✅删除资源

实践含义:只有幂等的操作才允许自动重试。给 POST 加幂等键,是「客户端重试」与「不重复扣款」共存的前提。

2.2 状态码

类别含义常用
2xx成功200 OK、201 Created、204 No Content
3xx重定向301 永久、302 临时、304 未修改(走缓存)
4xx客户端问题400 参数、401 未认证、403 无权限、404 不存在、409 冲突、422 校验失败、429 限流
5xx服务端问题500 异常、502 网关后端错、503 过载、504 后端超时

判据:5xx 会触发服务端告警与重试,4xx 不会。把「参数错误」返回 500,等于把自己的告警刷满、把可重试语义传给客户端。

2.3 关键 Header

Header作用易错点
Content-Type请求体类型发 JSON 却是 form-urlencoded,服务端解析成空
Accept期望响应类型版本协商时常用它区分
Authorization认证凭证Bearer 前缀拼写;401 响应带 WWW-Authenticate
Cookie / Set-Cookie携带/下发 CookieSameSite、Secure、HttpOnly 三件套
Cache-Control缓存策略no-cache ≠ 不缓存,是不直接用(先验证)
X-Forwarded-For经代理后的真实 IP只有信任链清晰才可信,否则可伪造
Idempotency-Key写操作幂等键需服务端持久化并设过期

三、HTTPS:HTTP 跑在 TLS 之上

flowchart LR
    A["客户端"] -->|"1. ClientHello<br/>支持的加密套件"| B["服务端"]
    B -->|"2. ServerHello<br/>选择套件 + 证书"| A
    A -->|"3. 验证证书链"| B
    B -->|"4. 用公钥加密会话密钥"| A
    A -->|"5. 私钥解密"| B
    A -->|"6. 之后用对称密钥通信"| C["加密通道"]
概念一句话常见事故
证书链站点证书 ← 中间 CA ← 根 CA(内置信任)漏配中间证书,浏览器能过、App/ curl 失败
有效期证书会过期无监控导致某天全站 HTTPS 报错
SNI一个 IP 承载多域名证书老客户端不支持 SNI 握手失败
HSTS强制走 HTTPS一旦下发,降级回 HTTP 会被浏览器拒绝

TLS 握手细节与 TCP 层关系见 网络协议栈与 DNS。


四、状态管理:Session、Cookie、Token 是三件事

flowchart TB
    subgraph SA["服务端存状态"]
        A["Session 存内存/Redis<br/>Cookie 只放 session id"]
    end
    subgraph SB["客户端存状态"]
        B["JWT 自包含<br/>服务端只验签"]
    end
    A -->|"扩容友好(外置后)"| OK1["可水平扩展"]
    B -->|"无法主动失效"| OK2["需黑名单或短有效期"]
方案状态存哪优点代价
内存 Session单机实现最简单重启即失效,无法多实例
集中式 SessionRedis可共享、可踢人多一次网络查询
JWT / Token客户端无状态、跨服务传递方便签发难撤销;payload 不能放敏感数据

安全底线:Cookie 加 HttpOnly(防 XSS 窃取)+ Secure(只走 HTTPS)+ SameSite=Lax/Strict(缓解 CSRF);JWT 必须校验 exp 与签名算法,禁止接受 alg: none。


五、版本演进:性能问题出在同一处

版本关键变化解决了什么遗留问题
HTTP/1.1持久连接 + 管线化反复建连开销同连接内请求串行,靠开多连接绕过
HTTP/2二进制分帧 + 多路复用 + 头部压缩队头阻塞(应用层)TCP 层丢包仍阻塞全部流
HTTP/3基于 QUIC(UDP)TCP 队头阻塞 + 连接迁移中间设备支持度与运维复杂度

实践结论:静态资源仍要做域名合并与打包,因为连接数与握手成本依旧是主要开销;不要指望 HTTP/2 自动解决加载慢。


六、缓存:三层命中,一次讲清

层控制方式典型配置
浏览器强缓存Cache-Control: max-age=31536000, immutable带内容哈希的 JS/CSS/图片
协商缓存ETag / Last-Modified → 304HTML 入口文件用 no-cache
CDN / 反向代理s-maxage、Cache-Key、Vary静态与准静态内容
需求配置
完全不缓存Cache-Control: no-store
每次向服务器验证Cache-Control: no-cache
一年内直接用(文件内容变则名字变)max-age=31536000, immutable
HTML 即时更新no-cache + ETag

判据:Vary 里没写的维度,缓存就会串内容——按语言/登录态返回不同结果时必须 Vary 或干脆不缓存。


七、超时、重试与限流:客户端的三件套

项建议原因
连接超时1–3 秒建连失败要快,重试才有意义
读超时按 P99 × 1.5 设置太长会拖垮调用方线程池
重试次数≤2 次,仅幂等操作避免重试风暴放大故障
退避指数退避 + 抖动防止同步重试打挂服务
限流响应429 + Retry-After让客户端有正确行为

八、实战排查:分段计时定位慢在哪

段指标(curl -w)慢了说明
DNStime_namelookupDNS 服务器或记录问题
TCP 连接time_connect网络链路或后端负载高
TLStime_appconnect证书链/握手/CPU
首字节time_starttransfer服务端处理慢,去看应用与 SQL
总耗时time_total带宽或响应体过大
响应慢的下一步(服务端)
├── 1. EXPLAIN 慢 SQL,看是否全表扫描
├── 2. 检查 N+1 查询与缺失的预加载
├── 3. 看缓存命中率与穿透比例
├── 4. 统计外部 API 调用耗时与超时设置
└── 5. 看进程/连接池是否饱和(排队即延迟)

🎓 实战场景(TIL)

  • 场景:用户快速双击提交,数据库多出两条订单。本质:把「幂等」当成调用方的道德约束,服务端没有去重。要点:写接口强制携带 Idempotency-Key(或业务单号)并在服务端存结果;GET 不得改变状态。
  • 场景:登录态偶发丢失,刷新又好了。本质:多台实例本地存 session,请求被负载均衡到另一台。要点:会话外置(Redis)或改 JWT;排查时先看 Set-Cookie 的 Domain/SameSite/Secure。
  • 场景:上线 CDN 后用户看到旧页面。本质:缓存键不含版本,Cache-Control 给得太长。要点:静态资源带指纹文件名 + 长缓存,index.html 给 no-cache;接口缓存放服务端。
  • 场景:接口升级后小程序报错,网关日志看不出问题。本质:HTTP 状态码与业务码混用,200 里包错误。要点:传输层失败用 4xx/5xx,业务失败用 200 + 结构化的 code/message,并统一给监控可识别的错误率口径。

📋 一页速查

我要判断照着做
能不能自动重试只有幂等(GET/PUT/DELETE 或带幂等键的 POST)可重试
返回哪个状态码参数错 400/422,未登录 401,无权限 403,过载 503,后端超时 504
静态资源怎么缓存内容哈希文件名 + max-age=1年, immutable
HTML 怎么缓存no-cache + ETag,保证即时更新
登录态放哪多实例就外置 Redis 或用短有效期 JWT
慢在哪一段curl -w 五段计时,首字节慢才查服务端
是否被限流429 + Retry-After,客户端退避重试
Cookie 安全HttpOnly + Secure + SameSite,缺一不可

🔗 相关链接