curl 与 HTTP 调试:请求构造、认证与排错套路

系统讲解 curl 与 HTTP 调试:请求构造(方法、头部、请求体、JSON 与表单)、认证与 Cookie 会话保持、超时与重试的失败语义、连接复用与 DNS 解析细节、TLS 与证书排查、上传下载与进度、代理与抓包(mitmproxy/tcpdump)、性能测量(-w 模板与 time_ 指标)、常见排错套路与速查表。

引言

curl 是 HTTP 世界的显微镜:一个 -v 就能看到 DNS 解析、TCP 连接、TLS 握手、请求头、响应头、重定向全链路。但很多人只把它当「下载工具」,遇到 502、超时、证书报错、Cookie 不生效时就束手无策。本文把 curl 当作调试仪器来讲:从请求构造讲到认证与 Cookie,从超时重试讲到连接复用与 DNS,再给出代理抓包、性能测量与一套可直接套用的排错 SOP,让你在「服务间调不通」时第一时间定位到层。

前置:字节与编码观察、命令行约定与退出码。状态码语义见 HTTP 状态码篇,网关层排查见 network 专题。


目录


1. curl 基础与详细输出调试

最小可用命令:

curl https://example.com              # GET,输出响应体到 stdout
curl -o page.html https://example.com # 存到文件;-O 用远端文件名;-s 静默;-I 只看头

-v 是核心调试开关:以 * 开头的是连接过程,> 是发出的请求,< 是收到的响应。

* Trying 93.184.216.34:443...          ← DNS 已解析,正在建 TCP
* Connected to example.com port 443    ← TCP 连上了
* TLS 1.3 connection using TLS_AES_128_GCM_SHA256   ← TLS 握手成功
> GET / HTTP/1.1  /  > Host: example.com             ← 发出的请求行与请求头
< HTTP/1.1 200 OK  /  < Content-Type: text/html      ← 响应状态行与响应头

分级详细度:-v(连接与头,最常用)、-vv(更多 TLS 细节)、--trace -(字节级追踪含 hex dump)、--trace-ascii -(ASCII 化更好读)、-s(静默,配合 -o /dev/null 只看退出码)。

退出码是脚本的第一信号:

0 成功;6 无法解析主机(DNS 失败);7 无法连接(端口拒绝/网络不通)
28 超时(--max-time / --connect-timeout);35 TLS 握手失败;60 证书校验失败

记忆:curl 是调试仪器——-v 看「DNS→TCP→TLS→请求→响应」全链路,退出码定位层(6 DNS、7 连接、28 超时、35 TLS、60 证书);脚本里用退出码而非解析文本判断成败。


2. 请求构造:方法、头部与请求体

方法:

curl -X GET    https://api.example.com/items
curl -X POST   https://api.example.com/items
curl -X PUT    https://api.example.com/items/1
curl -X PATCH  https://api.example.com/items/1     # 另有 DELETE / HEAD(或 -I)

头部:

curl -H 'Content-Type: application/json' -H 'Accept: application/json' \
     -H 'Authorization: Bearer TOKEN' -H 'X-Request-Id: 8f14e45f' https://api.example.com/items
curl -H 'User-Agent:' -H 'Expect:' https://example.com   # 删掉默认头

请求体:

curl -X POST https://api.example.com/items \
  -H 'Content-Type: application/json' -d '{"name":"widget","qty":3}'   # JSON
curl -X POST --data-binary @payload.json \
  -H 'Content-Type: application/json' https://api.example.com/items     # 从文件读
curl -F 'name=widget' -F 'file=@photo.jpg' https://api.example.com/upload   # 表单
curl -d 'name=widget&qty=3' https://api.example.com/items               # URL 编码

常用技巧:

--data-binary 保留原始字节(不改换行)——上传文件必用;-d @file 会去掉换行
--json 是简写:等价于 -d + Content-Type: application/json
-g 禁用 URL 的 glob 解析(URL 含 [] 时用);--url-query 拼查询参数(自动编码)
curl --json '{"name":"widget"}' https://api.example.com/items   # 或 --url-query 'q=hi' --url-query 'page=2'

记忆:方法用 -X、头用 -H、体用 -d/–data-binary(二进制必用)、表单用 -F、JSON 用 –json;URL 含方括号加 -g,查询参数用 –url-query 自动编码。


3. 认证、Cookie 与会话保持

认证方式:

curl -u user:pass https://api.example.com/private              # Basic
curl -H 'Authorization: Bearer eyJhbGci...' https://api.example.com/me   # Bearer
curl -H "Authorization: Bearer $TOKEN" https://api.example.com/me        # 环境变量
curl --cert client.crt --key client.key --cacert ca.pem https://mtls.example.com   # mTLS

