HTTP 实战与排错 — 抓包、curl 与常见故障定位
Wireshark 过滤式、curl/nc/ab 实战命令、502/504/CORS/缓存故障排查与版本对比 / 应用层 / TCP 80 / RFC 9110
这篇你能学到
- 用 Wireshark 和 tcpdump 把一次 HTTP 交互原原本本还原出来,并分清"服务器慢"还是"网络慢"。
- 把
curl用到位:看完整交互、分阶段测耗时、绕过 DNS 直连后端、模拟 CORS 预检、强制指定 HTTP 版本。 - 遇到 502/504/CORS/缓存不生效/重定向循环这些日常故障时,按固定顺序定位到底是客户端、网关还是后端的问题。
抓包观察
Wireshark 显示过滤式
这些过滤式能干什么:从海量数据包里精准捞出你要看的那几个 HTTP 请求——按方法捞、按 URI 捞、按状态码捞、按耗时捞,避免在几万行里翻找。
| 目的 | 过滤表达式 |
|---|---|
| 所有 HTTP 报文 | http |
| 只看请求 | http.request |
| 只看响应 | http.response |
| 指定方法 | http.request.method == "POST" |
| 指定 URI | http.request.uri == "/api/login" |
| URI 模糊匹配 | http.request.uri contains "/api/" |
| 指定 Host(虚拟主机排查) | http.host == "api.example.com" |
| 指定状态码 | http.response.code == 502 |
| 所有错误响应 | http.response.code >= 400 |
| 正文内容搜索 | http contains "error" |
| 指定 Content-Type | http.content_type contains "json" |
| 带 Cookie 的请求 | http.cookie |
| 服务端下发 Cookie | http.set_cookie |
| 响应耗时(服务器慢) | http.time > 1 |
| 非 80 端口的 HTTP | http && tcp.port == 8080 |
| HTTP/2 | http2;只看首部帧 http2.type == 1;数据帧 http2.type == 0 |
| HTTP/3 | http3;QUIC 层 quic |
| TCP 层辅助:重传 | tcp.analysis.retransmission |
| TCP 层辅助:连接被重置 | tcp.flags.reset == 1 |
若 HTTP 跑在非标准端口,需在
分析 → 解码为(Decode As)中把该端口指定为 HTTP,否则只显示为 TCP。
典型字段说明
在 Wireshark 中对任一 HTTP 包右键 追踪流 → HTTP 流(Follow HTTP Stream),可直接看到完整的请求-响应文本,这是排查 HTTP 问题最高效的动作。
关注这几处:
http.time:Wireshark 自动计算的"请求发出到响应首字节"耗时。区分"服务器慢"(http.time 大)与"网络慢"(TCP 重传多、RTT 大)。Host与Location:重定向循环时对照这两个字段,看是不是 HTTP↔HTTPS 或带斜杠↔不带斜杠来回跳。Content-Length与实际字节:不一致会导致客户端挂起等待。Connection: close:如果每个请求后都出现,说明连接复用失效,性能会明显下降。X-Forwarded-For/Via:判断请求经过了几层代理,以及后端看到的客户端 IP 是否正确。
tcpdump 命令行抓包
这组命令能干什么:在没有图形界面的服务器上抓包。第一条存盘留证,第二条直接在终端里把 HTTP 明文打出来,第三条只挑 GET 请求,最后一条把抓包文件里的请求汇总成一张表。
| |
常用命令 / 配置
curl —— 排查 HTTP 的瑞士军刀
这组命令能干什么:几乎所有 HTTP 问题的第一现场。看真实收发了什么、跟踪每一跳重定向、伪造任意方法和头部、绕过 DNS 直连某台后端、把一次请求的耗时拆成 DNS/TCP/TLS/首字节四段——慢在哪一步一目了然。
| |
手工敲 HTTP(理解协议的最佳练习)
这组命令能干什么:不用任何 HTTP 客户端库,纯手工把一条请求"打"进 TCP 连接里。这是验证"请求行 + 首部 + 空行"结构最直观的方式——少敲那个空行,服务器就会一直等着不回你。
| |
连接状态与压测
这组命令能干什么:从"连接"和"压力"两个角度看服务健康度。前两条看谁在听、有多少活跃连接,第三条专治 TIME_WAIT 堆积,后面几条用不同压测工具打出吞吐和延迟分布,最后一条起个临时服务器验证客户端行为。
| |
常见故障与排错
结论先行:一眼看懂每个现象在说什么
- 502 Bad Gateway —— 先记住结论:网关是好的,后端出问题了(崩了、或回了个不合法的响应)。
- 504 Gateway Timeout —— 先记住结论:后端还活着,只是太慢,慢过了网关的等待上限。
- 503 Service Unavailable —— 先记住结论:服务主动说"我现在不接客",健康检查全挂、限流熔断或维护中。
- 499(Nginx 私有码) —— 先记住结论:是客户端先跑了,不是服务端的错,去比前端超时和后端 P99。
- 400 Bad Request —— 先记住结论:请求本身写坏了,缺 Host、头太长或 URL 含非法字符。
- 413 Content Too Large —— 先记住结论:上传超限了,而且网关和后端两处限制都要改。
- CORS 报错 —— 先记住结论:这是浏览器拦的,服务端其实已经收到了请求,问题出在响应头没配对。
- 缓存不生效 / 更新不及时 —— 先记住结论:多半是
Cache-Control语义用错或漏配Vary,先看响应头再怀疑 CDN。 - 返回了别的站点的内容 —— 先记住结论:
Host头出问题了,请求命中了默认虚拟主机。 - 重定向循环 —— 先记住结论:两条重定向规则在互相推来推去,最常见是反代没透传
X-Forwarded-Proto。 - 请求挂起不返回 —— 先记住结论:有一方在等一段永远不会来的正文,声明的长度和实际字节对不上。
- 上传大文件卡在中途 —— 先记住结论:卡在
Expect: 100-continue或代理缓冲上,不是网络断了。 - 后端拿到的客户端 IP 都是网关 IP —— 先记住结论:真实 IP 在传递链路上被丢了,反代没设或后端没信任
X-Forwarded-For。 - TIME_WAIT 大量堆积 —— 先记住结论:连接建了又关、关了又建,长连接没开起来。
- HTTP/2 未生效(仍是 1.1) —— 先记住结论:ALPN 那一步没谈成,或者服务端压根没开 http2。
- GET 参数中文乱码 —— 先记住结论:编码环节没对齐,不是"中文不能传"。
详细排查表(现象 / 可能原因 / 排查方法)
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| 502 Bad Gateway | 上游(后端应用)挂了、崩溃、返回了不合法的 HTTP 响应;网关与上游协议不匹配(如后端是 HTTPS 但网关按 HTTP 连) | 看网关 error 日志(Nginx connect() failed / upstream prematurely closed);在网关机上直接 curl 后端IP:端口 复现 |
| 504 Gateway Timeout | 上游处理超过网关的 proxy_read_timeout;上游死锁或慢 SQL | 对比后端自身耗时日志与网关超时配置;curl -w time_starttransfer 直连后端测真实耗时 |
| 503 Service Unavailable | 上游全部不健康被摘除;限流/熔断触发;服务在维护 | 查健康检查配置与后端存活;看是否有 Retry-After 头 |
| 499(Nginx 私有码) | 客户端在服务端响应前主动断开(用户刷新/前端超时短于后端) | 对比前端超时设置与后端 P99 耗时 |
| 400 Bad Request | 缺 Host 头;首部超长(Nginx large_client_header_buffers);URL 含非法字符 | curl -v 看完整请求;调大 client_header_buffer_size |
| 413 Content Too Large | 上传超过服务端限制 | Nginx 调 client_max_body_size,注意网关与后端都要改 |
| CORS 报错(浏览器控制台) | 服务端未回 Access-Control-Allow-Origin;预检 OPTIONS 未被正确处理;带 Cookie 时 Allow-Origin 用了 * | 用上文的 OPTIONS 预检命令验证;带凭证时 Allow-Origin 必须是具体源且 Allow-Credentials: true |
| 缓存不生效 / 更新不及时 | Cache-Control 配错(no-cache 与 no-store 混淆);漏配 Vary;CDN 缓存键不含 query | curl -I 看 Cache-Control/ETag/Age;CDN 侧看 X-Cache: HIT/MISS |
| 返回了别的站点的内容 | Host 头缺失或错误,命中了服务器的默认虚拟主机 | curl -H 'Host: 正确域名' http://IP/;检查 Nginx default_server 配置 |
| 重定向循环 ERR_TOO_MANY_REDIRECTS | HTTP→HTTPS 与 HTTPS→HTTP 规则打架;反代未透传 X-Forwarded-Proto,后端误判为 HTTP 又重定向 | curl -vL 观察每一跳 Location;反代加 proxy_set_header X-Forwarded-Proto $scheme |
| 请求挂起不返回 | Content-Length 大于实际发送字节,服务端一直等剩余正文;或分块传输缺结束块 | 抓包对照声明长度与实际字节数 |
| 上传大文件卡在中途 | 未处理 Expect: 100-continue;代理缓冲区不足 | curl -H 'Expect:' 禁用后对比;调 proxy_request_buffering off |
| 后端拿到的客户端 IP 都是网关 IP | 反代未设置 X-Forwarded-For,或后端未信任该头 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;后端配置可信代理列表 |
| TIME_WAIT 大量堆积 | 短连接过多、未启用 keep-alive | ss -tan 统计;客户端与网关开启长连接(Nginx upstream { keepalive 64; }) |
| HTTP/2 未生效(仍是 1.1) | ALPN 未协商成功;Nginx 未加 http2 参数;明文场景浏览器不支持 h2c | curl -v --http2 https://… 看 ALPN, server accepted h2;nginx -V 确认编译了 http_v2 模块 |
| GET 参数中文乱码 | 未做 percent-encoding;服务端解码字符集不一致 | curl --data-urlencode;统一使用 UTF-8 + %XX 编码 |
通用排查顺序:DNS 解析对不对(dig) → TCP 能否连通(nc -zv) → curl -v 看完整请求响应 → 看状态码归类(4xx 找客户端,5xx 找服务端) → 绕过网关直连后端复现(--resolve) → 抓包 Follow HTTP Stream → 看服务端 access/error 日志。
与其他协议对比
HTTP/1.1 vs HTTP/2 vs HTTP/3
| 维度 | HTTP/1.1(RFC 9112) | HTTP/2(RFC 9113) | HTTP/3(RFC 9114) |
|---|---|---|---|
| 发布年份 | 1997 / 2022 修订 | 2015 / 2022 修订 | 2022 |
| 传输层 | TCP | TCP | QUIC over UDP(RFC 9000) |
| 报文格式 | 纯文本 | 二进制分帧 | 二进制分帧 |
| 并发方式 | 每连接串行;靠开多条 TCP 连接(浏览器 6–8 条) | 单连接多路复用(Stream) | 单连接多路复用(QUIC Stream) |
| HTTP 层队头阻塞 | ❌ 存在 | ✅ 已解决 | ✅ 已解决 |
| TCP 层队头阻塞 | ❌ 存在 | ❌ 仍存在 | ✅ 已解决(流间独立可靠) |
| 首部压缩 | 无(每次全量发送) | HPACK(RFC 7541) | QPACK(RFC 9204,避免 HOL) |
| 加密 | 可选 | 事实强制(浏览器只支持 h2 over TLS) | 强制(QUIC 内嵌 TLS 1.3) |
| 建连 RTT | TCP 1-RTT(+TLS 1–2 RTT) | 同左 | 1-RTT,恢复时 0-RTT |
| 连接迁移 | ❌ 换网络必须重连 | ❌ | ✅ Connection ID 支持 Wi-Fi↔4G 无缝切换 |
| 服务端推送 | ❌ | Server Push(已被浏览器弃用) | 支持但同样少用;改用 103 Early Hints |
| 协议协商 | 默认 | TLS ALPN = h2;明文 h2c 需 Upgrade | Alt-Svc 头或 DNS HTTPS 记录通告 h3 |
HTTP vs 其他应用层协议
| 维度 | HTTP | FTP | WebSocket | gRPC | MQTT |
|---|---|---|---|---|---|
| 通信模型 | 请求-响应 | 命令-响应 + 独立数据连接 | 全双工消息 | 双向流式 RPC | 发布-订阅 |
| 连接数 | 1 条(复用) | 2 条(控制 21 + 数据 20/临时) | 1 条 | 1 条(HTTP/2) | 1 条 |
| 状态 | 无状态 | 有状态(当前目录、传输模式) | 有状态 | 有状态(流) | 有状态(会话+订阅) |
| 承载 | TCP / QUIC | TCP | TCP(HTTP 升级) | HTTP/2 | TCP |
| 端口 | 80 / 443 | 21 / 20 | 80 / 443 | 443 | 1883 / 8883 |
| 典型用途 | Web、REST API | 文件传输 | 实时推送、聊天 | 微服务间通信 | 物联网遥测 |
速查表 / 常见面试题
状态码速记表
| 码 | 名称 | 一句话 |
|---|---|---|
| 200 | OK | 成功且有正文 |
| 201 | Created | 创建成功,Location 指向新资源 |
| 204 | No Content | 成功但无正文(DELETE、PUT 常用) |
| 206 | Partial Content | 范围请求成功 |
| 301 | Moved Permanently | 永久重定向,浏览器会缓存 |
| 302 | Found | 临时重定向(历史实现会把 POST 变 GET) |
| 304 | Not Modified | 协商缓存命中 |
| 307 / 308 | Temporary / Permanent Redirect | 重定向且保留方法与请求体 |
| 400 | Bad Request | 请求格式错 |
| 401 | Unauthorized | 未认证(其实该叫 Unauthenticated) |
| 403 | Forbidden | 已认证但无权限 |
| 404 | Not Found | 资源不存在 |
| 405 | Method Not Allowed | 方法不被允许,须回 Allow |
| 409 | Conflict | 状态冲突(如乐观锁失败) |
| 429 | Too Many Requests | 限流,配 Retry-After |
| 500 | Internal Server Error | 服务端异常兜底 |
| 502 | Bad Gateway | 上游回了坏响应 |
| 503 | Service Unavailable | 服务不可用/过载 |
| 504 | Gateway Timeout | 上游超时未响应 |
常见面试题
GET 和 POST 的区别? 语义上:GET 是安全且幂等的读取,POST 是非幂等的提交。工程上:GET 参数在 URL(会被日志、浏览器历史、Referer 记录,长度受服务器限制),POST 在请求体;GET 可被缓存和收藏,POST 默认不可。注意"GET 不能带请求体"是误解——规范允许但语义未定义,多数实现会忽略。
HTTP 为什么是无状态的?有什么好处和代价? 无状态指服务器不保存请求间上下文。好处是任意节点可处理任意请求,天然可水平扩展、易做负载均衡和故障转移。代价是每次请求都要重复携带身份信息,需要 Cookie/Token 机制补足。
强缓存与协商缓存的区别? 强缓存(
max-age/Expires)命中时完全不发请求;协商缓存(ETag/Last-Modified+ 条件请求)会发请求但服务端可能回 304 不带正文。前者省 RTT,后者省带宽。no-cache和no-store有什么区别?no-cache允许缓存副本但每次使用前必须向服务器校验;no-store禁止任何形式的存储,用于密码、支付信息等敏感响应。301 和 302、307 和 308 怎么选? 301/308 永久(客户端会缓存并更新书签,SEO 传递权重),302/307 临时。307/308 严格保留原方法与请求体,301/302 在历史实现中会把 POST 改写为 GET。API 重定向应优先 307/308。
HTTP/2 解决了队头阻塞吗? 只解决了应用层的:单连接上多个流可乱序交错传输。但底层仍是 TCP,一旦某个 TCP 段丢失,内核必须等待重传才能向上层交付后续数据,所有流一起阻塞。彻底解决要靠 HTTP/3 的 QUIC——它在 UDP 上为每个流独立维护可靠性。
为什么 HTTP/3 要基于 UDP? 不是因为 UDP 快,而是因为 TCP 的实现固化在操作系统内核和大量中间设备中,任何改动都无法快速部署。QUIC 在用户态基于 UDP 重新实现了可靠传输、拥塞控制与加密,从而可以随应用一起迭代,并顺带获得了 0-RTT、连接迁移、流独立可靠性等能力。
502 和 504 如何区分定位? 502 是上游给出了响应但不合法或连接被拒/被重置——通常后端进程崩了或端口协议不对;504 是上游在超时窗口内什么都没回——通常后端在慢处理。前者查后端存活与协议,后者查后端耗时与网关超时配置。
Cookie 的 SameSite 三个值有何区别?
Strict完全不随跨站请求发送(安全但体验差,跳转过来会丢登录态);Lax(现代浏览器默认)允许顶层导航的安全方法(GET)携带;None允许所有跨站携带,但必须同时设Secure。主要用于缓解 CSRF。HTTPS 和 HTTP 的关系? HTTPS 不是独立协议,而是
HTTP over TLS。HTTP 语义完全不变,只是把字节流交给 TLS 加密后再交给 TCP。详见 14-https。