Geonode logo
Geonode Team

Geonode Team

更新于:2026年10月7日

发布于:2026年9月2日

如何使用 curl 发送 POST 请求(附示例)

`curl -d "key=value" https://example.com` 发送一个 POST 请求。这就是整个问题的基本答案,也是人们接下来最常遇到的错误的根源。 `-d` 设置了表单编码的内容类型。如果使用该请求发送 JSON 数据却忽略了该头部字段,许多 API 会返回 415 错误并拒绝请求。 本指南涵盖了表单数据、JSON(包括大多数人不知道的快捷方式)、文件上传,以及如何判断在多种数据选项中你真正需要的是哪一种。

以下声明因与本文几乎无关,故简要说明:我们是 Geonode,主要销售代理服务。 **POST请求无需我们提供任何支持。**下文所述内容均基于您自身的网络连接,针对您自己的端点进行操作。代理服务器直到后文才会涉及,且文末有一段简短说明,指出当您通过代理发送POST请求时,唯一真正会发生变化的情况——这与大多数人的预期并不一致。

基础知识

curl -d "name=Ada&role=engineer" https://api.example.com/users

curl 手册 对 -d, --data

的描述如下:“将指定的数据发送至服务器。对于 HTTP(S),这通过 POST 方法实现,其工作原理与用户填写 HTML 表单并点击提交按钮时浏览器的行为相同。 此选项会使 curl 使用 content-type application/x-www-form-urlencoded

将数据传递给服务器。”

有两件事会自动发生,且都至关重要。

**-d

默认表示 POST。** 你不需要 -X POST

,添加它除了在重定向处理中(此时 -X

将应用于每个跳转节点)外,不会产生任何影响。

**-d

会设置 Content-Type: application/x-www-form-urlencoded

。** 对于表单提交而言这是正确的,但对于几乎所有其他情况都是错误的。

你可以重复执行 -d

,curl 会将各部分拼接起来:手册中指出“使用 -d name=daniel -d skill=lousy

会生成一个类似于 name=daniel&skill=lousy

的 POST 数据块。”

发送 JSON

这是最常见的实际应用场景,也是容易出错的地方。

显式方法:

curl -X POST https://api.example.com/users \
  -H "Content-Type: application/json" \
  -d '{"name":"Ada","role":"engineer"}'

大多数人未曾见过的快捷方式。 curl 提供了一个专用的 --json 选项,文档中说明如下:“通过 POST 请求将指定的 JSON 数据发送至 HTTP 服务器。--json 可作为以下三个选项的快捷方式:--data-binary [arg]、--header "Content-Type: application/json"、--header "Accept: application/json"。”

curl --json '{"name":"Ada","role":"engineer"}' https://api.example.com/users

三个选项合而为一,它不仅会设置 Accept,还会设置 Content-Type,这通常正是你所需要的。它还支持通过 @ 从文件或标准输入(stdin)读取数据:

curl --json @payload.json https://api.example.com/users
cat payload.json | curl --json @- https://api.example.com/users

手册中有一条诚实的警示:“无法验证传入的数据是否为真正的 JSON,也无法验证语法是否正确。”它仅设置请求头,并不进行验证。格式错误的请求体仍会以 JSON 内容类型发送,而服务器报错时指出的问题将是你的 JSON 本身,而非 curl。

它设置的头部“可以像往常一样通过 --header 覆盖”,因此你可以保留这个快捷方式,并调整其中的一部分。

**单引号很重要。**请用单引号包裹 JSON 正文,以免 shell 展开 $ 或解释其中的双引号。如果你的 JSON 还包含单引号,请将其写入文件中。

五种数据选项及其适用场景

这一部分将消除大多数困惑,因为 curl 提供了几种在具体方面有所不同的 --data 变体。

选项内容类型设置是否为 @ 特殊格式?换行符适用场景
-d / --dataform-urlencoded是,读取文件已移除表单提交
--data-rawform-urlencoded否已移除以 @ 开头的数据
--data-binaryform-urlencoded是保留文件,精确字节
--data-urlencodeform-urlencoded是编码包含特殊字符的值
--jsonapplication/json是保留JSON 正文

