强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

curl 深度教程 / 第 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_namelookupDNS 解析时间t1
time_connectTCP 连接时间t2(含 DNS)
time_appconnectTLS 握手时间t3(含 DNS+TCP)
time_pretransfer传输准备时间t3(略大于 appconnect)
time_starttransferTTFB(首字节时间)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 hostDNS 解析失败检查域名、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 errorTLS 读取错误检查 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

注意事项

  1. -v 输出到 stderr:不会污染 stdout 管道,但需要 2>&1 捕获
  2. –trace 包含二进制数据:始终输出到文件,不要直接显示
  3. 敏感信息泄露:调试输出可能包含 Token、Cookie 等,注意清理
  4. 性能影响--trace-vvv 会降低性能,仅用于调试
  5. 生产环境:使用 -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 测试与自动化。