☰
深入解读 curl `--fail-with-body`:HTTP 出错时保留响应体并返回退出码 22
2026/10/10 19:04:58 网站建设 项目流程

深入解读 curl--fail-with-body:HTTP 出错时保留响应体并返回退出码 22

【免费下载链接】curlA command line tool and library for transferring data with URL syntax, supporting DICT, FILE, FTP, FTPS, GOPHER, GOPHERS, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS, MQTT, MQTTS, POP3, POP3S, RTSP, SCP, SFTP, SMB, SMBS, SMTP, SMTPS, TELNET, TFTP, WS and WSS. libcurl offers a myriad of powerful features项目地址: https://gitcode.com/GitHub_Trending/cu/curl

导读

--fail-with-body是 curl 命令行的 HTTP 选项,用于在服务器返回 4xx/5xx 错误响应时让 curl 以退出码 22 报告失败,同时仍然保留并输出服务器返回的错误响应正文。本篇文章围绕该选项的语义、与--fail的区别、源码级工作机制、脚本中的实战用法及测试验证展开,阅读后你将能够在自动化脚本与排障流程中精准控制"报错"与"留证"行为。

curl 默认如何看待 HTTP 状态码

HTTP 协议中,4xx/5xx 状态码表示服务器无法正常交付文档(如 404 Not Found、500 Internal Server Error),此时服务器通常会返回一段说明性质的正文(常常是 HTML,会描述出错原因及更多信息)。

关键认知:默认情况下,curl 不把 HTTP 状态码视为失败。也就是说,执行curl https://example.com/not-exist收到 404 时,命令依然以 0(成功)退出,把那段错误 HTML 原样输出到 stdout。这一默认行为在 fail.md 中也有明确说明:"By default, curl does not consider HTTP response codes to indicate failure."

脚本因此常常无法简单依据退出码判断 HTTP 层面是否出错——这正是--fail与--fail-with-body这类选项存在的意义。

--fail-with-body的语法与行为

该选项的官方定义文件位于 fail-with-body.md,其 front-matter 记录了完整的选项元数据:

属性值
长选项名--fail-with-body
短选项名无
适用协议HTTP
帮助文本Fail on HTTP errors but save the body(HTTP 出错时失败但保留正文)
分类http output
加入版本7.76.0
互斥选项fail
参数类型boolean(开关型,不带参数值)

核心语义

根据原文档(fail-with-body.md),该选项的作用是:

Return an error on server errors where the HTTP response code is 400 or greater. … This option allows curl to output and save that content but also to return error 22.

即:

  1. 当 HTTP 响应码大于等于 400时,curl 将本次传输判定为失败;
  2. 与"不保存内容"的失败模式不同,服务器返回的错误正文照常被输出/保存(例如输出到 stdout 或写入-o指定的文件);
  3. 命令最终返回错误码 22(对应 libcurl 的CURLE_HTTP_RETURNED_ERROR)。

因此,在需要"既知道请求失败了,又能拿到错误页正文用于分析根因"的场景(例如打印 CDN/网关错误页、留存 WAF 拦截响应)中,--fail-with-body是比--fail更合适的选择。

基本用法:

# 服务器返回 4xx/5xx 时命令退出码为 22,同时正文仍会输出 curl --fail-with-body https://example.com/does-not-exist

--fail-with-body不接收参数值,可与 URL 任意组合,也适用于多次传输的批量场景(front-matter 中Multi: boolean表示它可在同一命令中对每个 URL 独立生效)。

与--fail的区别及互斥关系

--fail(短选项-f,自 curl 4.0 时代就已存在,见 fail.md)针对完全相同的条件(HTTP 响应码 >= 400)让 curl 失败并返回错误 22,区别在于--fail会阻止正文输出:

Fail with error code 22 and with no response body output at all for HTTP transfers returning HTTP response codes at 400 or greater.

两者的行为对比:

行为--fail(-f)--fail-with-body
HTTP 响应码 >= 400 时失败是是
失败时返回退出码2222
是否输出/保存错误正文否(完全不输出)是(正文保留)
在命令行加入的版本4.07.76.0
实现层面通过 libcurl 的CURLOPT_FAILONERROR提前中止传输传输完整跑完后在工具层检查状态码再报错

互斥与告警

两个选项互为"互斥"(Mutexed: fail / fail-with-body)。同时指定时,后指定的会撤销先指定的,并打印告警。命令行解析逻辑位于 src/tool_getparam.c:

case C_FAIL: /* --fail without body */ if(toggle && (config->fail == FAIL_WITH_BODY)) warnf("--fail deselects --fail-with-body here"); config->fail = toggle ? FAIL_WO_BODY : FAIL_NONE; break; case C_FAIL_WITH_BODY: /* --fail-with-body */ if(toggle && (config->fail == FAIL_WO_BODY)) warnf("--fail-with-body deselects --fail here"); config->fail = toggle ? FAIL_WITH_BODY : FAIL_NONE; break;

