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"
指定 URIhttp.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-Typehttp.content_type contains "json"
带 Cookie 的请求http.cookie
服务端下发 Cookiehttp.set_cookie
响应耗时(服务器慢)http.time > 1
非 80 端口的 HTTPhttp && tcp.port == 8080
HTTP/2http2;只看首部帧 http2.type == 1;数据帧 http2.type == 0
HTTP/3http3;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 大)。
  • HostLocation:重定向循环时对照这两个字段,看是不是 HTTP↔HTTPS 或带斜杠↔不带斜杠来回跳。
  • Content-Length 与实际字节:不一致会导致客户端挂起等待。
  • Connection: close:如果每个请求后都出现,说明连接复用失效,性能会明显下降。
  • X-Forwarded-For / Via:判断请求经过了几层代理,以及后端看到的客户端 IP 是否正确。

tcpdump 命令行抓包

这组命令能干什么:在没有图形界面的服务器上抓包。第一条存盘留证,第二条直接在终端里把 HTTP 明文打出来,第三条只挑 GET 请求,最后一条把抓包文件里的请求汇总成一张表。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 抓 80 端口全部流量存盘(-s 0 抓完整包)
tcpdump -i any -s 0 -w http.pcap 'tcp port 80'

# 直接在终端看 HTTP 明文(-A 以 ASCII 打印)
tcpdump -i any -A -s 0 'tcp port 80 and (((ip[2:2] - ((ip[0]&0xf)<<2)) - ((tcp[12]&0xf0)>>2)) != 0)'

# 只抓 GET 请求(匹配 TCP 载荷前 4 字节 "GET ")
tcpdump -i any -A 'tcp port 80 and tcp[((tcp[12:1] & 0xf0) >> 2):4] = 0x47455420'

# tshark 提取请求汇总
tshark -r http.pcap -Y http.request -T fields -e frame.time -e http.host -e http.request.method -e http.request.uri

常用命令 / 配置

curl —— 排查 HTTP 的瑞士军刀

这组命令能干什么:几乎所有 HTTP 问题的第一现场。看真实收发了什么、跟踪每一跳重定向、伪造任意方法和头部、绕过 DNS 直连某台后端、把一次请求的耗时拆成 DNS/TCP/TLS/首字节四段——慢在哪一步一目了然。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# 查看完整交互(含请求头、响应头、TLS 信息)
curl -v https://example.com

# 只看响应头
curl -I https://example.com # 发 HEAD
curl -s -o /dev/null -D - https://example.com # 发 GET 但只打印头

# 跟随重定向并打印每一跳
curl -vL https://example.com 2>&1 | grep -E '^< (HTTP|Location)'

# 指定方法与 JSON 正文
curl -X POST https://api.example.com/v1/users \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer eyJhbGci...' \
  -d '{"name":"alice"}'

# 表单提交与文件上传
curl -X POST -d 'user=alice&pass=secret' https://example.com/login
curl -F 'file=@./report.pdf' -F 'desc=月报' https://example.com/upload

# 强制指定 HTTP 版本(验证服务端支持情况)
curl -v --http1.1 https://example.com
curl -v --http2 https://example.com
curl -v --http3 https://example.com

# 绕过 DNS 直连指定后端(灰度/故障隔离必备)
curl -v --resolve api.example.com:443:10.0.0.5 https://api.example.com/health

# 带 Cookie 会话
curl -c cookies.txt -d 'user=alice&pass=x' https://example.com/login
curl -b cookies.txt https://example.com/dashboard

# 范围请求 / 断点续传
curl -r 0-1023 -o part1 https://example.com/big.iso
curl -C - -O https://example.com/big.iso

# 测试压缩是否生效
curl -H 'Accept-Encoding: gzip' -I https://example.com | grep -i content-encoding

# 分阶段耗时统计(精确定位慢在哪一步)
curl -w '\nDNS: %{time_namelookup}s\nTCP: %{time_connect}s\nTLS: %{time_appconnect}s\nTTFB: %{time_starttransfer}s\nTotal: %{time_total}s\n' \
  -o /dev/null -s https://example.com