--data-raw 存在的唯一原因在于:手册中说明它“与 --data 类似地提交数据,但不会对 @ 字符进行特殊解释”。 如果您的原始数据以 @ 开头——例如电子邮件地址、用户名或提及——-d 会将其误读为文件名,从而导致令人困惑的错误。其官方示例为 curl --data-raw "@at@at@"。

--data-binary 才是用于上传文件的正确方法。 手册中写道:“请完全按照规定格式提交数据,绝不进行任何额外处理……换行符和回车符将被保留,且绝不进行任何转换。”请注意,它默认仍会发送 application/x-www-form-urlencoded,因此如果你要提交任意二进制数据,手册建议你覆盖此设置:-H "Content-Type: application/octet-stream"。

这就是为什么 -d @file.json 可能会出现细微的故障:换行符会被去除。 对于 JSON 而言,这通常无关紧要;但对于任何空格具有语义意义的情况,这就会造成影响。--data-binary @file.json 或 --json @file.json 是更安全的形式。

--data-urlencode 可处理包含 &、=、空格或其他可能破坏表单编码的值的表单。手册中记录了多种语法,而你需要的几乎总是 name=content,它会对内容进行 URL 编码,同时保留表单名称不变:

curl --data-urlencode "comment=hello & goodbye = fine" https://example.com/post

如果没有这个参数,& 会被识别为字段分隔符,导致您的评论被无声截断。此外还有 name@filename,该函数会从文件中加载内容,对其进行 URL 编码,并在名称后附加 =。

文件上传和多部分表单

对于实际的文件上传,应使用 -F,其工作原理与 -d 不同。

手册中写道:“-F, --form <name=content>……模拟用户已填写并点击提交按钮的表单。这使得 curl 能够根据 RFC 2388 规范,使用 Content-Type multipart/form-data 发送 POST 数据。”

curl -F "file=@report.pdf" -F "title=Q3 Report" https://api.example.com/upload

@和<之间的区别值得了解,因为这并不直观。手册中写道:“要强制将‘content’部分设为文件,请在文件名前添加@符号;要从文件中获取内容部分,请在文件名前添加<符号。 因此,@ 与 < 的区别在于:@ 会将文件作为文件上传附加到帖子中,而 < 则会创建一个文本字段,并从文件中获取该文本字段的内容。”

因此,@ 会将文件作为文件上传;< 则会将文件内容作为文本字段的值发送。

要为某个部分设置内容类型:

curl -F "file=@data.csv;type=text/csv" https://api.example.com/upload

若需要以 @ 或 < 开头的字面值,请使用 --form-string,该方法不会对这两个字符进行解释。

请勿手动设置 Content-Type: multipart/form-data。curl 会自动生成该值并包含边界参数,覆盖该值将导致服务器无法解析请求——这是引发原因不明的 400 和 415 错误的常见原因。

对于简单的文件 PUT 操作,-T 更为简便:“将指定的本地文件上传到远程 URL……如果此选项与 HTTP(S) URL 配合使用,则采用 PUT 方法。”

身份验证与头部信息

curl --json '{"a":1}' \
  -H "Authorization: Bearer eyJhbG..." \
  https://api.example.com/items

-H 该设置可重复使用,并会覆盖 curl 的默认值,包括 --json 设置的默认值。

对于基本身份验证,请使用 -u user:password —— 或仅使用 -u user ,这会促使 curl 弹出提示框,从而避免密码被记录在 shell 历史记录中。 手册中指出:“在支持该功能的系统上,curl 会将给定的选项参数从进程列表中隐藏”,同时补充道:“这不足以保护凭据”。

对于基于会话的 API,请捕获并复用 Cookie:

curl -c jar.txt -d "user=ada&pass=secret" https://example.com/login
curl -b jar.txt --json '{"a":1}' https://example.com/api/items

调试无法正常工作的 POST 请求

一个简短的操作流程,几乎能解决所有问题。

查看您实际发送的内容:

curl -v --json '{"a":1}' https://api.example.com/items 2>&1 | grep -E '^[<>]'

