☰
curl 发送 POST JSON 与 GET 查询参数实战指南
2026/10/20 4:38:31 网站建设 项目流程

1. 从一次接口联调说起:为什么curl比Postman更值得练

很多人第一次接触接口调试,都是从图形化工具开始的。点几下、填几个框、选一下请求方法,请求就发出去了,看起来很方便。但我在带新人的过程中发现一个很普遍的现象:用惯了图形化工具的人,一旦换到服务器环境、容器里、或者需要把请求写进脚本自动化执行的时候,立刻就卡住了。因为那些环境里根本没有图形界面,你唯一能依靠的就是命令行工具。

curl就是这样一个绕不开的东西。它几乎预装在所有主流的 Linux 发行版和 macOS 上,Windows 10 之后的系统也自带了 curl。它不依赖任何运行时环境,一条命令就能把 HTTP 请求发出去,还能把响应原封不动地打印出来。对于后端开发、运维、测试来说,curl 是真正意义上的"通用语言"。

这篇文章我聚焦两件最常用、也最容易出错的事:用 curl 发送带 JSON 参数的 POST 请求,以及用 curl 发送带查询参数的 GET 请求。别看这两个动作简单,里面涉及的内容类型声明、引号转义、参数编码、响应处理等细节,坑一点都不少。我会把每个参数为什么这么写、不这么写会出什么问题,都掰开讲清楚。适合刚接触接口调试的新手,也适合想把自己命令行的老手查漏补缺。

先给一个最直观的对比。下面这条是发送 JSON POST 请求的最小可用命令:

curl -X POST https://api.example.com/v1/users \ -H "Content-Type: application/json" \ -d '{"name":"张三","age":28}'

看起来就三行,但每一行都有讲究。-X POST指定方法,-H声明内容类型,-d携带请求体。接下来我会逐层拆开,把每个部分背后的逻辑讲透。

2. POST 请求携带 JSON 参数:从最小命令到生产级写法

2.1 Content-Type 为什么必须是 application/json

这是新手最容易忽略、也最容易导致"请求发出去了但服务端报错"的地方。HTTP 请求体本身只是一串字节,服务端怎么知道这串字节该按什么格式解析?答案就在Content-Type请求头里。

如果你不写这个头,curl 默认会带上Content-Type: application/x-www-form-urlencoded。这时候服务端会尝试把你的 JSON 字符串当成表单数据来解析,结果就是字段全部丢失或者解析失败,返回一个 400 或者参数为空的响应。我见过太多次"我明明发了 JSON,为什么后端说没收到参数",九成都是这个头没写对。

所以记住一条铁律:只要请求体是 JSON,就必须显式声明Content-Type: application/json。写法上,-H后面跟的字符串里,冒号后面建议留一个空格,虽然 HTTP 规范允许不留,但留空格可读性更好,也避免某些服务端解析器对格式敏感。

2.2 -d 参数的引号陷阱:单引号、双引号与转义

-d是--data的简写,用来指定请求体内容。这里最大的坑在于 shell 的引号处理机制。

在 Linux/macOS 的 bash 或 zsh 里,单引号内的内容会被原样传递,不做任何变量替换。所以下面这种写法是最安全的:

-d '{"name":"张三","age":28}'

JSON 本身用双引号包裹键和字符串值,外层用单引号,两者不冲突,干净利落。

但如果你在 Windows 的 CMD 里,情况就不一样了。CMD 不认单引号作为字符串边界,你得用双引号包裹整个 JSON,然后对内部的每个双引号做转义:

curl -X POST https://api.example.com/v1/users ^ -H "Content-Type: application/json" ^ -d "{\"name\":\"张三\",\"age\":28}"

注意 Windows CMD 的换行符是^,不是反斜杠。如果你用的是 PowerShell,转义规则又变了,PowerShell 里反引号是转义符,处理 JSON 会非常痛苦。我的建议是:在 Windows 上做接口调试,优先用 Git Bash 或者 WSL,能省掉大量转义的麻烦。