Cookie 与会话:

curl -c cookies.txt https://example.com/login -d 'user=a&pass=b'   # 保存响应 Cookie
curl -b cookies.txt https://example.com/dashboard                  # 带 Cookie 发请求
curl -b 'session=abc123; theme=dark' https://example.com           # 内联 Cookie
curl -b cookies.txt -c cookies.txt https://example.com/next        # 读入并回写

Cookie 不生效的排查:

1. 域/路径不匹配 → Cookie 的 Domain/Path 不覆盖当前 URL
2. Secure → HTTPS 才发送,HTTP 调试时不带;过期 → Expires/Max-Age 已过
3. HttpOnly → JS 读不到(不影响 curl);SameSite → 跨站可能被浏览器丢弃
4. 多个同名 Cookie → 路径更具体的优先

登录流程调试套路:

# ① 拿 CSRF token / 会话
curl -c jar.txt -s https://app.example.com/login | grep -o 'csrf_token" value="[^"]*'
# ② 提交登录(带上 cookie 与 csrf)
curl -b jar.txt -c jar.txt -X POST https://app.example.com/login \
  -d 'user=admin&pass=secret&csrf_token=XXX'
# ③ 用会话访问受保护资源
curl -b jar.txt https://app.example.com/api/me

记忆:认证用 -u(Basic)/ -H Authorization(Bearer)/ –cert+–key(mTLS);Cookie 用 -c 存、-b 读,-b+-c 同文件模拟浏览器;Cookie 不生效先查 Domain/Path/Secure/过期,登录流程要「先取 CSRF 再带 Cookie 提交」。


4. 超时、重试与失败语义

没有超时的 curl 可能永远挂着,生产脚本必须显式设置。

curl --connect-timeout 5 --max-time 30 https://api.example.com/slow
curl --speed-limit 100 --speed-time 10 https://example.com/big   # 低速保护

超时的分层:

--connect-timeout 仅限制"建立连接"阶段(DNS + TCP + TLS)
--max-time        限制"整个请求"的总时长
--speed-limit/time 针对"传输过慢"(连上了但卡住)
→ 三个一起用才完整:连接快 + 总时长有上限 + 慢速能中断

重试:

curl --retry 3 --retry-delay 2 --retry-max-time 60 https://api.example.com/flaky
curl --retry 3 --retry-all-errors https://api.example.com/flaky   # 对全部错误重试(慎用)
curl --retry 3 --retry-connrefused https://api.example.com        # 连接被拒也重试
--retry 默认只对"瞬时错误"重试:超时、连接失败、5xx(部分)、HTTP/2 流错误
--retry-all-errors 才对全部错误重试(4xx 重试无意义)

失败语义与幂等:重试前必须确认「请求是否幂等」——GET/HEAD/PUT/DELETE 可重试,POST 重试可能重复下单(须配合幂等键)。超时 ≠ 失败:超时后请求可能已到达服务端并被处理,这也是为什么 POST 重试要幂等键。

用退出码驱动脚本:

if ! curl -fsS --max-time 10 https://api.example.com/health -o /dev/null; then
  echo "health check failed (exit $?)"; exit 1
fi
# -f 让 HTTP >=400 也返回非零退出码;-s 静默、-S 出错时仍打印错误

记忆:超时要三件套——–connect-timeout(连接)+ –max-time(总时长)+ –speed-limit/time(慢速中断);重试用 –retry 但要注意「超时≠失败、POST 不幂等」,脚本用 -f 让 4xx/5xx 触发非零退出码。


5. 连接复用、DNS 与 TLS 细节

连接复用(keep-alive):一次 curl 进程内多个 URL 会复用连接,但每次调用 curl 都是新连接。

curl -v https://example.com/a https://example.com/b https://example.com/c   # 复用连接
curl --no-keepalive https://example.com    # 强制新连接(排查连接池)

DNS 解析:

curl -s -o /dev/null -w '%{remote_ip}\n' https://example.com   # 只看解析到的 IP
curl --resolve example.com:443:10.0.0.5 https://example.com    # 指定 IP 但保留 Host/SNI
curl --connect-to example.com:443:10.0.0.5:443 https://example.com   # 指定连到哪
curl -4 https://example.com ; curl -6 https://example.com      # 强制 IPv4 / IPv6
--resolve 改"域名→IP"的映射(Host/SNI 不变)
--connect-to 改"连到哪"(更通用,可改端口)
两者都是排查"DNS 污染/负载均衡/灰度"的利器

TLS 细节:

curl -v https://example.com 2>&1 | grep -Ei 'SSL|TLS|certificate'   # 握手与证书概要
openssl s_client -connect example.com:443 -servername example.com </dev/null  # 看完整链
curl -k https://self-signed.example.com        # 忽略证书校验(仅调试!)
curl --cacert /path/ca.pem https://internal.example.com   # 自签/内部 CA
curl --cert c.crt --key c.key https://mtls.example.com    # 双向 TLS
curl --tlsv1.2 --tls-max 1.2 https://example.com          # 强制 TLS 版本(排查兼容)

HTTP 版本:--http1.1 / --http2 / --http3 强制版本,配合 -v 看实际协商结果。

记忆:连接复用只在同一 curl 调用内;–resolve/–connect-to 绕过 DNS 直连指定 IP(灰度演练利器);TLS 排查用 curl -v 看概要、openssl s_client 看全链、-k 仅调试、–cacert/–cert 处理自签与 mTLS。


6. 上传下载与进度控制

下载:

curl -C - -O https://example.com/big.iso              # 断点续传
curl --limit-rate 1M -O https://example.com/big.iso   # 限速 1MB/s
curl -r 0-1048575 -o part1.bin https://example.com/big.iso   # 分块(Range)
curl -L -O https://example.com/redirect-to-file       # 跟随重定向(默认不跟)
-L 跟随重定向:默认 curl 不跟,看到 301/302 就停
  排查时用 -v 看 Location,确认跳到哪

上传:

curl -T local.bin https://upload.example.com/remote.bin    # PUT 上传
curl -F 'file=@photo.jpg' -F 'desc=holiday' https://api.example.com/upload   # multipart
curl -X PUT -H 'Content-Range: bytes 0-1048575/10485760' --data-binary @part1.bin \
     https://upload.example.com/big                        # 分块上传

进度与静默:-#(进度条)、-sS(静默但保留错误输出)、--no-progress-meter(不显示进度表)。POST 重定向的坑:301/302 在旧实现下可能把 POST 变 GET(丢 body),303 强制变 GET,307/308 保持方法——curl -L 遇到 301/302 的 POST 需 --post301/--post302 显式保持方法。

记忆:下载用 -C - 续传、–limit-rate 限速、-r 分块、-L 跟随重定向(默认不跟);上传用 -T(PUT)或 -F(multipart);POST 遇 301/302 想保方法要加 –post301/–post302。


7. 代理与抓包排错

走代理:

curl -x http://127.0.0.1:8080 https://example.com        # HTTP/HTTPS 代理
curl --proxy socks5://127.0.0.1:1080 https://example.com # SOCKS5 代理
export https_proxy=http://127.0.0.1:8080                 # 环境变量方式
export no_proxy=localhost,127.0.0.1,.internal            # 内网直连
curl --noproxy '*' https://example.com                   # 临时忽略代理

用 mitmproxy 解密 HTTPS:

mitmproxy -p 8080        # 启动代理,浏览器/curl 走它即可看到明文
curl -x http://127.0.0.1:8080 --cacert ~/.mitmproxy/mitmproxy-ca-cert.pem https://example.com

tcpdump 抓原始包:

sudo tcpdump -i any -nn -A 'tcp port 443' | head -50          # 含 SNI,不解密
sudo tcpdump -i any -w out.pcap 'host 10.0.0.5 and port 8080' # 存 pcap 给 Wireshark

分层排错对照:

现象可能层验证手段
退出码 6DNSdig / --resolve 直连
退出码 7TCPnc -vz host port
退出码 35/60TLSopenssl s_client / -k 对比
卡住不返回网络/服务端--max-time + 看是否超时
200 但内容错应用/代理-v 看响应头(Via/X-Cache)
间歇失败连接池/多实例多次请求 + %{remote_ip}

记忆:代理用 -x 或 https_proxy 环境变量(no_proxy 排除内网);解 HTTPS 用 mitmproxy + 信任其 CA;抓包用 tcpdump -w 存 pcap 给 Wireshark;排错按退出码分层——6 DNS、7 TCP、35/60 TLS、卡住看超时。


8. 性能测量与输出模板

-w(write-out)能把各阶段耗时打印出来,是不装额外工具就能做的性能剖析。

curl -s -o /dev/null -w 'dns=%{time_namelookup}s connect=%{time_connect}s tls=%{time_appconnect}s
ttfb=%{time_starttransfer}s total=%{time_total}s http=%{http_code} ip=%{remote_ip}
size=%{size_download} speed=%{speed_download}B/s\n' https://example.com

各阶段含义:

time_namelookup DNS 解析 / time_connect TCP 连接 / time_appconnect TLS 握手
time_starttransfer 首字节到达(TTFB) / time_total 整个请求完成
→ 差值揭示瓶颈:namelookup 大 → DNS 慢;connect-namelookup 大 → TCP 慢(RTT)
   appconnect-connect 大 → TLS 慢;starttransfer-appconnect 大 → 服务端慢
   total-starttransfer 大 → 传输慢