以 > 开头的行是您的请求,< 是响应。确认请求方法、Content-Type 和请求体是否与您的预期一致。出人意料的是,相当一部分“API 故障”的报告问题其实到此就解决了。

阅读状态码和错误正文:

curl -sS -o body.txt -D headers.txt --json '{"a":1}' https://api.example.com/items
head -1 headers.txt; head -c 300 body.txt

解读常见错误:

状态码通常表示
400请求正文格式错误或缺少必填字段
401凭据缺失或无效
403已认证但无权限
405该端点不接受 POST 请求 — 请检查 URL 和方法
413请求正文过大
415Content-Type
错误 — 典型的 JSON 格式错误(-d
)
422内容类型正确,但数据验证失败

415 与 422 的区别值得深入理解:415 表示包装错误,422 表示内容有误。 我们在 什么是 415 状态码 中对此进行了详细说明。

在脚本中突出显示错误:

curl --fail-with-body --silent --show-error \
     --connect-timeout 5 --max-time 30 \
     --json @payload.json https://api.example.com/items

--fail-with-body 在发生 HTTP 错误时会返回非零退出状态,同时仍会打印响应正文——当 API 返回有用的 JSON 错误消息时,这正是你所需要的。而简单的 --fail 会丢弃正文,从而忽略了错误说明。

将浏览器请求转换为 curl

这是重现“在浏览器中能正常工作但在代码中却无法工作”的 POST 请求的最快方法——而大多数人发现这一技巧时,往往已经为时已晚。

**从开发者工具中复制该请求。**在 Chrome、Firefox 和 Safari 中,打开“网络”标签页,找到该请求,右键点击并选择“复制为 cURL”。你将获得一条完整的命令,其中包含浏览器发送的每个头部、Cookie 和请求体。将其粘贴到终端中,其行为应与浏览器完全一致。

这立即解答了大多数调试过程中潜藏的核心问题:问题出在请求上,还是出在我的代码上?如果复制的命令能正常工作而你的却不行,那么问题就出在你发送的内容上,而现在你手头已有两个版本可以并排比较。

然后逐步精简。 复制的命令通常包含三十个头部,其中大部分与当前问题无关。分批删除这些头部并重新运行,直到请求失败为止。剩下的就是服务器实际所需的最小头部集,而这正是你应用程序中应该包含的内容:

curl 'https://api.example.com/items' \
  -H 'content-type: application/json' \
  -H 'authorization: Bearer eyJhbG...' \
  --data-raw '{"name":"Ada"}'

请注意,浏览器导出的地址是 --data-raw 而不是 -d,这正是因为如果正文以 @ 开头,否则会被误读为文件名。

请注意以下两点,它们在复制后将无法保留。 Cookie 会被作为字面量包含在请求头中,并会过期。此外,页面通过 JavaScript 计算出的任何内容——例如 CSRF 令牌、签名或基于时间戳生成的值——都会作为固定字符串嵌入到复制的请求中,因此只能使用一次,之后便无法再用。 如果复现的请求首次成功但第二次运行失败,原因几乎总是如此,解决方法是动态获取令牌,而不是将其硬编码。

反向操作方面,有多种工具可将 curl 命令转换为大多数编程语言的代码,这是一种合理的做法,可以从一个可运行的命令转换为可运行的客户端,而无需手动重新输入请求头。

通过代理发送 POST 请求

内容简短,重点在于一个注意事项,而非具体技术。

curl -x http://user:pass@proxy.example.com:9000 \
     --json '{"a":1}' https://api.example.com/items

操作机制保持不变。变化的是重试策略,这是在添加 --retry 之前值得思考的部分。

POST 请求通常不具备幂等性。 发送两次可能会生成两条记录。curl 的 --retry 默认仅在临时状况下触发,但 --retry-all-errors 则大大扩展了这一范围——而且通过代理时,5xx 错误通常意味着目标服务器拒绝了你的请求,而非服务器暂时出现故障。 重试这种请求,最好是徒劳无功,最坏则会导致写入重复。

