状态码、头部、缓存与重定向,用抓包工具看懂每一次请求。
| 章节 | |
|---|---|
| 01 请求与响应的结构 | 1 小节 |
| 02 常见状态码逐个讲 | 4 小节 |
| 03 缓存头与 CDN | 3 小节 |
| 04 重定向链追踪 | 1 小节 |
| 05 Cookie 与会话 | 1 小节 |
| 06 抓包工具上手 | 4 小节 |
HTTP 报文是纯文本(HTTP/2、HTTP/3 改成了二进制帧,但语义完全一样),结构就四段:起始行、头部、空行、消息体。
POST /api/login HTTP/1.1
Host: www.ip438.com
User-Agent: curl/8.4.0
Content-Type: application/json
Content-Length: 34
Accept: application/json
{"user":"demo","password":"***"}HTTP/1.1 200 OK
Date: Mon, 28 Sep 2026 03:12:44 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 27
Cache-Control: no-store
Set-Cookie: sid=abc123; Path=/; HttpOnly; Secure; SameSite=Lax
{"code":0,"msg":"success"}请求行里的方法决定语义,几个必须分清的性质:
| 方法 | 幂等 | 安全 | 可缓存 | 说明 |
|---|---|---|---|---|
| GET | 是 | 是 | 是 | 只取数据,不该有副作用 |
| HEAD | 是 | 是 | 是 | 只要响应头,探活、查大小很好用 |
| POST | 否 | 否 | 基本否 | 提交,重复发会重复产生数据 |
| PUT | 是 | 否 | 否 | 整体替换,发两次结果一样 |
| PATCH | 否 | 否 | 否 | 局部更新 |
| DELETE | 是 | 否 | 否 | 删除 |
| OPTIONS | 是 | 是 | 否 | 跨域预检用的就是它 |
「幂等」不是「会不会改数据」,而是「发一次和发 N 次的最终状态是否相同」。这个性质决定了网关和客户端敢不敢自动重试——所以别把删除操作做成 POST 之外再做个 GET 入口,爬虫一扫就全没了。
| 头 | 方向 | 作用 |
|---|---|---|
Host | 请求 | 同一个 IP 上区分虚拟主机,HTTP/1.1 必填 |
Content-Type | 双向 | 载荷类型,少了 charset 就会乱码 |
Content-Length / Transfer-Encoding: chunked | 双向 | body 多长,或者分块传 |
Accept-Encoding / Content-Encoding | 双向 | 压缩协商,gzip / br |
X-Forwarded-For / X-Real-IP | 请求 | 代理层带上的真实客户端 IP,可伪造,别直接当身份用 |
Location | 响应 | 3xx 要跳去哪 |
Server | 响应 | 服务端软件,生产环境建议隐藏 |
完整对照表在 HTTP 请求头和响应头信息对照表;想直接构造一个请求发出去看结果,用 HTTP 请求测试;在浏览器里调通之后要落到代码里,用 curl 命令转代码把 curl 一键转成 PHP / Python / Go。
状态码分五类:1xx 协议中间态、2xx 成功、3xx 重定向、4xx 你错了、5xx 我错了。日常真正需要分清的是这些:
| 码 | 含义 | 什么时候用 |
|---|---|---|
| 200 OK | 成功 | 默认 |
| 201 Created | 已创建 | POST 创建资源后,配合 Location 指向新资源 |
| 204 No Content | 成功但没有内容 | 删除成功、埋点上报 |
| 206 Partial Content | 部分内容 | 断点续传、视频拖进度条,配合 Range |
| 码 | 是否保留方法 | 是否永久 | 场景 |
|---|---|---|---|
| 301 | 否(POST 会变 GET) | 永久 | 换域名、规范化 URL,搜索引擎会转移权重 |
| 302 | 否 | 临时 | 临时跳转 |
| 303 | 否(强制 GET) | 临时 | 表单提交后跳到结果页,防重复提交 |
| 307 | 是 | 临时 | 需要保持 POST 的临时跳转 |
| 308 | 是 | 永久 | 需要保持 POST 的永久跳转 |
| 304 Not Modified | — | — | 协商缓存命中,没有 body,见下一章 |
301 要慎用:浏览器会把它长期缓存下来,配错了访客本地清不掉缓存就一直跳错地方。上线新规则时先用 302 验证,确认无误再换 301。
| 码 | 含义 | 常见原因 |
|---|---|---|
| 400 | 请求格式错 | JSON 写坏、参数类型不对 |
| 401 | 未认证 | 没带或带错凭证,响应应带 WWW-Authenticate |
| 403 | 已认证但无权限 | 也常被 WAF、IP 黑名单用来挡人 |
| 404 | 资源不存在 | |
| 405 | 方法不允许 | 用 GET 去请求只收 POST 的接口 |
| 413 | 载荷过大 | nginx client_max_body_size 没放开 |
| 429 | 请求过多 | 限流,响应应带 Retry-After |
401 和 403 的区别是「你是谁没搞清」还是「知道你是谁但不让你进」。
| 码 | 含义 | 排查方向 |
|---|---|---|
| 500 | 服务端内部错误 | 看应用日志,通常是代码异常 |
| 502 | 网关收到无效响应 | 后端进程挂了 / 端口不对 / 后端崩溃 |
| 503 | 服务不可用 | 过载、维护中,应带 Retry-After |
| 504 | 网关超时 | 后端太慢,或者网关超时设太短 |
502 和 504 是站长最常撞上的一对:502 多半是 php-fpm / 应用进程没起来或被打挂了,504 是它还活着但跑得太慢。先看后端进程在不在、端口通不通,再看慢查询。
全量码表在 HTTP 协议状态码;想确认线上某个 URL 现在返回什么,用 HTTP 状态检测;一批 URL 一次过,用批量网站巡检。
HTTP 缓存分两层:强缓存(本地直接用,不发请求)和协商缓存(发请求问一句「变了吗」,没变就回 304)。
Cache-Control: public, max-age=31536000, immutable| 指令 | 含义 |
|---|---|
max-age=N | N 秒内直接用本地副本 |
s-maxage=N | 只对 CDN / 共享缓存生效,优先级高于 max-age |
public / private | 能否被 CDN 等中间节点缓存,带个人数据的一律 private |
no-cache | 可以存,但每次都要去校验(不是不缓存) |
no-store | 真的不许存,登录态、支付页用 |
immutable | 内容永不变,刷新时也别去校验 |
Expires 是 HTTP/1.0 的老写法,和 Cache-Control 同时存在时后者优先,留着只为兼容。
# 响应带上
ETag: "a1b2c3"
Last-Modified: Mon, 21 Sep 2026 10:00:00 GMT
# 下次请求带回去
If-None-Match: "a1b2c3"
If-Modified-Since: Mon, 21 Sep 2026 10:00:00 GMT命中就回 304 Not Modified,没有 body,省的是带宽不是往返。ETag 比 Last-Modified 精确(后者只到秒,且多机部署时文件 mtime 可能不一致)。
| 资源 | 建议 |
|---|---|
带指纹的静态文件 app.9f2c1.js | max-age=31536000, immutable |
不带指纹的 logo.png | max-age=86400 |
| HTML 页面 | no-cache 或很短的 max-age,保证能及时更新 |
| 登录后页面、接口 | no-store + private |
给 HTML 配长缓存是最常见的事故:改了内容用户看不到,还没法让他们清缓存。静态资源要长缓存,必须靠文件名加指纹,而不是靠 ?v=2(部分中间节点会忽略查询串)。
CDN 还要多看两个头:Age(在缓存里待了多久)和 X-Cache: HIT/MISS(各家命名不同)。调试时用 curl -I 看一眼最快:
curl -I https://www.ip438.com/static/app.css
curl -I -H 'Accept-Encoding: gzip, br' https://www.ip438.com/压缩有没有真正生效,用网站 GZIP 压缩检测确认;整体加载表现用网站测速。
一次访问背后常常串着好几跳:http://example.com → https://example.com → https://www.example.com → https://www.example.com/index.html。每一跳都是一次完整的 DNS + TCP + TLS,对首屏和 SEO 都是纯损耗。
# 跟随跳转并打印每一跳
curl -sIL https://ip438.com | grep -Ei '^(HTTP/|location:)'
# 只要最终地址和跳数
curl -so /dev/null -w '%{num_connects} 次连接 %{num_redirects} 次跳转 %{url_effective}\n' -L https://ip438.comhttp + 裸域 直接跳到 https + www(或反过来),不要先升 HTTPS 再补 www,那是两跳。canonical。Location: //example.com 这种省协议写法在老客户端上会出问题。ERR_TOO_MANY_REDIRECTS,curl -L 默认最多跟 50 跳。nginx 里的标准写法:
server {
listen 80;
server_name example.com www.example.com;
return 301 https://www.example.com$request_uri;
}
server {
listen 443 ssl;
server_name example.com; # 裸域也要有证书,否则跳转前就报警告
return 301 https://www.example.com$request_uri;
}短链接是重定向的另一种用法,点之前先用短网址还原看清楚最终落点,能躲掉不少钓鱼。跳转链和最终状态也可以用 HTTP 状态检测一次看全;怀疑运营商在中间插东西,用网站劫持检测。
HTTP 本身无状态,登录态靠 Cookie 带。服务端 Set-Cookie 下发,浏览器之后自动带上 Cookie 头。
Set-Cookie: sid=abc123; Path=/; Domain=example.com; Max-Age=86400; HttpOnly; Secure; SameSite=Lax| 属性 | 作用 | 建议 |
|---|---|---|
Domain | 哪些域名带上它 | 不写=只当前主机;写了会带给所有子域 |
Path | 哪些路径带上它 | 一般 / |
Expires / Max-Age | 存活时间 | 都不写就是会话 Cookie,关浏览器即失效 |
HttpOnly | JS 读不到 | 会话 Cookie 必须加,挡 XSS 偷 Cookie |
Secure | 只走 HTTPS | 必须加 |
SameSite | 跨站请求带不带 | Lax 为现代浏览器默认值,能挡掉大部分 CSRF |
SameSite 三个值的区别:Strict 完全不跨站带(从外站点链接进来会显示未登录);Lax 顶级导航的 GET 会带,POST 和子资源不带;None 都带,但必须同时加 Secure,否则浏览器直接丢弃——嵌到别家站点里的服务(在线客服、第三方支付)要特别注意这条。
| 方案 | 优点 | 代价 |
|---|---|---|
| Session ID + 服务端存储(Redis) | 能随时踢下线,Cookie 里没有敏感信息 | 需要共享存储 |
| JWT 等自包含令牌 | 无状态,天然适合多服务 | 签发后无法撤销,只能靠短有效期 + 刷新令牌兜底 |
JWT 的载荷只是 Base64URL 编码、不是加密,任何人都能解开看内容,所以里面不能放密码、手机号这类敏感数据。解一个令牌看看它到底装了什么,用 JWT 在线解析。
会话安全的三条底线:登录成功后换一个新的 session id(防会话固定)、退出时服务端真正销毁(不只是删 Cookie)、敏感操作二次校验。
排查 HTTP 问题,按「从外到内」的顺序换工具,别一上来就开抓包软件。
# 看完整的请求头与响应头(> 是发出的,< 是收到的)
curl -v https://www.ip438.com/ -o /dev/null
# 分段耗时,一眼看出慢在 DNS、TCP、TLS 还是服务端
curl -o /dev/null -s -w \
'dns:%{time_namelookup} tcp:%{time_connect} tls:%{time_appconnect} ttfb:%{time_starttransfer} total:%{time_total}\n' \
https://www.ip438.com/
# 跳过 DNS 直接指定后端 IP,用来确认是不是 CDN 调度的问题
curl -v --resolve www.ip438.com:443:1.2.3.4 https://www.ip438.com/--resolve 这一招很实用:域名照常写(Host 和 SNI 都对),只是把解析结果按你说的来,这样可以单独测某一台源站或某个 CDN 节点。
Network 面板里最该看的几列:
(from disk cache) / (from memory cache),那是没发请求;Charles / Fiddler / mitmproxy 这类工具是在中间装一个代理,要解 HTTPS 必须把它的根证书装到系统/浏览器信任区。这也正说明了 HTTPS 的信任模型:只要有人能往你的信任链里塞证书,就能看到明文,所以别随便装来路不明的根证书。
移动端抓包还有一道坎:Android 7 之后 App 默认不信任用户安装的证书,需要目标 App 配合(debug 版 network_security_config);做了证书绑定(pinning)的 App 则完全抓不了。
到了怀疑网络本身的时候才用它:
# 抓指定端口,不解析域名和服务名,存成文件拿回本地用 Wireshark 看
tcpdump -i any -nn port 443 -w /tmp/https.pcap
# 直接看 HTTP 明文(仅限未加密的 80 端口)
tcpdump -i any -nn -A -s 0 port 80要区分「协议本身的问题」还是「实现的问题」,先确认协议协商对不对:HTTP/2 有没有真正启用、TLS 版本和加密套件是什么,用 HTTP/2 与 TLS 协议检测扫一眼,比在本地猜快得多。
本文由 ip438查询网 原创,正文中的命令与配置片段可直接取用。 内容会随实践持续修订,发现错漏欢迎反馈。