curl:把一次 HTTP 故障压缩成可重复命令
先确认正在运行哪一个 curl
浏览器访问正常,CI 却报证书错误,第一反应不该是加 -k。Windows 自带 curl、Git for Windows、容器镜像和 Linux runner 可能使用不同版本、TLS 后端、CA 路径与代理配置。先把运行事实留下:
command -v curl
curl --version
curl --manual | headcurl --version 会列出协议、Features 和 TLS 库。HTTP/2、HTTP/3、HTTPS proxy、SSPI 或证书存储能力是否存在,要看这份输出,不能从操作系统名称推断。团队脚本若依赖较新的 --fail-with-body、--retry-all-errors 等选项,应固定 runner 镜像并在入口校验版本。
一条最小请求先证明传输链
第一次调用只观察,不急着加入真实认证:
curl --silent --show-error \
--connect-timeout 3 --max-time 10 \
--dump-header response.headers \
--output response.json \
--write-out '%{http_code} %{remote_ip} %{time_connect} %{time_appconnect} %{time_starttransfer}\n' \
https://api.example.test/health这里分别留下状态码、远端地址、TCP 建连、TLS 握手和首字节时间。--connect-timeout 只约束连接阶段,--max-time 才约束整次传输。只保留总耗时会把 DNS、代理、TLS 和服务处理混成一句“接口慢”。
需要观察请求头时用 --verbose;需要精确协议字节和时间戳时用 --trace-time --trace-ascii。两者都可能记录 Cookie、Authorization、客户端证书路径和响应正文,输出必须进入临时受控目录,分享前逐项脱敏。
用正反实验定位差异
同一目标至少做三组对照。先去掉认证,确认服务稳定返回 401 或 403;再注入临时测试凭证,观察请求是否进入业务处理。证书实验使用组织 CA 建立成功基线,再换成空 CA 或错误主机名验证失败路径。网络路径则分别直连受控测试入口和经过代理调用,对比 remote_ip、证书链与分阶段计时。
JSON 请求放进文件,减少 shell 引号差异和历史泄漏:
curl --silent --show-error --fail-with-body \
--connect-timeout 3 --max-time 15 \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer ${API_TOKEN}" \
--data-binary @request.json \
https://api.example.test/orders--fail-with-body 让 HTTP 400+ 返回非零退出码,同时保留诊断正文。它仍不能判断 200 中的业务错误;关键字段必须再由 jq、契约测试或专用脚本断言。
代理与证书是两条可独立失败的链
企业环境中,curl 可能先与 HTTPS 代理建立 TLS,再由代理隧道连接目标服务。代理证书、服务证书、客户端证书不是同一个对象:
curl --proxy https://proxy.example.test:8443 \
--proxy-cacert corp-proxy-ca.pem \
--cacert service-ca.pem \
--cert client.pem --key client.key \
https://api.example.test/private优先修复信任链,不把 --insecure 写进文档、别名或流水线。若必须为隔离实验跳过验证,要同时记录原因、目标、负责人和删除时间;实验结论不得当作正式验收。
环境变量 HTTP_PROXY、HTTPS_PROXY、NO_PROXY 会改变实际路径。排障材料至少记录变量是否存在和脱敏后的域名范围。NO_PROXY 的匹配语义还可能随实现与版本不同,不能用浏览器“直连成功”证明 curl 一定走相同路径。
退出码只是故障层,不是根因
| 退出码 | 首先检查 | 还需组合的证据 |
|---|---|---|
5 / 6 | 代理或目标 DNS | resolver、VPN、代理解析方式 |
7 | TCP 或代理连接 | 端口、监听、路由、安全策略 |
22 | 启用了 fail 且 HTTP 为 400+ | 响应 body、request id、应用日志 |
28 | 达到超时条件 | 分阶段计时、网关与服务耗时 |
35 / 60 | TLS 握手或服务证书验证 | SNI、SAN、中间证书、CA 来源 |
脚本必须显式传递 curl 的退出状态。PowerShell 使用 $LASTEXITCODE;Bash 可用 set -euo pipefail,但管道后仍要确认希望传播哪一个程序的状态。跨平台共享请求文件、预期响应和证据格式,比强行共享一份 shell 脚本更可靠。
把 smoke 收进项目,而不是个人历史
仓库可保留 scripts/api-smoke/、无秘密的 request.json、.env.example 和固定 runner 镜像。每次提交只运行少量只读接口,合并后再扩大契约与回归范围。设置总时限、并发和重试上限;只有确认幂等的请求才能自动重试。
排障结束后删除 body、header、trace、Cookie jar、临时 CA 和客户端证书副本,撤销临时 token。凭证若进入命令参数、终端历史、日志或 Git,应按泄漏处理并轮换,不能只删文件。
升级时用同一只读请求双跑旧版与新版,比较 curl --version、TLS 后端、状态、计时和退出码。失败回退 runner 镜像或工具版本,不回退为跳过证书、取消超时或忽略退出状态。
官方资料:curl 文档总览、curl 手册、HTTP scripting。
