HTTP 工具
一份可直接干活的 HTTP 排查参考:状态码与重定向语义、Cache-Control 各指令的作用,以及当响应头、URL 或分享卡片出问题时该拿哪个工具。
9 工具
你实际在排查的是什么
一次 HTTP 交换是四个可以分开看的东西,而排查中被浪费掉的时间大多来自把它们混为一 谈:URL(你要的是什么)、请求头(你声称自己是谁、能接受什么)、 状态行(判定结果),以及响应头(关于缓存、安全和内容类型的契约)。正文是 其中最不重要的部分——等它出错的时候,响应头早就把原因讲完了。
第二点,浏览器给你看的几乎没有一样是源站的原始输出。在你的服务器和 Network 面板
之间,隔着一个 CDN、也许还有代理、HTTP/2 或 /3(它们会把字段名小写化并去掉原因
短语)、一个 service worker,以及磁盘缓存。一行 200 OK (from disk cache) 是一次
响应的记忆,不是一次响应——所以当某个头看起来不见了,先问问你读的是谁的副本。
第三点,HTTP 在很大程度上是自述的:User-Agent、Referer、Accept-Language 以及
每一个自定义 X- 头,都只是客户端选择发送的声明,一行代码就能伪造。只有源站输出
的东西——状态码和响应头——才在你的掌控之内。
状态码分类速查
| 分类 | 含义 | 客户端该做什么 | 启发式可缓存 |
|---|---|---|---|
1xx | 中间态(100、101、103) | 继续等 | 否 |
2xx | 成功 | 使用正文(204 没有正文) | 200、203、204、206 |
3xx | 重定向 / 重新验证 | 跟随 Location;收到 304 就复用缓存 | 300、301、308 |
4xx | 请求本身就是错的 | 改正它;不要盲目重试 | 404、405、410、414 |
5xx | 服务器把一个合法请求办失败了 | 退避;尊重 Retry-After | 仅 501 |
有两个码经常被处理错:429 应该带上 Retry-After,而 304 Not Modified 是成功——
把它记成错误会掩盖一个正常工作的缓存。
大家总选错的重定向矩阵
| 码 | 永久性 | 方法与请求体是否保留 | 用于 |
|---|---|---|---|
301 | 永久 | 否——可能被改成 GET | 重命名的 URL、主机归并 |
302 | 临时 | 否——同样有改写风险 | 历史默认值;对 POST 请避免 |
303 | 临时 | 否——强制变成 GET | POST 之后重定向到结果页 |
307 | 临时 | 是 | API 端点临时迁移 |
308 | 永久 | 是 | POST 端点永久迁移 |
浏览器还会把 301 缓存得很死,有时长达整个配置文件的生命周期,所以一个发错的 301
撤销起来代价高昂——先在预发环境验证。
Cache-Control 逐条指令
| 指令 | 管的是什么 | 备注 |
|---|---|---|
max-age=N | 新鲜期秒数,对任何缓存生效 | 0 强制重新验证 |
s-maxage=N | 仅对共享缓存的新鲜期 | 在 CDN 侧覆盖 max-age |
no-cache | 可以存;复用前必须重新验证 | 不等于 no-store |
no-store | 禁止存储该响应 | 需登录才能看的 HTML |
private | 浏览器可存,共享缓存不可存 | 个性化响应 |
must-revalidate | 出错时不得继续提供过期副本 | 与 max-age 搭配 |
immutable | 新鲜期内绝不重新验证 | 带内容哈希的资源 |
stale-while-revalidate=N | 先给过期副本,后台再刷新 | 平滑 CDN 未命中 |
Vary 也属于这一节:如果响应会随 Accept-Language 或某个自定义头而不同,而你从
未声明它,共享缓存就会把错误的变体交给下一位访客。
什么问题用哪个工具
手上有一个 URL、想知道源站自己怎么回答,就用
HTTP 头信息检查工具:请求从 api.sitekits.dev 发出,所以路径
上没有扩展、没有缓存、没有 service worker,你拿到的是最终状态、最终 URL、跳数以及
每一个响应头。它只读头,从不读正文,其 SSRF 防护会拒绝 localhost 和私有地址段。
需要发出某个具体请求时——带 JSON 请求体的 PATCH、一个
Authorization: Bearer 头、一个裸的 OPTIONS 用来读预检——用
REST API 测试器。请求从你的浏览器直连目标,所以目标的 CORS
策略对它生效的方式,跟对你自己前端代码生效的方式完全一样——当 bug 就是 CORS 时
这正是你要的。如果怀疑对象是 token,把它粘进
JWT 令牌解码器 读 exp 和各项声明;解码不是验证。
当出问题的单位是整个页面加载而不是单个请求,就导出一份 HAR 放进
HAR 文件查看器;计时相关的内容在
HAR 实战指南 里。查询串上的谜团交给
URL 解析和分析工具,它经由浏览器的 WHATWG 解析器列出百分号解码
后的参数;某个值被多转义了一次时再加上
URL 编码 / 解码。UserAgent 解析器 把一条
UA 字符串归约为浏览器、操作系统、设备类别和一个 bot 启发式判断;不认识的状态码去
HTTP 状态码参考;文件本该渲染却被下载,通常是
MIME 类型检查器 关于 Content-Type 的问题。当响应本身健康、
只是分享卡片不对时,把页面的 HTML 粘进 OG 预览——解析在本地完成,
所以预发构建和需要登录的页面都能用。
落坑清单
挂在 HTTP 响应上的 Strict-Transport-Security 毫无作用
浏览器会忽略经明文 HTTP 送达的 HSTS,在那里设置的 Secure cookie 也会被丢掉。把
升级重定向放在 80 端口,把安全响应头放在 HTTPS 响应上——并且两跳都要检查,不是只看
最后一跳。
安全响应头是按响应生效的,不是按站点
你 HTML 上一条严格的 Content-Security-Policy,对你的 API 返回的 JSON、CDN 的错误
页、或者别处的上传存储桶什么都不代表。CSP 生成器 负责写出这个
头;至于哪些响应会带上它,由你的部署配置决定。
重定向链要花掉往返
http://example.com → https://example.com → https://www.example.com/ →
/home/ 会在任何 HTML 之前多花三个往返,而且每一跳都可能丢掉一个方法、cookie 或
查询串。在边缘把它压成一跳。
正文里塞错误的 200 会击穿基于状态码的监控
一边回 200 一边在载荷里写 {"error": ...},会让按状态码分类触发的告警看不见故障,
并抬高被测出来的可用性。如果 SLO 归你负责,就连载荷一起断言——参见为
SRE 工作 汇集的那批工具。
+ 和 %20 不能互换
在按 application/x-www-form-urlencoded 编码的查询串里,+ 解码为空格;而在路径段
里它就是一个字面的加号。双重编码是它的兄弟 bug:%20 变成 %2520,而故障看起来
像路由问题,不像转义问题。