curl--post301选项详解:让 POST 请求在 301 重定向后保持 POST 方法
【免费下载链接】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
--post301是 curl 命令行工具中用于控制 HTTP 重定向行为的关键选项:当服务器返回301 Moved Permanently且 curl 在--location模式下跟随重定向时,默认会按浏览器习惯把 POST 请求改写为 GET,而--post301可以强制 curl 遵循 RFC 7231 6.4.2 的规范,让 POST 在重定向后依然是 POST。本文结合 curl 仓库源码(lib/http.c、lib/setopt.c、include/curl/curl.h)与测试用例(tests/data/test1012、tests/data/test1054),完整讲解该选项的语义、默认行为、底层实现与 libcurl 编程等价物。
选项速览
--post301的完整定义位于 docs/cmdline-opts/post301.md,其命令行元数据如下:
| 属性 | 值 |
|---|---|
| Long 名称 | --post301 |
| Help 文本 | Do not switch to GET after a 301 redirect |
| 适用协议 | HTTP |
| 加入版本 | 7.17.1 |
| 类别 | http post |
| Multi | boolean(可多次出现,与其他布尔选项行为一致) |
| 相关选项 | --post302、--post303、--location |
| 官方示例 | --post301 --location -d "data" $URL |
该选项的官方语义为:遵循 RFC 7231 的 6.4.2 节,在跟随 301 重定向时不把 POST 请求转换为 GET 请求。文档同时指出,非 RFC 行为在 Web 浏览器中无处不在(几乎所有的浏览器都会在 301/302 后把 POST 改写为 GET),因此 curl 默认执行转换以与浏览器保持一致;但某些服务器可能要求 POST 在重定向后仍然是 POST,此时就需要--post301。该选项只有在配合--location(-L)使用时才有意义——没有重定向跟随,自然也就不存在"重定向前后方法切换"的问题。
背景:为什么 curl 默认把 POST 改成 GET
要理解--post301,必须先理解 curl 的重定向处理机制。当服务器返回 3xx 状态码并携带Location:响应头时,--location会让 curl 向新地址重新发起请求,参见 docs/cmdline-opts/location.md。
对于 301、302、303 这三类状态码,curl 的默认策略是:如果当前请求是 POST,则把后续请求切换为 GET;而对其他 3xx 状态码(如 307 Temporary Redirect、308 Permanent Redirect),curl 会用未修改的原始方法重新发送请求。--location文档中对此有明确描述:
When curl follows a redirect and if the request is a POST, it sends the following request with a GET if the HTTP response was 301, 302, or 303. If the response code was any other 3xx code, curl resends the following request using the same unmodified method.
这一默认行为的成因,在 lib/http.c 的Curl_http_follow函数中有详细注释:301/302 的历史语义允许用户代理将 POST 改为 GET,而现实中大量 Web 服务器期望这种转换,因此 libcurl 强制使用 GET,以便拿到与主流用户代理一致的结果。代码注释还指出,这一行为"被 RFC1945 和已废弃的 RFC2616 所禁止,但可以通过CURLOPT_POSTREDIR覆盖"。
301/302/303 与 307/308 的对比
| 状态码 | RFC 依据 | curl 默认行为 | 保持 POST 的选项 |
|---|---|---|---|
| 301 Moved Permanently | RFC 7231 6.4.2 | POST → GET | --post301 |
| 302 Found | RFC 7231 6.4.3 | POST → GET | --post302 |
| 303 See Other | RFC 7231 6.4.4 | POST → GET | --post303 |
| 307 Temporary Redirect | RFC 7231 6.4.7 | 保持原方法 | 无需选项 |
| 308 Permanent Redirect | RFC 7538 | 保持原方法 | 无需选项 |
有趣的是,--post301、--post302、--post303三个选项的措辞略有差异:前两者是"Respect RFC … and do not convert"(遵循 RFC,不转换),而--post303是"Violate RFC … and do not convert"(违反 RFC,不转换)——因为对于 303,RFC 规范本身就要求把方法改为 GET("See Other"表示 Location 指向的是原资源的替代展示,而非资源本身),所以强制保留 POST 反而是对规范的偏离,详见 docs/cmdline-opts/post303.md。
命令行用法与实战示例
基本用法
--post301是布尔开关,无参数值,与--location搭配使用:
# 跟随 301 重定向,且重定向后的请求仍然使用 POST curl --post301 --location -d "data" $URL # 简写形式 curl -L --post301 -d "data" $URL让所有常见重定向都保持 POST
如果要让 POST 在 301、302、303 三类重定向下都保持不变,可以组合使用三个选项:
curl --post301 --post302 --post303 --location -d "data" $URL与--request(-X)的交互
--location文档明确指出:用--request设置的方法会覆盖 curl 原本会选用的方法。也就是说,如果重定向后 curl 准备切换为 GET,但你在命令行上用-X POST强制指定了方法,那么后续请求仍会是 POST。在实际使用中,--post301与显式-X POST的效果可以叠加,但语义上--post301更精准——它只影响重定向场景,不会干扰其他请求。
一个完整的可复现示例
假设$URL指向一个返回301加Location:头的服务端点:
# 默认行为:第一次是 POST,跟随 301 后第二次请求变成 GET curl -v -L -d "moo" $URL # 使用 --post301:两次请求都是 POST,请求体 "moo" 会被重新发送 curl -v -L --post301 -d "moo" $URL源码级原理:--post301的完整调用链
--post301从命令行参数到实际生效,贯穿了 curl 的 CLI 层与 libcurl 库层,完整链路如下:
- 命令行解析:在 src/tool_getparam.c 中注册参数名
{"post301", ARG_BOOL, ' ', C_POST301},命中后在case C_POST301(src/tool_getparam.c)写入config->post301 = toggle,其中toggle由布尔参数的--post301/--no-post301形式决定。 - 配置存储:字段定义在 src/tool_cfgable.h 的
struct GlobalConfig中,为位域BIT(post301)。 - 转换为 libcurl 选项:在 src/config2setopts.c 中,
if(config->post301)将命令行配置映射为 libcurl 的CURLOPT_POSTREDIR,并 OR 上CURL_REDIR_POST_301位。 - libcurl 存储:lib/setopt.c 处理
CURLOPT_POSTREDIR,通过位运算拆解到三个独立的状态位:s->post301 = !!(arg & CURL_REDIR_POST_301)、s->post302、s->post303。这三个状态位定义在 lib/urldata.h。 - 重定向时生效:lib/http.c 的
Curl_http_follow在收到 301 响应后判断:if(HTTPREQ_IS_POST(data) && !data->set.post301)时才会调用http_switch_to_get(data, 301)把请求方法改为 GET;反之,当post301为真时跳过转换,保持 POST。
Curl_http_follow中的核心判断
在 lib/http.c 中,三个状态码的处理逻辑如下:
case 301: /* Moved Permanently */ if(HTTPREQ_IS_POST(data) && !data->set.post301) { http_switch_to_get(data, 301); switch_to_get = TRUE; } break; case 302: /* Found */ if(HTTPREQ_IS_POST(data) && !data->set.post302) { http_switch_to_get(data, 302); switch_to_get = TRUE; } break; case 303: /* See Other */ if(!HTTPREQ_IS_POST(data) || !data->set.post303) { http_switch_to_get(data, 303); switch_to_get = TRUE; } break;注意 303 分支的差异:--post303的语义是"即使规范要求切换为 GET 也要保持 POST",所以判断条件是!HTTPREQ_IS_POST(data) || !data->set.post303——只有当请求本身不是 POST,或者用户明确设置了post303时,才不进行方法切换。而HTTPREQ_IS_POST宏(lib/http.c)覆盖了三种 POST 形式:HTTPREQ_POST、HTTPREQ_POST_FORM(-F表单)与HTTPREQ_POST_MIME。
http_switch_to_get做了什么
当确实需要切换方法时,lib/http.c 的http_switch_to_get完成以下工作:
static void http_switch_to_get(struct Curl_easy *data, int code) { const char *req = CURL_EASY_STR(data, STRING_CUSTOMREQUEST); if((req ||>/* symbols to use with CURLOPT_POSTREDIR. CURL_REDIR_POST_301, CURL_REDIR_POST_302 and CURL_REDIR_POST_303 can be bitwise ORed so that CURL_REDIR_POST_301 | CURL_REDIR_POST_302 | CURL_REDIR_POST_303 == CURL_REDIR_POST_ALL */ #define CURL_REDIR_GET_ALL 0L #define CURL_REDIR_POST_301 1L #define CURL_REDIR_POST_302 2L #define CURL_REDIR_POST_303 4L #define CURL_REDIR_POST_ALL \ (CURL_REDIR_POST_301 | CURL_REDIR_POST_302 | CURL_REDIR_POST_303)对应的 C 代码写法:
CURL *curl = curl_easy_init(); curl_easy_setopt(curl, CURLOPT_URL, url); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, "data"); curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); /* 等价于命令行 --post301 */ curl_easy_setopt(curl, CURLOPT_POSTREDIR, CURL_REDIR_POST_301); /* 等价于 --post301 --post302 --post303 的组合 */ curl_easy_setopt(curl, CURLOPT_POSTREDIR, CURL_REDIR_POST_ALL);CURL_REDIR_GET_ALL(值 0)表示全部切换为 GET,即默认行为。注意 lib/setopt.c 会校验参数:当传入值小于CURL_REDIR_GET_ALL(即负数)时,curl_easy_setopt会返回CURLE_BAD_FUNCTION_ARGUMENT错误。
此外,lib/easyoptions.c 中还存在POST301这个别名,它同样映射到CURLOPT_POSTREDIR,供以选项名编程(如curl_easy_setopt的字符串形式或其他语言的绑定层)时使用。
测试用例验证
curl 仓库的集成测试直接验证了--post301的行为,可对照查看:
- tests/data/test1012:名为 "HTTP POST with 301 redirect and --post301",测试命令为
http://%HOSTIP:%HTTPPORT/blah/%TESTNUMBER -L -d "moo" --post301。其<protocol>校验段显示,重定向前后的两次请求都是 POST,且均携带Content-Length: 3与请求体moo,证明 301 后 POST 方法及请求体都被完整保留。 - tests/data/test1054:名为 "HTTP POST from file with 301 redirect and --post301",验证从文件读取请求体(
-d @file)的场景:命令-L -d @%LOGDIR/test%TESTNUMBER.txt --post301下,重定向前后两次请求同样是 POST,请求体field=data被重新发送。
这两个用例从客户端与服务端两侧共同确认:启用--post301后,curl 会向重定向目标重新发送完整的 POST 请求(包括请求体),而不是像默认那样降级为无请求体的 GET。
使用注意事项小结
--post301仅在配合--location/-L时生效,单独使用无任何效果;- 该选项只针对301状态码;302 用
--post302,303 用--post303,307/308 默认就保持原方法,无需处理; - 保持 POST 意味着请求体会被重新发送,因此上传数据必须可回卷(可重读);对一次性数据流(如某些实时管道输入),重定向跟随可能失败;
- 若服务器实际期望的是"保持方法"语义,也可以考虑直接改用 307/308 状态码,让服务端与客户端都获得更明确的语义,参见 lib/http.c 中引用的 RFC 原文;
- 在 libcurl 编程中,用
CURLOPT_POSTREDIR配合CURL_REDIR_POST_301(或按位 OR 组合)即可获得与命令行完全一致的行为。
相关文档索引
- --location 重定向跟随总览
- --post302:302 重定向后保持 POST
- --post303:303 重定向后保持 POST
- --request:覆盖请求方法
- --max-redirs:限制跟随重定向次数
- --location-trusted:跨主机传递凭据
- libcurl 重定向相关头文件定义
【免费下载链接】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),仅供参考