还有一个隐蔽的坑:如果你的 JSON 里包含单引号(比如某个字段值是it's),那外层就不能用单引号了,得换成双引号并转义内部双引号,或者用$'...'这种 bash 的 ANSI-C 引用方式。这种边界情况在实际业务里并不罕见,尤其是处理英文文本内容的时候。

2.3 从文件读取请求体:-d @filename 的实战价值

当 JSON 参数很长、嵌套层级很深的时候,把它塞在命令行里既难读又容易出错。这时候可以把 JSON 写到一个文件里,然后用@符号引用:

curl -X POST https://api.example.com/v1/orders \ -H "Content-Type: application/json" \ -d @order.json

order.json文件内容就是标准的 JSON,不需要任何转义。这个方式在自动化脚本里特别有用,因为你可以用程序动态生成 JSON 文件,再交给 curl 发送,职责分离得很清楚。

这里有个细节要注意:-d @filename会把文件内容原样作为请求体,但它不会自动设置 Content-Type,所以你依然要手动加-H。另外,如果文件名以@开头,需要用--data @./@file这种方式规避歧义。

2.4 用 --data-binary 保留原始字节

-d有一个不太为人知的行为:它会去掉请求体里的换行符。对于大多数紧凑的 JSON 来说无所谓,但如果你发送的是格式化过的多行 JSON,或者发送的是二进制数据、文件上传,-d就会破坏内容。

这时候要用--data-binary:

curl -X POST https://api.example.com/v1/upload \ -H "Content-Type: application/json" \ --data-binary @payload.json

--data-binary会原封不动地发送文件内容,包括换行和所有字节。我在做文件哈希校验、签名验证这类对字节敏感的场景时,一律用--data-binary,避免因为换行被吞导致签名对不上。

2.5 一个完整的生产级 POST 示例

把上面的要点组合起来,一个健壮的 POST 请求大概长这样:

curl -X POST "https://api.example.com/v1/users" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "Accept: application/json" \ --data-binary @user.json \ --connect-timeout 10 \ --max-time 30 \ -w "\nHTTP状态码: %{http_code}\n耗时: %{time_total}s\n" \ -o response.json

这里额外加了几个生产环境常用的参数:--connect-timeout控制连接超时,--max-time控制整个请求的最大耗时,避免脚本卡死;-w用来在请求结束后打印状态码和耗时,方便日志记录;-o把响应体写入文件,而不是直接刷屏。这些参数在写自动化脚本时几乎是标配,建议养成习惯。

3. GET 请求的参数拼接:查询字符串的正确姿势

3.1 最朴素的写法与它的隐患

GET 请求把参数放在 URL 的查询字符串里,最基本的写法就是直接把参数拼在 URL 后面:

curl "https://api.example.com/v1/users?page=1&size=20&keyword=test"

注意这里整个 URL 用双引号包起来了。为什么?因为 URL 里的&在 shell 里是"后台执行"的意思,不加引号的话,shell 会把&后面的内容当成另一条命令,请求就发不完整了。这是新手最常踩的坑之一,症状是"请求只发了一半参数"或者"命令执行后卡住不动"。

所以第一条经验:只要 URL 里带&,就必须用引号包裹整个 URL。单引号双引号都行,单引号更保险,因为它不做变量替换。

3.2 用 --data-urlencode 配合 -G 自动编码

手动拼接参数有个大问题:特殊字符需要 URL 编码。比如参数值里有空格、中文、&、=、#这些字符,直接拼进去会破坏 URL 结构。空格会被截断,&会被当成参数分隔符,#后面的内容会被当成锚点丢弃。

curl 提供了一个非常优雅的解决方案:用-G把-d的参数转成查询字符串,同时用--data-urlencode自动做 URL 编码:

curl -G "https://api.example.com/v1/search" \ --data-urlencode "keyword=张三 李四" \ --data-urlencode "category=技术&开发" \ --data-urlencode "page=1"

-G的作用是告诉 curl:把后面-d系列参数拼成查询字符串,用 GET 方法发送。--data-urlencode会自动把参数值里的特殊字符编码成%XX形式。上面那个keyword=张三 李四会被正确编码成keyword=%E5%BC%A0%E4%B8%89%20%E6%9D%8E%E5%9B%9B,服务端能正确解析。

这个写法我强烈推荐,尤其是参数值来自用户输入或者包含中文的时候。手动编码既费时又容易出错,交给 curl 处理最省心。

3.3 中文参数的编码原理与验证方法

说到中文编码,稍微展开讲一下原理,理解了就不容易慌。URL 里只能出现 ASCII 字符,中文属于非 ASCII,必须先转成字节序列再编码。以 UTF-8 为例,"张"这个字的 UTF-8 字节是E5 BC A0,每个字节前面加%就变成%E5%BC%A0。所以一个中文参数编码后会变得很长,这是正常的。

如果你不确定编码对不对,可以用-v参数看 curl 实际发出的请求:

curl -v -G "https://api.example.com/v1/search" \ --data-urlencode "keyword=张三"

-v会打印出完整的请求行,你能看到GET /v1/search?keyword=%E5%BC%A0%E4%B8%89 HTTP/1.1这样的内容,一眼就能确认编码是否正确。这个技巧在排查"服务端收到的中文是乱码"这类问题时特别有用。

3.4 GET 请求也能带请求头

虽然 GET 请求通常不需要请求体,但请求头是照常可以带的。比如需要鉴权、需要指定接受的数据格式:

curl -G "https://api.example.com/v1/users" \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "Accept: application/json" \ --data-urlencode "page=1" \ --data-urlencode "size=20"

这里有个容易混淆的点:GET 请求里用-d配合-G时,-d的内容会变成查询字符串,而不是请求体。如果你不加-G,-d就会让 curl 自动把方法变成 POST。所以-G和-d是一对搭档,缺一不可。

4. 响应处理与调试:让 curl 的输出真正有用

4.1 只看响应体、只看响应头、两者都要

curl 默认只输出响应体。但调试的时候,响应头里的状态码、Content-Type、Set-Cookie 往往更关键。控制输出的参数有这么几个:

参数作用适用场景
默认只输出响应体拿数据、管道给其他程序
-i输出响应头 + 响应体需要看状态码和头信息
-I只输出响应头(发 HEAD 请求)快速检查资源是否存在
-v输出完整通信过程深度排查连接、TLS、重定向问题
-s静默模式,不显示进度条脚本里用,输出干净

我个人的习惯是:日常调试用-i,脚本里用-s,遇到诡异问题用-v。-v的输出会包含>开头的发送内容和<开头的接收内容,能清楚看到 curl 到底发了什么、服务端回了什么,是排查问题的终极武器。

4.2 用 -w 定制输出格式,把状态码和耗时打出来

-w(write-out)是 curl 里被低估的一个功能。它允许你在请求结束后,按自定义格式打印一些元信息。常用的变量有:

  • %{http_code}:HTTP 状态码
  • %{time_total}:总耗时(秒)
  • %{time_connect}:TCP 连接耗时
  • %{size_download}:下载字节数
  • %{content_type}:响应的 Content-Type

一个实用的组合:

curl -s -o /dev/null -w "状态码:%{http_code} 耗时:%{time_total}s 大小:%{size_download}字节\n" \ "https://api.example.com/v1/health"

-o /dev/null把响应体丢弃,只保留-w的输出。这条命令特别适合做健康检查或者接口性能摸底,输出干净利落,方便脚本解析。

4.3 处理重定向与超时

默认情况下,curl 不会跟随 3xx 重定向,只会把重定向响应原样返回。如果你希望它自动跟随,加-L:

curl -L "https://api.example.com/v1/redirect-endpoint"

但要注意,-L在 POST 请求上有个坑:当重定向目标是 301/302 时,很多客户端会把方法改成 GET,请求体也会丢失。curl 的行为取决于具体版本和--post301、--post302这些参数。如果你的接口依赖重定向后仍保持 POST,务必显式加上--post301 --post302 --post303。

超时方面,前面提过--connect-timeout和--max-time。我的经验值是:连接超时设 5 到 10 秒,总超时根据接口正常响应时间乘以 3 来设。比如一个正常 200ms 返回的接口,总超时设 5 秒就够,设太长反而会让故障排查变慢。

4.4 把响应交给 jq 做格式化

服务端返回的 JSON 通常是一整行,人眼很难读。这时候用jq管道处理一下,立刻清爽:

curl -s "https://api.example.com/v1/users?page=1" | jq .

jq .会把 JSON 格式化并高亮。如果只想取某个字段:

curl -s "https://api.example.com/v1/users/1" | jq '.data.name'

这个组合在调试接口时几乎是必备的。jq需要单独安装,主流包管理器里都有,装一次受用很久。

5. 那些年我踩过的 curl 坑:一份排错清单

5.1 参数丢失、乱码、请求方法不对

这三个问题占了 curl 使用问题的绝大多数,我把它们整理成一张对照表,遇到时直接查:

现象最可能的原因解决办法
服务端收不到参数Content-Type 没设或设错加-H "Content-Type: application/json"
中文变乱码没做 URL 编码用--data-urlencode配合-G
请求只发了一半URL 里的&没被引号包裹整个 URL 用引号包起来
GET 变成了 POST用了-d但没加-G加-G参数
JSON 解析失败引号转义错误外层用单引号,或改用@file
请求体换行丢失用了-d而非--data-binary换成--data-binary
命令卡住不返回服务端没响应且无超时加--max-time

这张表我建议截图存下来,遇到问题先对照一遍,能省下大量搜索时间。

5.2 关于引号,再强调一次

引号问题是 curl 新手最大的拦路虎,我单独拎出来说。核心规则就三条:

第一,JSON 请求体外层用单引号,因为 JSON 内部全是双引号,用单引号包裹就不用转义。第二,URL 里有&时整个 URL 用引号包裹,否则 shell 会截断。第三,Windows 环境尽量避开 CMD,用 Git Bash 或 WSL,转义规则和 Linux 一致,能少掉一半的头发。

如果你实在要在 CMD 里写复杂 JSON,我的建议是别硬扛,把 JSON 写到文件里用-d @file.json,绕开所有转义问题。这不是偷懒,是聪明的做法。

5.3 认证信息不要硬编码在命令里

调试阶段直接在命令里写 token 很方便,但一旦命令被记录到 shell 历史、日志文件、或者 CI 的构建输出里,token 就泄露了。正确的做法是用环境变量:

curl -X POST "https://api.example.com/v1/users" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer ${API_TOKEN}" \ -d @user.json

然后在执行前export API_TOKEN=xxx。更进一步,可以用--netrc-file或者从密钥管理服务动态获取。这个习惯越早养成越好,我见过因为命令历史泄露密钥导致的安全事件,代价很大。

5.4 批量请求时的连接复用

如果你要连续发很多请求,比如压测或者批量同步数据,每次都启动一个新的 curl 进程开销不小。curl 支持在单次调用里发多个 URL:

curl -s "https://api.example.com/v1/users/1" \ -s "https://api.example.com/v1/users/2" \ -s "https://api.example.com/v1/users/3"

curl 会复用底层连接,比循环调用快不少。不过要注意,多个 URL 的输出会连在一起,最好配合-o分别写文件,或者用-w加分隔符。如果请求量真的很大,那还是建议用专门的压测工具或者写脚本用编程语言的 HTTP 库,curl 更适合中小规模的批量操作。

6. 把 curl 用进日常:几个真实场景的落地写法

6.1 接口健康检查脚本

运维场景里经常需要定时探测接口是否存活。一个简单的健康检查脚本可以这样写:

#!/bin/bash URL="https://api.example.com/v1/health" CODE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 "$URL") if [ "$CODE" = "200" ]; then echo "OK" else echo "FAIL: $CODE" fi

这个脚本可以挂到定时任务里,配合告警系统使用。关键点是-o /dev/null丢弃响应体、-w只取状态码、--max-time防止卡死。三个参数缺一不可。

6.2 用 curl 做接口回归测试

在 CI 流程里,可以用 curl 快速验证接口契约。比如验证创建用户接口返回 201 且响应体包含 id 字段:

RESP=$(curl -s -X POST "https://api.example.com/v1/users" \ -H "Content-Type: application/json" \ -d '{"name":"test","age":20}') CODE=$(echo "$RESP" | jq -r '.code') if [ "$CODE" != "0" ]; then echo "接口回归失败: $RESP" exit 1 fi

这种写法轻量、无依赖,适合放在部署流水线的最后一步做冒烟测试。比引入一套完整的测试框架要快得多,对于小团队来说性价比很高。

6.3 抓取接口数据做临时分析

有时候需要快速拉一批数据下来看看,用 curl 配合 jq 就能搞定:

for i in $(seq 1 10); do curl -s "https://api.example.com/v1/items?page=$i" | jq -c '.data[]' done > items.jsonl

这段脚本把 10 页数据拉下来,每行一个 JSON 对象,存成 JSONL 格式,后续用任何工具都能处理。jq -c是紧凑输出,一行一个对象,方便流式处理。这种临时脚本在数据分析、问题排查时特别顺手。

6.4 关于 curl 版本的一点提醒

curl 的功能在不同版本间有差异,比如--data-urlencode是 7.18 之后才有的,--retry系列参数也是后来加的。如果你在老旧系统上跑脚本,可能会遇到参数不识别的问题。用curl --version确认版本,必要时在脚本里做兼容处理。我一般会在脚本开头加一句版本检查,避免在陌生环境里翻车。

7. 写在最后的一点个人体会

curl 这个工具,刚上手的时候觉得参数多、语法怪,但用熟了之后会发现它的设计其实非常一致:-H管头,-d管体,-X管方法,-G管查询字符串,-o管输出,-w管元信息。把这几个核心参数吃透,日常百分之九十的接口调试场景都能覆盖。

我自己的习惯是,每遇到一个新的接口,先用 curl 在命令行里跑通,确认请求格式、认证方式、响应结构都对了,再把它翻译成代码。这样能把"接口本身的问题"和"代码写得不对的问题"分开,排查起来效率高很多。很多时候代码里调不通,回头用 curl 一试,发现是请求头少了一个,或者参数编码方式不对,问题立刻定位。

最后分享一个小技巧:把常用的 curl 命令存成一个 shell 函数或者别名,比如api-get、api-post,把 token、base url、常用头都封装进去,日常调用就只需要传路径和参数。这样既省事,又能避免每次都手写一长串参数出错。工具的价值就在于把重复劳动固化下来,让注意力集中在真正需要思考的地方。

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

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

立即咨询