例如执行curl --fail-with-body --fail URL,工具会提示Warning: --fail deselects --fail-with-body here,最终按--fail语义处理。该行为由测试 tests/data/test360 验证:该测试同时传入两个选项并断言 stderr 中出现上述警告。

源码级工作机制

状态枚举:三种"失败模式"

curl 工具层用一个枚举区分三种状态,定义在 src/tool_cfgable.h:

#define FAIL_NONE 0 #define FAIL_WITH_BODY 1 #define FAIL_WO_BODY 2
  • FAIL_NONE:默认状态,HTTP 状态码不视为失败;
  • FAIL_WITH_BODY:由--fail-with-body设置;
  • FAIL_WO_BODY:由--fail/-f设置。

选项名到枚举的映射在 src/tool_getparam.c 中登记,其中--fail对应短选项'f'、--fail-with-body无短选项:

{"fail", ARG_BOOL, 'f', C_FAIL}, {"fail-early", ARG_BOOL, ' ', C_FAIL_EARLY}, {"fail-with-body", ARG_BOOL, ' ', C_FAIL_WITH_BODY},

两条截然不同的实现路径

--fail(FAIL_WO_BODY)的落实位置在 libcurl 库层。工具把配置转成 libcurl 选项时(src/config2setopts.c)执行:

my_setopt_long(curl, CURLOPT_FAILONERROR, config->fail == FAIL_WO_BODY);

也就是说只有FAIL_WO_BODY才会把 libcurl 的CURLOPT_FAILONERROR置 1,使传输在库内被提前判定失败(见 lib/setopt.c 对CURLOPT_FAILONERROR的注释:"Do not output the >=400 error code HTML-page, but instead only return error")。底层判定函数是 lib/http.c 的http_should_fail()。

--fail-with-body(FAIL_WITH_BODY)则完全不同:它不设置CURLOPT_FAILONERROR,因此整个 HTTP 传输(包括错误正文的接收与写出)会正常跑完,正文被完整保存;随后在传输后置检查post_check_result()中补做状态码判定,见 src/tool_operate.c:

else if(config->fail == FAIL_WITH_BODY) { /* if HTTP response >= 400, return error */ long code = 0; curl_easy_getinfo(per->curl, CURLINFO_RESPONSE_CODE, &code); if(code >= 400) { if(!global->silent || global->showerror) curl_mfprintf(tool_stderr, "curl: (%d) The requested URL returned error: %ld\n", CURLE_HTTP_RETURNED_ERROR, code); return CURLE_HTTP_RETURNED_ERROR; } }

这段代码揭示了--fail-with-body的内部原理:

  1. 传输完成后,通过curl_easy_getinfo(per->curl, CURLINFO_RESPONSE_CODE, &code)取回最终 HTTP 响应码;
  2. 若code >= 400,向 stderr 打印curl: (22) The requested URL returned error: <状态码>;
  3. 返回CURLE_HTTP_RETURNED_ERROR,即退出码22。

因为检查发生在"后置"阶段,正文此时早已写入输出目标(stdout 或-o指定的文件),从而实现了"出错但留证"。相反,--fail的CURLOPT_FAILONERROR路径会在收到错误状态码时让 libcurl 提前中止并把整个响应当作失败丢弃(lib/http.c 等处),正文不再输出。

退出码 22 是什么

退出码 22 对应 libcurl 错误常量CURLE_HTTP_RETURNED_ERROR,定义于 include/curl/curl.h,其人类可读描述为 "HTTP response code said error"(见 lib/strerror.c)。历史上它还保留着别名CURLE_HTTP_NOT_FOUND(include/curl/curl.h)。

因此脚本中可统一判断:

curl --fail-with-body https://api.example.com/order/123 -o error_page.html if [ $? -eq 22 ]; then echo "HTTP 层请求失败,错误页已保存到 error_page.html 供排查" fi

libcurl 层CURLOPT_FAILONERROR的边界行为(理解差异的关键)

由于--fail-with-body走的是工具层后置检查(对>=400无条件报错),而--fail依赖库内CURLOPT_FAILONERROR的判定逻辑,两者在少数边界场景下行为并不完全等价。理解库层逻辑有助于避免误用。

http_should_fail()(lib/http.c)按序执行以下判断:

  • 未开启CURLOPT_FAILONERROR:一律不失败;
  • 响应码< 400:永不失败;
  • 断点续传场景:若此前用Range/resume发起 GET 且收到416,视为"文件已下载完"而非失败(lib/http.c);
  • 响应码>= 400且不是 401/407:一律判定失败;
  • 对于401(认证失败)/ 407(代理认证失败):需结合当前认证协商状态判断(lib/http.c),认证流程可能让这类状态码"放行"。

