第 12 章:调试与诊断
第 12 章:调试与诊断
当请求"不工作"时,你需要的是信息,而不是猜测。curl 提供了从概览到逐字节的全方位调试能力。
12.1 详细输出(-v / -vvv)
-v 是 curl 最常用的调试选项,它显示请求和响应的头部信息。
基本使用
# -v:显示请求/响应头和 TLS 信息
curl -v https://example.com
# -vv:更详细(显示 TLS 握手细节)
curl -vv https://example.com
# -vvv:最详细(显示每一步的字节级细节)
curl -vvv https://example.com
# 将 -v 输出保存到文件(-v 输出到 stderr)
curl -v https://example.com 2>debug.log
理解 -v 输出
curl -v https://example.com 2>&1
# === 第一阶段:DNS 解析 ===
# * Host example.com:443 was resolved.
# * IPv6: 2606:2800:220:1:248:1893:25c8:1946
# * IPv4: 93.184.216.34
# === 第二阶段:TCP 连接 ===
# * Trying 93.184.216.34:443...
# * Connected to example.com (93.184.216.34) port 443
# === 第三阶段:TLS 握手 ===
# * ALPN: curl offers h2, http/1.1
# * TLSv1.3 (OUT), TLS handshake, Client hello (1):
# * TLSv1.3 (IN), TLS handshake, Server hello (2):
# * SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
# * Server certificate:
# * subject: CN=example.com
# * start date: Jan 15 00:00:00 2024 GMT
# * expire date: Apr 15 23:59:59 2024 GMT
# * issuer: C=US; O=Let's Encrypt; CN=R3
# * SSL certificate verify ok.
# === 第四阶段:HTTP 请求 ===
# > GET / HTTP/1.1
# > Host: example.com
# > User-Agent: curl/8.5.0
# > Accept: */*
# === 第五阶段:HTTP 响应 ===
# < HTTP/1.1 200 OK
# < Content-Type: text/html; charset=UTF-8
# < Content-Length: 1256
# < Date: Sat, 10 May 2026 12:00:00 GMT
# === 第六阶段:响应体 ===
# <html>...(省略)...</html>
分离请求头和响应头
# 仅显示发送的请求头
curl -v https://example.com 2>&1 | grep "^>"
# 仅显示接收的响应头
curl -v https://example.com 2>&1 | grep "^<"
# 显示 TLS 相关信息
curl -v https://example.com 2>&1 | grep "^\*"
12.2 –trace 详细追踪
--trace 提供比 -v 更详细的逐字节输出。
基本使用
# 输出到文件(二进制安全)
curl --trace debug.log https://example.com
# 输出到 stdout(十六进制 + ASCII)
curl --trace - https://example.com
# 输出到 stderr
curl --trace /dev/stderr https://example.com
–trace 输出解读
== Info: Connected to example.com (93.184.216.34) port 443
=> Send SSL data, 5 bytes (0x5)
0000: 16 03 01 00 dc .....
=> Send header, 98 bytes (0x62)
0000: 47 45 54 20 2f 20 48 54 54 50 2f 31 2e 31 0d 0a GET / HTTP/1.1..
0010: 48 6f 73 74 3a 20 65 78 61 6d 70 6c 65 2e 63 6f Host: example.co
0020: 6d 0d 0a 55 73 65 72 2d 41 67 65 6e 74 3a 20 63 m..User-Agent: c
...
<= Recv header, 17 bytes (0x11)
0000: 48 54 54 50 2f 31 2e 31 20 32 30 30 20 4f 4b 0d HTTP/1.1 200 OK.
0010: 0a .
–trace-ascii 更友好的追踪
# --trace-ascii:仅显示 ASCII 文本,更易读
curl --trace-ascii debug.txt https://example.com
# 输出示例:
# => Send header, 98 bytes (0x62)
# 0000: GET / HTTP/1.1. Host: example.com. User-Agent: curl/8.5.0. Accept:
# 0040: */*.
追踪特定阶段
# 仅追踪 SSL/TLS 通信
curl --trace-ssl ssl_debug.log https://example.com
# 使用环境变量控制调试级别
CURL_DEBUG=ssl,http curl https://example.com
# 可用模块:ssl, http, tcp, dns, all
# 追踪 DNS 解析
CURL_DEBUG=dns curl https://example.com
12.3 时间分析
使用 -w 进行时间测量
# 显示完整的请求时间分解
curl -o /dev/null -s -w "\
DNS 解析: %{time_namelookup}s\n\
TCP 连接: %{time_connect}s\n\
TLS 握手: %{time_appconnect}s\n\
首字节时间: %{time_starttransfer}s\n\
总耗时: %{time_total}s\n\
---\n\
下载速度: %{speed_download} bytes/s\n\
下载大小: %{size_download} bytes\n\
HTTP 状态码: %{http_code}\n\
" https://example.com
时间各阶段含义
时间线图:
|<-- DNS -->|<-- TCP -->|<-- TLS -->|<-- 服务器处理 -->|<-- 数据传输 -->|
0 t1 t2 t3 t4 t5
t1: time_namelookup DNS 解析完成
t2: time_connect TCP 连接建立
t3: time_appconnect TLS 握手完成
t4: time_starttransfer 收到第一个字节(TTFB)
t5: time_total 传输完成
| 变量 | 含义 | 公式 |
|---|---|---|
time_namelookup | DNS 解析时间 | t1 |
time_connect | TCP 连接时间 | t2(含 DNS) |
time_appconnect | TLS 握手时间 | t3(含 DNS+TCP) |
time_pretransfer | 传输准备时间 | t3(略大于 appconnect) |
time_starttransfer | TTFB(首字节时间) | t4 |
time_total | 总时间 | t5 |
# 计算各阶段增量
curl -o /dev/null -s -w '%{json}\n' https://example.com | jq '{
"DNS 解析": (.time_namelookup * 1000 | round | tostring + "ms"),
"TCP 连接": ((.time_connect - .time_namelookup) * 1000 | round | tostring + "ms"),
"TLS 握手": ((.time_appconnect - .time_connect) * 1000 | round | tostring + "ms"),
"服务器处理": ((.time_starttransfer - .time_appconnect) * 1000 | round | tostring + "ms"),
"数据传输": ((.time_total - .time_starttransfer) * 1000 | round | tostring + "ms"),
"总耗时": (.time_total * 1000 | round | tostring + "ms")
}'
批量测试与统计
#!/bin/bash
# benchmark.sh - 简单的性能基准测试
URL="${1:-https://example.com}"
RUNS="${2:-10}"
echo "测试 URL: $URL"
echo "运行次数: $RUNS"
echo "---"
total_ttfb=0
total_time=0
for i in $(seq 1 $RUNS); do
result=$(curl -o /dev/null -s -w '%{time_starttransfer} %{time_total}' "$URL")
ttfb=$(echo "$result" | awk '{print $1}')
total=$(echo "$result" | awk '{print $2}')
total_ttfb=$(echo "$total_ttfb + $ttfb" | bc)
total_time=$(echo "$total_time + $total" | bc)
printf "第 %2d 次: TTFB=%.3fs, 总时间=%.3fs\n" "$i" "$ttfb" "$total"
done
avg_ttfb=$(echo "scale=3; $total_ttfb / $RUNS" | bc)
avg_time=$(echo "scale=3; $total_time / $RUNS" | bc)
echo "---"
echo "平均 TTFB: ${avg_ttfb}s"
echo "平均总时间: ${avg_time}s"
12.4 连接诊断
DNS 诊断
# 查看 DNS 解析过程
curl -v https://example.com 2>&1 | grep -E "Host|resolved|Trying"
# 使用 --resolve 强制指定 IP(绕过 DNS)
curl --resolve example.com:443:93.184.216.34 https://example.com
# 强制使用 IPv4
curl -4 https://example.com
# 强制使用 IPv6
curl -6 https://example.com
# 使用自定义 DNS 服务器(需 c-ares 支持)
curl --dns-servers 8.8.8.8,1.1.1.1 https://example.com
# DNS 缓存诊断:连续请求看是否有缓存
for i in 1 2 3; do
echo "--- 第 $i 次 ---"
curl -o /dev/null -s -w "DNS: %{time_namelookup}s\n" https://example.com
done
TCP 连接诊断
# 查看连接详情
curl -v https://example.com 2>&1 | grep -E "Trying|Connected|connect"
# 绑定到特定网络接口
curl --interface eth0 https://example.com
# 指定本地端口
curl --local-port 5000-5100 https://example.com
# 连接超时诊断
curl --connect-timeout 3 https://slow-server.example.com 2>&1
# 如果超时,会显示:
# curl: (28) Connection timed out after 3001 milliseconds
SSL/TLS 诊断
# 查看 TLS 握手详情
curl -vvv https://example.com 2>&1 | grep -i -E "ssl|tls|cert|cipher|alpn"
# 检查特定 TLS 版本是否支持
for ver in tlsv1 tlsv1.1 tlsv1.2 tlsv1.3; do
echo -n "$ver: "
curl --$ver -s -o /dev/null -w "%{http_code}" https://example.com 2>/dev/null || echo "不支持"
echo
done
# 使用 openssl 补充诊断
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \
| grep -E "Protocol|Cipher|Verify"
# 检查证书链完整性
echo | openssl s_client -connect example.com:443 -showcerts 2>/dev/null \
| grep -E "Certificate chain|depth|verify"
12.5 协议调试
HTTP/2 调试
# 强制使用 HTTP/2 并查看协商过程
curl -v --http2 https://example.com 2>&1 | grep -i "ALPN\|HTTP/2\|http/1.1"
# 输出示例:
# * ALPN: curl offers h2, http/1.1
# * ALPN: server accepted h2
# * using HTTP/2
# 检查服务器是否支持 HTTP/2
curl -sI --http2 https://example.com 2>&1 | head -3
# HTTP/2 200
# 比较 HTTP/1.1 和 HTTP/2 性能
echo "--- HTTP/1.1 ---"
curl -o /dev/null -s -w "Total: %{time_total}s\n" --http1.1 https://example.com
echo "--- HTTP/2 ---"
curl -o /dev/null -s -w "Total: %{time_total}s\n" --http2 https://example.com
HTTP/3 调试
# 检查 HTTP/3 支持(需要 curl 编译时启用 HTTP/3)
curl -v --http3 https://example.com 2>&1 | grep -i "HTTP/3\|QUIC"
# 如果不支持 HTTP/3
# curl: (1) Unsupported protocol
代理调试
# 调试代理连接
curl -v -x http://proxy.example.com:8080 https://target.example.com 2>&1
# 调试 SOCKS 代理
curl -v --socks5 127.0.0.1:1080 https://example.com 2>&1
# 查看 CONNECT 隧道建立过程
curl -v -p -x http://proxy.example.com:8080 https://target.example.com 2>&1
# 输出:
# > CONNECT target.example.com:443 HTTP/1.1
# > Host: target.example.com:443
# < HTTP/1.1 200 Connection established
12.6 常见错误排查
错误诊断速查表
| 错误信息 | 含义 | 排查步骤 |
|---|---|---|
Could not resolve host | DNS 解析失败 | 检查域名、DNS 设置 |
Connection refused | 服务未监听 | 检查端口、服务状态 |
Connection timed out | 连接超时 | 检查防火墙、网络 |
SSL certificate problem | 证书验证失败 | 检查证书、CA 证书 |
SSL peer certificate was not given | 服务器未提供证书 | 检查 TLS 配置 |
The requested URL returned error: 403 | 禁止访问 | 检查认证、权限 |
The requested URL returned error: 404 | 资源不存在 | 检查 URL |
Recv failure: Connection reset | 连接被重置 | 检查防火墙、TLS |
GnuTLS recv error | TLS 读取错误 | 检查 TLS 版本/密码套件 |
Unacceptable file | 服务器不接受 | 检查 Accept 头 |
系统化排查流程
#!/bin/bash
# diagnose.sh - 系统化诊断 curl 请求问题
URL="$1"
echo "=== 基本信息 ==="
curl --version | head -1
echo -e "\n=== DNS 解析 ==="
host "${URL#https://}" | head -5
echo -e "\n=== TCP 连接 ==="
host="${URL#https://}"
host="${host#http://}"
host="${host%%/*}"
timeout 5 bash -c "echo > /dev/tcp/${host}/443" 2>/dev/null \
&& echo "TCP 端口 443 可达" || echo "TCP 端口 443 不可达"
echo -e "\n=== TLS 握手 ==="
echo | timeout 5 openssl s_client -connect "${host}:443" -servername "$host" 2>/dev/null \
| grep -E "Protocol|Cipher|Verify"
echo -e "\n=== HTTP 请求 ==="
curl -v --connect-timeout 10 --max-time 30 "$URL" 2>&1 | head -50
echo -e "\n=== 时间分析 ==="
curl -o /dev/null -s -w "DNS: %{time_namelookup}s\nTCP: %{time_connect}s\nTLS: %{time_appconnect}s\nTTFB: %{time_starttransfer}s\nTotal: %{time_total}s\n" "$URL"
12.7 与 Wireshark/tcpdump 配合
# 使用 tcpdump 抓包,同时执行 curl
# 终端 1:抓包
sudo tcpdump -i any -w capture.pcap host example.com and port 443
# 终端 2:执行请求
curl https://example.com
# 停止抓包后用 Wireshark 分析
wireshark capture.pcap
# 仅抓 TLS 握手
sudo tcpdump -i any -w tls.pcap host example.com and port 443 -c 50
# 实时查看 HTTP 头(需要 HTTP 解密或明文 HTTP)
sudo tcpdump -i any -A host example.com and port 80
注意事项
- -v 输出到 stderr:不会污染 stdout 管道,但需要
2>&1捕获 - –trace 包含二进制数据:始终输出到文件,不要直接显示
- 敏感信息泄露:调试输出可能包含 Token、Cookie 等,注意清理
- 性能影响:
--trace和-vvv会降低性能,仅用于调试 - 生产环境:使用
-v而非--trace,信息量足够且开销小
# 安全地分享调试日志(去除敏感信息)
curl -v https://api.example.com/data 2>&1 \
| sed 's/Authorization: .*/Authorization: [REDACTED]/' \
| sed 's/Cookie: .*/Cookie: [REDACTED]/' \
> safe_debug.log
扩展阅读
📖 下一章:第 13 章:API 测试 — 使用 curl 进行 REST、GraphQL、gRPC API 测试与自动化。