把它做成模板:把 -w 的内容写进 /tmp/fmt.txt,用 -w @/tmp/fmt.txt 复用;多轮测量用 for i in $(seq 1 10); do curl -s -o /dev/null -w '%{time_total} %{http_code}\n' URL; done | sort -n | head -3 看最快几轮、排除抖动。

常用 write-out 变量:%{http_code}(状态码)、%{remote_ip}(实际连接 IP,查多实例/负载均衡)、%{num_connects}(新建连接次数,>1 说明未复用)、%{size_download}、%{speed_download}、%{ssl_verify_result}(0 为成功)。

记忆:-w 是内置的性能剖析——time_namelookup/connect/appconnect/starttransfer/total 的差值直接指出瓶颈(DNS/TCP/TLS/服务端/传输);%{remote_ip} 查多实例、%{num_connects} 查是否复用。


9. 常见排错套路

SOP 一:请求打不通

① curl -v 看停在哪一步:停在 Trying → DNS 或 TCP(--resolve 直连排除 DNS)
   停在 TLS → 证书/协议(openssl s_client 看链)
   有请求无响应 → 服务端/超时(加 --max-time 看是否超时)
② nc -vz host port 确认端口可达
③ 换 --resolve 直连具体 IP,判断是否负载均衡/某实例故障

SOP 二:状态码异常

4xx → 检查方法/路径/头/体/凭证(-v 看完整请求);401 换 token、403 查权限
5xx → 看响应头是否来自网关(Server/Via/X-Cache)
      502/504 用 --resolve 直连后端实例,绕过网关定位
429 → 看 Retry-After,确认限流维度(IP/Token/路径)

SOP 三:性能慢:用 -w 拆解阶段耗时——ttfb 大则服务端慢;total-ttfb 大则传输慢;connect 大则网络 RTT 高或握手重传;namelookup 大则 DNS 慢。

SOP 四:结果不稳定:多次请求对比 %{remote_ip}(是否命中不同实例)、%{num_connects}(是否未复用);用 --no-keepalive 排除 keepalive 被中间设备切断。

SOP 五:POST 后异常:-v 看是否有 3xx 重定向(-L 是否把 POST 变 GET),加 --post301/--post302 保持方法;超时重发致重复则检查服务端幂等键。

# 通用武器:一次看全状态码 + 各阶段耗时 + IP + 是否复用
curl -sS -o /dev/null -w 'code=%{http_code} ip=%{remote_ip} conns=%{num_connects} t=%{time_total}\n' URL

记忆:排错五套路——打不通看 -v 停在哪步 + nc 验端口 + –resolve 直连;状态码异常分 4xx/5xx 并看响应头来源;性能慢用 -w 拆阶段;结果不稳对比 %{remote_ip}/%{num_connects};POST 异常查重定向与幂等。


10. 速查表与一句话记忆

需求命令
看连接全过程 / 只看响应头curl -v URL / curl -I URL
发 JSON / 表单上传curl --json '{"a":1}' URL / curl -F 'file=@x.jpg' URL
保存 / 带 Cookiecurl -c jar.txt URL / curl -b jar.txt URL
认证-u user:pass / -H 'Authorization: Bearer T'
超时 / 重试--connect-timeout 5 --max-time 30 / --retry 3 --retry-delay 2
跟随重定向 / 断点续传-L / -C - -O
直连指定 IP / 忽略证书--resolve host:443:IP / -k(仅调试)
走代理 / 性能剖析-x http://127.0.0.1:8080 / -w '%{time_total}'
脚本判断-fsS + 退出码

一句话记忆:curl 是 HTTP 显微镜——-v 看「DNS→TCP→TLS→请求→响应」全链路,退出码定位层(6 DNS、7 连接、28 超时、35/60 TLS 与证书);请求构造用 -X/-H/-d/–data-binary/-F/–json;认证 -u 或 Authorization 头、Cookie 用 -c 存 -b 读;超时必配三件套(–connect-timeout + –max-time + –speed-limit)、重试要记得「超时≠失败、POST 不幂等」;–resolve/–connect-to 直连 IP 排查 DNS 与多实例,TLS 用 openssl s_client 看全链、-k 仅调试;-w 的 time_ 差值直接指出瓶颈;排错按「停在哪步 + 状态码来源 + 阶段耗时」三步走——把 curl 当仪器用,而不是当下载器用。


延伸阅读

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「others」更多文章

  1. 语义化版本与依赖解析:从 SemVer 规则到依赖地狱治理
  2. 图像与媒体工具链:ImageMagick、ffmpeg 与格式选型实战
  3. 国际化与本地化处理:locale、LC_ 变量与 ICU 实践