这也是 fail.md 特别提醒的原因:--fail"并非万无一失,尤其在涉及认证时(401 与 407)可能出现非成功响应码漏判"。如果你在脚本中必须对任何>=400响应(包括 401/407)一律报错,同时又要保留响应正文,--fail-with-body那套"跑完再查码"的后置逻辑反而是更直接、更可预期的选择。

实战:用--fail-with-body完成"出错留证"的请求

抓取并保存服务器错误页

很多网关、WAF 或反代在 4xx/5xx 时返回自定义错误页,正文里往往包含 trace/request-id 等排障线索。需要把这些正文留存:

curl --fail-with-body --silent --show-error \ https://api.example.com/users/42 \ -o response_body.txt
  • 成功(状态码 < 400):正文存入response_body.txt,退出码 0;
  • 失败(状态码 >= 400):正文同样存入response_body.txt,但退出码为 22,stderr 打印curl: (22) The requested URL returned error: 404。

对比--fail:遇到错误时正文不会被保存,response_body.txt将不存在或为空,无法事后分析。

检查头部/隐藏进度输出

可与其他选项自由组合,例如配合-i(输出含响应头)或--no-progress-meter以便于解析:

# 保留错误页正文的同时附带响应头,适合调试重定向与缓存策略 curl --fail-with-body --no-progress-meter -i https://cdn.example.com/path/resource

与--retry组合:重试后仍然失败时也能留证

--fail-with-body可与重试选项叠加。测试 tests/data/test1635 即验证了--retry 1 --fail-with-body在服务端返回 429(含Retry-After)并重试一次后的行为:重试期间每次 429 响应正文都会被接收,最终请求仍失败时退出码为 22。

# 遇到 429/5xx 自动重试一次,最终仍失败时保留最后一次错误正文 curl --fail-with-body --retry 1 --retry-delay 1 https://api.example.com/rate-limited

断言服务器"确实"出错

把"HTTP 状态码"转化为脚本可判定的退出码,是 CI/CD、监控探针中的常见需求:

if curl --fail-with-body --silent "http://internal.example.com/healthz" -o /dev/null; then echo "健康检查通过" else echo "健康检查失败(退出码 $?),已输出错误详情" >&2 fi

测试用例印证

仓库为--fail-with-body提供了多组回归测试,可作为理解其行为的权威参考:

测试文件验证内容
tests/data/test349服务端返回HTTP/1.0 404时执行--fail-with-body,断言退出码为22(<errorcode>22</errorcode>),且该测试能正常接收 404 正文
tests/data/test360同时传入--fail-with-body --fail,断言 stderr 出现Warning: --fail deselects --fail-with-body here(互斥告警)
tests/data/test361--fail-with-body在 HTTP 错误返回时连续两次(多 URL/重复传输场景)均正确报错
tests/data/test1635--retry 1 --fail-with-body与 429 +Retry-After的组合行为

相关选项与进一步阅读

  • fail.md:--fail/-f,报错但不输出正文的快速失败模式,及其"非万无一失"(401/407)的注意点;
  • fail-early.md:--fail-early,注意它解决的是另一个维度的问题——在发生一次传输错误时尽早终止整体操作,而非针对 HTTP 状态码;
  • libcurl 层面等价机制为CURLOPT_FAILONERROR,在 curl_easy_setopt 选项索引 中可检索到对应条目;
  • 想深入失败模式的完整实现,可分别阅读:
    • 工具层选项解析与互斥告警:src/tool_getparam.c、src/tool_cfgable.h;
    • 工具层后置状态码检查(--fail-with-body的核心):src/tool_operate.c;
    • 库层快速失败判定:CURLOPT_FAILONERROR的设置与消费分别在 src/config2setopts.c、lib/setopt.c 与 lib/http.c。

小结

--fail-with-body(curl 7.76.0 起可用)在--fail的基础上补上了"正文留证"的能力:它不干预传输过程,让服务器返回的错误页正常落盘,再于传输完成后用CURLINFO_RESPONSE_CODE检查状态码,对>=400的响应返回退出码 22(CURLE_HTTP_RETURNED_ERROR)。对需要自动化排障、抓取错误页根因信息的脚本而言,它比--fail更实用;理解它与CURLOPT_FAILONERROR库层判定的差异(尤其是 401/407 与 416 的边界处理),则能帮助你在具体场景中做出正确选择。

【免费下载链接】curlA command line tool and library for transferring data with URL syntax, supporting DICT, FILE, FTP, FTPS, GOPHER, GOPHERS, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS, MQTT, MQTTS, POP3, POP3S, RTSP, SCP, SFTP, SMB, SMBS, SMTP, SMTPS, TELNET, TFTP, WS and WSS. libcurl offers a myriad of powerful features项目地址: https://gitcode.com/GitHub_Trending/cu/curl

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询