**超时并不意味着失败。**如果请求在服务器接收后超时,操作可能已在您看到错误时完成。 通过代理时,会增加一个可能发生这种情况的中间节点。如果操作至关重要,请使用幂等键(大多数关键 API 都支持此功能),而不是依赖重试逻辑来确保安全。

并检查是哪一跳拒绝了你的请求。 %{http_connect} 会将代理对 CONNECT 请求的响应与目标服务器的状态分开报告:

curl -sS -o /dev/null -x "$PROXY" \
  -w 'connect=%{http_connect} status=%{response_code}\n' \
  --json '{"a":1}' https://api.example.com/items

connect=407 表示代理要求凭据。connect=200 status=403 表示代理正常工作,但目标服务器拒绝了请求。不同的问题,需要不同的解决方法。

大家还问

如何使用 curl 发送 POST 请求?

curl -d "key=value" URL。-d 选项默认表示 POST,因此无需使用 -X POST。它还会设置 Content-Type: application/x-www-form-urlencoded,这对表单提交是正确的,但对 JSON 而言则不正确。

如何使用 curl 发送 JSON POST 请求?

curl --json '{"key":"value"}' URL 是快捷方式——它会设置 --data-binary,并将 Content-Type 和 Accept 这两个头部都设为 application/json。 更完整的写法是 -X POST -H "Content-Type: application/json" -d '...'。请注意,--json 不会对你的 JSON 进行有效性验证。

为什么使用 curl 时会收到 415 Unsupported Media Type 错误?

几乎总是因为你在使用 -d 发送 JSON 请求体时未设置内容类型。-d 会发送表单编码格式,而期望接收 JSON 的 API 会拒绝它。请使用 --json,或添加 -H "Content-Type: application/json"。

-d 和 --data-raw 有什么区别?

-d 会将开头的 @ 视为“从该文件读取”。--data-raw 则不会,因此当你的字面数据以 @ 开头时(例如电子邮件地址或标识符),你需要使用 。除此之外,它们的行为完全相同。

如何使用 curl POST 上传文件?

curl -F "file=@document.pdf" URL,该命令会发送 multipart/form-data。使用 @ 将文件作为附件上传,使用 < 将文件内容作为文本字段值发送。请勿手动设置 Content-Type 头部——curl 会自动生成该头部并包含所需的边界字符。

如何从文件发送 POST 数据?

使用 curl --json @payload.json URL 发送 JSON 格式,或使用 --data-binary @file 发送包含换行符的精确字节数据。当空格位置重要时,请避免使用 -d @file,因为 -d 会去除换行符和回车符。

如何 POST 包含“&”符号的值?

请使用 --data-urlencode "field=value with & inside"。若使用普通的 -d,系统会将“&”符号识别为字段分隔符,并在此处无提示地截断您的值。

失败的 POST 请求是否应重试?

需谨慎处理。POST 请求通常不具备幂等性,因此重试可能会导致数据重复——而且超时并不意味着服务器未处理该请求。如果 API 支持幂等性键,请务必使用;对于 --retry-all-errors 请求需格外谨慎,特别是在通过代理服务器时,因为 5xx 状态码通常表示请求被拒绝,而非暂时的故障。

总结

整个话题归根结底就是一个问题:端点要求哪种内容类型,而你的命令是否发送了该内容类型?

-d 会发送表单编码(form-encoded)格式,这虽然适用于表单提交,但对于 JSON 却不合适——而这种单一的不匹配正是导致大多数人遇到 415 错误的根源。--json 才是应优先采用的选项,但它鲜为人知:仅需一个标志即可设置正文处理方式及两个头部,并支持 @ 格式处理文件和标准输入(stdin)。

除此之外,其他变体也因特定原因而存在,值得记住。当数据以 @ 开头时,请使用 --data-raw。当换行符很重要时,请使用 --data-binary。当值中包含会破坏表单编码的字符时,请使用 --data-urlencode。用于实际文件上传时,请使用 -F,其中 @ 用于附加文件,< 用于从文件中读取文本字段内容。

如果出现错误,请使用 -v 运行该代码,并在进行任何修改前先阅读 > 中的相关说明。您实际发送的请求往往与您认为自己发送的请求不一致,而这种差异正是该领域大多数困惑的根源所在。