# 模拟 CORS 预检
curl -X OPTIONS https://api.example.com/v1/users -i \
  -H 'Origin: https://web.example.com' \
  -H 'Access-Control-Request-Method: POST' \
  -H 'Access-Control-Request-Headers: content-type,authorization'

手工敲 HTTP(理解协议的最佳练习)

这组命令能干什么:不用任何 HTTP 客户端库,纯手工把一条请求"打"进 TCP 连接里。这是验证"请求行 + 首部 + 空行"结构最直观的方式——少敲那个空行,服务器就会一直等着不回你。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 用 nc 手打一条请求,注意最后必须有一个空行
printf 'GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n' | nc example.com 80

# 交互式(输完两次回车)
nc example.com 80
GET / HTTP/1.1
Host: example.com

# HTTPS 场景用 openssl 代替 nc
printf 'GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n' \
  | openssl s_client -quiet -connect example.com:443 -servername example.com

连接状态与压测

这组命令能干什么:从"连接"和"压力"两个角度看服务健康度。前两条看谁在听、有多少活跃连接,第三条专治 TIME_WAIT 堆积,后面几条用不同压测工具打出吞吐和延迟分布,最后一条起个临时服务器验证客户端行为。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# 查看监听与连接状态
ss -ltnp | grep ':80'
ss -tan state established '( dport = :80 or sport = :80 )' | wc -l

# 统计各 TCP 状态数量(TIME_WAIT 堆积排查)
ss -tan | awk 'NR>1{print $1}' | sort | uniq -c | sort -rn

# 压测
ab -n 10000 -c 100 -k https://example.com/ # ApacheBench,-k 开启 keep-alive
wrk -t4 -c200 -d30s --latency https://example.com/ # wrk,输出延迟分布
hey -n 5000 -c 50 -m POST -D body.json https://api.example.com/v1/users

# 快速起一个静态服务器验证客户端行为
python3 -m http.server 8080

常见故障与排错

结论先行:一眼看懂每个现象在说什么

  • 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 RequestHost 头;首部超长(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-cacheno-store 混淆);漏配 Vary;CDN 缓存键不含 querycurl -ICache-Control/ETag/Age;CDN 侧看 X-Cache: HIT/MISS
返回了别的站点的内容Host 头缺失或错误,命中了服务器的默认虚拟主机curl -H 'Host: 正确域名' http://IP/;检查 Nginx default_server 配置
重定向循环 ERR_TOO_MANY_REDIRECTSHTTP→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-alivess -tan 统计;客户端与网关开启长连接(Nginx upstream { keepalive 64; }
HTTP/2 未生效(仍是 1.1)ALPN 未协商成功;Nginx 未加 http2 参数;明文场景浏览器不支持 h2ccurl -v --http2 https://…ALPN, server accepted h2nginx -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
传输层TCPTCPQUIC 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)
建连 RTTTCP 1-RTT(+TLS 1–2 RTT)同左1-RTT,恢复时 0-RTT
连接迁移❌ 换网络必须重连✅ Connection ID 支持 Wi-Fi↔4G 无缝切换
服务端推送Server Push(已被浏览器弃用)支持但同样少用;改用 103 Early Hints
协议协商默认TLS ALPN = h2;明文 h2c 需 UpgradeAlt-Svc 头或 DNS HTTPS 记录通告 h3

HTTP vs 其他应用层协议

维度HTTPFTPWebSocketgRPCMQTT
通信模型请求-响应命令-响应 + 独立数据连接全双工消息双向流式 RPC发布-订阅
连接数1 条(复用)2 条(控制 21 + 数据 20/临时)1 条1 条(HTTP/2)1 条
状态无状态有状态(当前目录、传输模式)有状态有状态(流)有状态(会话+订阅)
承载TCP / QUICTCPTCP(HTTP 升级)HTTP/2TCP
端口80 / 44321 / 2080 / 4434431883 / 8883
典型用途Web、REST API文件传输实时推送、聊天微服务间通信物联网遥测

速查表 / 常见面试题

状态码速记表

名称一句话
200OK成功且有正文
201Created创建成功,Location 指向新资源
204No Content成功但无正文(DELETE、PUT 常用)
206Partial Content范围请求成功
301Moved Permanently永久重定向,浏览器会缓存
302Found临时重定向(历史实现会把 POST 变 GET)
304Not Modified协商缓存命中
307 / 308Temporary / Permanent Redirect重定向且保留方法与请求体
400Bad Request请求格式错
401Unauthorized未认证(其实该叫 Unauthenticated)
403Forbidden已认证但无权限
404Not Found资源不存在
405Method Not Allowed方法不被允许,须回 Allow
409Conflict状态冲突(如乐观锁失败)
429Too Many Requests限流,配 Retry-After
500Internal Server Error服务端异常兜底
502Bad Gateway上游回了坏响应
503Service Unavailable服务不可用/过载
504Gateway Timeout上游超时未响应

常见面试题

  1. GET 和 POST 的区别? 语义上:GET 是安全且幂等的读取,POST 是非幂等的提交。工程上:GET 参数在 URL(会被日志、浏览器历史、Referer 记录,长度受服务器限制),POST 在请求体;GET 可被缓存和收藏,POST 默认不可。注意"GET 不能带请求体"是误解——规范允许但语义未定义,多数实现会忽略。

  2. HTTP 为什么是无状态的?有什么好处和代价? 无状态指服务器不保存请求间上下文。好处是任意节点可处理任意请求,天然可水平扩展、易做负载均衡和故障转移。代价是每次请求都要重复携带身份信息,需要 Cookie/Token 机制补足。

  3. 强缓存与协商缓存的区别? 强缓存(max-age/Expires)命中时完全不发请求;协商缓存(ETag/Last-Modified + 条件请求)会发请求但服务端可能回 304 不带正文。前者省 RTT,后者省带宽。

  4. no-cacheno-store 有什么区别? no-cache 允许缓存副本但每次使用前必须向服务器校验;no-store 禁止任何形式的存储,用于密码、支付信息等敏感响应。

  5. 301 和 302、307 和 308 怎么选? 301/308 永久(客户端会缓存并更新书签,SEO 传递权重),302/307 临时。307/308 严格保留原方法与请求体,301/302 在历史实现中会把 POST 改写为 GET。API 重定向应优先 307/308。

  6. HTTP/2 解决了队头阻塞吗? 只解决了应用层的:单连接上多个流可乱序交错传输。但底层仍是 TCP,一旦某个 TCP 段丢失,内核必须等待重传才能向上层交付后续数据,所有流一起阻塞。彻底解决要靠 HTTP/3 的 QUIC——它在 UDP 上为每个流独立维护可靠性。

  7. 为什么 HTTP/3 要基于 UDP? 不是因为 UDP 快,而是因为 TCP 的实现固化在操作系统内核和大量中间设备中,任何改动都无法快速部署。QUIC 在用户态基于 UDP 重新实现了可靠传输、拥塞控制与加密,从而可以随应用一起迭代,并顺带获得了 0-RTT、连接迁移、流独立可靠性等能力。

  8. 502 和 504 如何区分定位? 502 是上游给出了响应但不合法或连接被拒/被重置——通常后端进程崩了或端口协议不对;504 是上游在超时窗口内什么都没回——通常后端在慢处理。前者查后端存活与协议,后者查后端耗时与网关超时配置。

  9. Cookie 的 SameSite 三个值有何区别? Strict 完全不随跨站请求发送(安全但体验差,跳转过来会丢登录态);Lax(现代浏览器默认)允许顶层导航的安全方法(GET)携带;None 允许所有跨站携带,但必须同时设 Secure。主要用于缓解 CSRF。

  10. HTTPS 和 HTTP 的关系? HTTPS 不是独立协议,而是 HTTP over TLS。HTTP 语义完全不变,只是把字节流交给 TLS 加密后再交给 TCP。详见 14-https