1. 为什么值得花时间把curl的GET和POST彻底搞明白日常开发里,调试接口这件事几乎每天都在发生。浏览器里点来点去只能覆盖最简单的场景,一旦遇到需要自定义请求头、携带JSON体、模拟特定认证方式的情况,图形化工具就开始捉襟见肘。而curl这个命令行工具,几乎每一台开发机、每一台服务器上都预装着,不需要额外安装,不需要打开任何界面,一条命令就能把请求发出去,结果直接打印在终端里。这种“随手可用”的特性,让它在接口调试、自动化脚本、CI/CD流水线里成了不可替代的存在。这篇文章要聊的,就是curl最核心的两个用法:发送带JSON参数的POST请求和发送GET请求。看起来简单,但实际操作中踩坑的人非常多——JSON引号被shell吃掉、中文乱码、Content-Type没设对导致服务端解析失败、GET请求参数里的特殊字符没有转义、重定向和超时行为不符合预期,这些问题几乎每个后端或前端开发者都遇到过至少一次。我会从实际使用的角度,把这两个请求方式的完整细节拆开讲清楚,包括参数怎么传、请求头怎么设、常见错误怎么排查,以及一些在脚本里批量调用接口时的实用技巧。不管你是刚接触接口调试的新手,还是已经用了几年curl但一直靠“复制粘贴改改”过日子的老手,下面这些内容都值得过一遍。因为很多问题的根源,不是curl本身难用,而是shell的解析规则和HTTP协议的细节在背后“捣鬼”,搞清楚这些,你才能真正做到“一条命令解决问题”,而不是反复试错。2. curl发送GET请求的完整拆解2.1 GET请求的基本形态与最简用法GET请求是HTTP协议里最基础的方法,语义是“获取资源”。用curl发一个GET请求,最简单的形式就是直接把URL跟在命令后面:curl https://api.example.com/users这条命令执行后,curl会向目标地址发起一个GET请求,然后把服务端返回的响应体直接打印到终端。默认情况下,curl不会显示响应头,也不会显示请求过程,只输出body内容。如果你在终端里看到了一串JSON或者HTML,说明请求成功了。但这里有个很多人忽略的点:curl默认会把响应体直接输出到标准输出,如果你请求的是一个二进制文件(比如图片),终端会显示一堆乱码。这时候需要用-o参数把结果写到文件里:curl -o response.json https://api.example.com/users或者用-O参数让curl自动以URL的最后一段作为文件名保存:curl -O https://api.example.com/files/report.pdf这两个参数的区别很实用:-o是你自己指定文件名,-O是让curl根据URL自动命名。下载文件的时候用-O省事,但URL里带查询参数时自动命名可能会出问题,这时候还是老老实实用-o。2.2 带查询参数的GET请求怎么写才不出错GET请求传参数的方式是拼在URL后面的查询字符串(query string),格式是?key1=value1&key2=value2。很多人第一次写的时候会直接这样:curl https://api.example.com/search?keyword=hello world&page=1这条命令在终端里执行会出问题。原因在于shell会把空格当作参数分隔符,hello和world被拆成了两个部分,curl实际收到的URL是https://api.example.com/search?keyword=hello,后面的world&page=1被当成了额外的参数。正确的做法是用引号把整个URL包起来:curl "https://api.example.com/search?keyword=hello world&page=1"但即使加了引号,空格在URL里也是不合法的字符,服务端收到后可能会解析异常。更规范的做法是对参数值做URL编码,空格编码成%20:curl "https://api.example.com/search?keyword=hello%20world&page=1"curl本身提供了一个--data-urlencode参数,但它主要用于POST请求。对于GET请求,如果你不想手动编码,可以用-G参数配合--data-urlencode来实现自动编码:curl -G "https://api.example.com/search" --data-urlencode "keyword=hello world" --data-urlencode "page=1"这条命令里,-G告诉curl“把后面用--data-urlencode指定的数据作为查询参数拼到URL上,而不是放在请求体里”。curl会自动把hello world编码成hello%20world,最终发出的URL是https://api.example.com/search?keyword=hello%20world&page=1。这个用法在参数值包含中文、空格、特殊符号时特别省心,强烈建议记住。提示:中文参数一定要编码。虽然很多服务端框架能自动处理未编码的中文,但HTTP协议层面URL只允许ASCII字符,不编码在某些代理或网关环节会直接报400错误。2.3 GET请求中查看响应头和调试信息默认情况下curl只输出响应体,但调试接口时经常需要看响应头,比如检查Content-Type、Set-Cookie、Cache-Control这些字段。用-i参数可以把响应头和响应体一起输出:curl -i https://api.example.com/users如果只想看响应头,不关心body,用-I参数发送一个HEAD请求:curl -I https://api.example.com/users注意-I发送的是HEAD方法,不是GET。有些服务端对HEAD方法的支持不完整,可能返回405或者不返回正确的头信息。如果你需要发送GET请求但只看响应头,可以用-i配合-o /dev/null把body丢弃:curl -i -o /dev/null https://api.example.com/users调试时另一个非常有用的参数是-v,它会输出完整的请求和响应过程,包括TCP连接、TLS握手、发送的请求头、收到的响应头:curl -v https://api.example.com/users-v的输出里,以>开头的行是curl发送的请求内容,以<开头的行是服务端返回的响应内容。当你怀疑请求头没设对、认证信息没带上、或者服务端返回了非预期的状态码时,-v是第一手排查工具。如果觉得-v输出还不够详细,可以用--trace-ascii -把每一个字节的收发都打印出来,不过这个通常只在排查极端诡异的协议问题时才用得上。2.4 GET请求的常见误区与实操心得第一个误区是忽略重定向。curl默认不跟随重定向,如果服务端返回301或302,curl只会把重定向的响应头打印出来,不会自动跳到新地址。很多API在HTTP和HTTPS之间跳转,或者带不带末尾斜杠会跳转,这时候需要加-L参数:curl -L https://api.example.com/users/加了-L之后,curl会自动跟随Location头指向的新地址。但要注意,跟随重定向时curl默认会把认证信息也带到新地址,如果跨域跳转可能会有安全问题,可以用--max-redirs限制跳转次数。第二个误区是不设超时。默认情况下curl会一直等待服务端响应,如果服务端卡住,你的脚本就会挂在那里。生产环境的脚本里一定要加超时参数:curl --connect-timeout 5 --max-time 15 https://api.example.com/users--connect-timeout是连接阶段的超时,--max-time是整个请求的最大耗时。两个都设上,才能保证脚本不会因为一个慢接口而整体卡死。第三个误区是在循环里反复调用时不复用连接。如果你在脚本里连续请求同一个域名的多个接口,每次curl都重新建立TCP连接和TLS握手,开销很大。可以用--keepalive-time或者在一个curl命令里指定多个URL来复用连接:curl https://api.example.com/users https://api.example.com/orders https://api.example.com/products这样curl会在同一个连接上依次请求这三个地址,省去了重复握手的时间。不过要注意,这种方式下每个URL的响应会依次输出,如果某个请求失败,后面的请求默认还是会继续执行。3. curl发送POST请求带JSON参数的实战细节3.1 POST请求的基本结构与Content-Type的重要性POST请求和GET请求最大的区别在于,数据是放在请求体(body)里发送的,而不是拼在URL上。用curl发POST请求,核心参数是-X POST和-d(或者--data):curl -X POST -d '{"name":"张三","age":25}' https://api.example.com/users这条命令看起来没问题,但实际发给服务端后,很多框架会解析失败。原因在于Content-Type。curl用-d发送数据时,默认的Content-Type是application/x-www-form-urlencoded,而不是application/json。服务端看到这个Content-Type,会按照表单格式去解析body,结果就是解析出来的字段全是乱的。正确的做法是显式指定Content-Type为application/json:curl -X POST -H "Content-Type: application/json" -d '{"name":"张三","age":25}' https://api.example.com/users加上-H "Content-Type: application/json"之后,服务端才知道body里是JSON格式,会用JSON解析器去处理。这个细节是新手最容易踩的坑,没有之一。我见过太多人调试了半天,以为是JSON格式写错了,最后发现只是少了一个请求头。注意:-H参数可以多次使用,每次指定一个请求头。curl会自动把多个-H合并到请求里。请求头的名字不区分大小写,但值通常区分大小写,比如application/json不能写成Application/JSON。3.2 JSON参数里的引号问题与shell转义在命令行里写JSON,最头疼的就是引号。JSON本身要求键和字符串值用双引号,而shell里双引号又有特殊含义。上面那条命令用的是单引号包裹整个JSON,这样shell不会对里面的双引号做处理,是最省事的写法。但如果JSON里本身需要包含单引号,比如:{"name":"O'Brien"}单引号包裹就会出问题。这时候有几种处理方式。第一种是用双引号包裹,然后对JSON里的双引号做转义:curl -X POST -H "Content-Type: application/json" -d "{\"name\":\"O'Brien\"}" https://api.example.com/users这种写法可读性很差,JSON稍微长一点就容易写错。第二种方式是把JSON写到文件里,然后用-d @filename读取:curl -X POST -H "Content-Type: application/json" -d @payload.json https://api.example.com/userspayload.json文件里直接写标准的JSON,不需要任何转义。这是处理复杂JSON最推荐的方式,尤其是在脚本里,把JSON和命令分离,维护起来清晰得多。第三种方式是用here-document:curl -X POST -H "Content-Type: application/json" -d @- https://api.example.com/users <<'EOF' { "name": "张三", "age": 25, "tags": ["developer", "backend"] } EOF-d @-表示从标准输入读取数据,here-document里的内容原样传给curl。这种方式在shell脚本里特别实用,既不需要额外文件,也不用担心转义问题。3.3 发送嵌套JSON和数组的注意事项实际接口里,JSON往往不是简单的键值对,而是嵌套对象或者数组。比如:{ "user": { "name": "张三", "contacts": { "email": "zhangsan@example.com", "phone": "13800138000" } }, "roles": ["admin", "editor"] }这种结构用命令行直接写很容易出错,因为括号和引号太多,肉眼很难检查。我的建议是,只要JSON超过两层嵌套,就一律用文件方式发送。如果非要在命令行里写,可以用python -m json.tool或者jq先验证JSON格式是否正确:echo '{"user":{"name":"张三"}}' | python -m json.tool这条命令会把JSON格式化输出,如果格式有误会直接报错。确认无误后再把同样的内容放到curl命令里。另外,jq工具在构造JSON时非常方便,可以用它动态生成JSON再传给curl:jq -n --arg name "张三" --arg email "zhangsan@example.com" '{user:{name:$name,contacts:{email:$email}}}' | curl -X POST -H "Content-Type: application/json" -d @- https://api.example.com/users这种管道组合的写法在自动化脚本里很常见,既保证了JSON的合法性,又避免了手动拼接字符串的转义问题。3.4 POST请求中其他常用参数与行为控制除了-d和-H,POST请求还有几个参数值得掌握。--data-binary和-d的区别在于,-d会去掉数据里的换行符,而--data-binary会原样发送。如果你发送的JSON是格式化过的、带换行的,用-d会把换行去掉,虽然对JSON解析没影响,但如果你需要计算签名或者校验原始body,就必须用--data-binary:curl -X POST -H "Content-Type: application/json" --data-binary @payload.json https://api.example.com/users--data-raw和-d类似,但它不会把@开头的字符串当作文件名处理。如果你的JSON值恰好以@开头,用-d会被curl误认为是读取文件,这时候要用--data-raw。另一个常用的是-u参数,用于HTTP Basic认证:curl -X POST -u "username:password" -H "Content-Type: application/json" -d @payload.json https://api.example.com/userscurl会自动把username:password做Base64编码放到Authorization头里。不过在生产环境里,密码直接写在命令行里会留在shell历史记录中,更安全的做法是用-u "username",curl会交互式提示输入密码,或者用--netrc从配置文件读取。如果服务端需要Bearer Token认证,就用-H手动加Authorization头:curl -X POST -H "Authorization: Bearer your_token_here" -H "Content-Type: application/json" -d @payload.json https://api.example.com/usersToken同样不建议直接写在命令行里,可以在脚本里从环境变量读取:curl -X POST -H "Authorization: Bearer $API_TOKEN" -H "Content-Type: application/json" -d @payload.json https://api.example.com/users这样Token不会出现在命令历史里,也方便在不同环境之间切换。4. 高频问题排查与实战经验汇总4.1 服务端返回400/415错误时怎么快速定位400 Bad Request和415 Unsupported Media Type是POST JSON请求最常见的两个错误码。415通常意味着Content-Type没设对,服务端不支持你发送的媒体类型。排查步骤很简单:先用-v看curl实际发送的请求头里Content-Type是什么,确认是不是application/json。如果Content-Type对了但还是415,检查一下是不是多打了空格或者大小写有问题。400的原因就多了,可能是JSON格式错误、缺少必填字段、字段类型不对。这时候光看curl的输出不够,要把服务端返回的响应体完整打印出来。很多框架会在响应体里给出具体的错误信息,比如“field 'name' is required”或者“invalid JSON at position 15”。用-i参数把响应头和响应体一起输出,或者直接不加任何参数,curl默认就会输出响应体。如果服务端返回的body是空的,那就需要看服务端的日志了。这种情况下,用--trace-ascii -把curl发送的原始数据打印出来,和服务端日志里的请求体做对比,通常能发现是编码问题还是数据被截断。4.2 中文乱码和编码问题的根源分析中文乱码在curl请求里出现,通常有两个原因。第一个是终端本身的编码和curl输出的编码不一致,这个只影响你在终端里看到的显示,不影响实际发送的数据。第二个是请求体或URL里的中文没有正确编码,导致服务端解析出来的字节序列不是合法的UTF-8。对于POST JSON请求,只要Content-Type里声明了charset=utf-8,并且你发送的JSON文件本身是UTF-8编码,一般不会有问题。但有些服务端框架要求Content-Type必须精确匹配application/json;charset=UTF-8,少一个charset就会用默认编码解析。保险起见,可以在Content-Type里显式加上charset:curl -X POST -H "Content-Type: application/json; charset=utf-8" -d @payload.json https://api.example.com/users对于GET请求里的中文参数,一定要用--data-urlencode配合-G,让curl自动做URL编码。手动拼中文到URL里,即使终端显示正常,实际发送的字节也可能不符合HTTP规范。4.3 超时、连接失败与重试策略网络请求失败的原因很多,curl返回的错误码能帮你快速定位。退出码6表示无法解析域名,7表示连接被拒绝,28表示超时,35表示TLS握手失败。在脚本里可以根据退出码做不同的处理:curl -X POST -H "Content-Type: application/json" -d @payload.json https://api.example.com/users if [ $? -eq 28 ]; then echo "请求超时,准备重试" fi对于不稳定的网络环境,可以用--retry参数让curl自动重试:curl --retry 3 --retry-delay 2 --retry-connrefused -X POST -H "Content-Type: application/json" -d @payload.json https://api.example.com/users--retry 3表示最多重试3次,--retry-delay 2表示每次重试间隔2秒,--retry-connrefused表示连接被拒绝时也重试。注意默认情况下curl只对超时和5xx错误重试,对4xx错误不重试,因为4xx通常意味着请求本身有问题,重试也没用。4.4 常见问题速查表问题现象可能原因排查方法解决方案服务端返回415Content-Type未设置或设置错误用-v查看请求头添加-H "Content-Type: application/json"JSON解析失败JSON格式错误或引号转义问题用python -m json.tool验证改用-d @file从文件读取中文显示乱码终端编码或URL未编码检查locale和请求体编码GET用--data-urlencode,POST确保文件为UTF-8请求一直挂起未设置超时观察curl是否卡住添加--connect-timeout和--max-time重定向后请求失败未跟随重定向用-v查看是否返回301/302添加-L参数认证失败Token未正确传递用-v查看Authorization头检查-H或-u参数请求体被截断数据中包含特殊字符用--trace-ascii -查看原始数据改用--data-binary4.5 在脚本中批量调用接口的实用技巧当你需要在shell脚本里循环调用接口时,有几个细节能帮你省不少事。第一,把公共的请求头和URL前缀提取成变量:API_BASE="https://api.example.com" AUTH_HEADER="Authorization: Bearer $API_TOKEN" CONTENT_TYPE="Content-Type: application/json" for id in 1 2 3 4 5; do curl -s -X POST -H "$AUTH_HEADER" -H "$CONTENT_TYPE" -d "{\"id\":$id}" "$API_BASE/process" done-s参数让curl静默执行,不显示进度条和错误信息,适合在脚本里用。如果需要看错误,可以加-S让curl在静默模式下仍然显示错误信息。第二,用-w参数输出请求的耗时和状态码,方便做监控和统计:curl -s -o /dev/null -w "status=%{http_code} time=%{time_total}s\n" -X POST -H "$CONTENT_TYPE" -d @payload.json "$API_BASE/users"-w支持很多变量,常用的有%{http_code}(HTTP状态码)、%{time_total}(总耗时)、%{time_connect}(连接耗时)、%{size_download}(下载字节数)。把这些输出重定向到日志文件,就能对接口的稳定性和性能有一个直观的记录。第三,如果脚本里需要并发调用多个接口,可以用&把curl放到后台执行,然后用wait等待所有请求完成:for id in 1 2 3 4 5; do curl -s -X POST -H "$CONTENT_TYPE" -d "{\"id\":$id}" "$API_BASE/process" & done wait echo "所有请求已完成"不过并发调用时要注意服务端的承受能力,以及输出可能会交错在一起。如果需要收集每个请求的结果,可以把输出写到不同的临时文件里,最后再合并。5. 从GET到POST:把curl真正用顺手的几个习惯5.1 用配置文件管理常用参数如果你经常请求同一个API,每次都敲一长串-H和URL前缀很烦。curl支持从配置文件读取默认参数,配置文件默认路径是~/.curlrc,你也可以用-K指定自定义配置文件:curl -K api-config.txt https://api.example.com/usersapi-config.txt里可以写:header = "Content-Type: application/json" header = "Authorization: Bearer your_token_here" connect-timeout = 5 max-time = 15这样每次请求都会自动带上这些参数,命令行里只需要写URL和-d就行了。配置文件的方式在团队协作时也很有用,可以把公共配置放在项目目录里,大家共用。5.2 把curl输出格式化成可读的JSONcurl返回的JSON默认是一整行,字段多了以后很难看。用jq管道格式化一下,可读性立刻提升:curl -s https://api.example.com/users | jq .如果jq没装,用python -m json.tool也能达到类似效果:curl -s https://api.example.com/users | python -m json.tool这两个工具还能用来提取特定字段,比如只看返回列表里每个元素的id和name:curl -s https://api.example.com/users | jq '.[] | {id, name}'调试接口时,这种“请求+格式化+提取”的组合能让你快速看清数据结构,比在终端里瞪着一行超长的JSON高效得多。5.3 保存和复用请求的实战建议调试成功的curl命令,建议随手保存下来。可以建一个requests.md或者api-examples.sh文件,把常用的请求按接口分类记下来。记录的时候,把URL、方法、请求头、请求体都写清楚,最好再附上一条示例响应。下次需要调同一个接口时,直接复制粘贴改参数就行,不用重新回忆当时是怎么拼的。另外,浏览器开发者工具里的“Copy as cURL”功能很好用,可以把浏览器里发出的请求直接导出成curl命令。但要注意,浏览器导出的命令里可能包含很多不必要的请求头(比如User-Agent、Accept-Encoding、Cookie等),复制过来后建议精简一下,只保留必要的部分。特别是Cookie,里面可能包含敏感信息,分享命令给别人之前一定要删掉。5.4 安全使用curl的几条底线第一,不要把密码和Token硬编码在命令行里。命令行会留在shell历史记录中,用history命令就能看到。用环境变量或者配置文件,并且确保配置文件的权限是600。第二,不要对不信任的地址发送带认证信息的请求。curl的-L跟随重定向时,默认会把Authorization头带到新地址,如果重定向到了一个不受控的域名,认证信息就泄露了。可以用--location-trusted显式控制这个行为,或者用--max-redirs 0禁止重定向。第三,生产环境的脚本里一定要设超时。没有超时的curl请求在服务端无响应时会一直挂着,在CI/CD流水线里可能导致整个任务卡死。--connect-timeout和--max-time是最基本的保护。第四,注意请求体里的敏感数据。用-d发送的JSON如果包含手机号、身份证号、密码等敏感信息,在-v或--trace-ascii的输出里会明文显示。调试完成后记得清理终端输出和日志文件。我个人在实际操作中的体会是,curl这个工具看似简单,但真正用好需要理解shell的解析规则和HTTP协议的细节。很多人用了几年curl,遇到问题还是靠“换一种写法试试”,而不是从原理上定位原因。把GET和POST这两个最基础的方法彻底搞清楚,把Content-Type、引号转义、URL编码、超时重试这几个关键点吃透,后面遇到再复杂的请求场景,比如文件上传、多部分表单、自定义认证,都能举一反三。最后再分享一个小技巧:如果你不确定一条curl命令最终发出的请求长什么样,加一个-v参数,它会把请求行、请求头、请求体(部分情况下)都打印出来,这是排查问题最快的方式,没有之一。
1. 为什么值得花时间把curl的GET和POST彻底搞明白日常开发里,调试接口这件事几乎每天都在发生。浏览器里点来点去只能覆盖最简单的场景,一旦遇到需要自定义请求头、携带JSON体、模拟特定认证方式的情况,图形化工具就开始捉襟见肘。而curl这个命令行工具,几乎每一台开发机、每一台服务器上都预装着,不需要额外安装,不需要打开任何界面,一条命令就能把请求发出去,结果直接打印在终端里。这种“随手可用”的特性,让它在接口调试、自动化脚本、CI/CD流水线里成了不可替代的存在。这篇文章要聊的,就是curl最核心的两个用法:发送带JSON参数的POST请求和发送GET请求。看起来简单,但实际操作中踩坑的人非常多——JSON引号被shell吃掉、中文乱码、Content-Type没设对导致服务端解析失败、GET请求参数里的特殊字符没有转义、重定向和超时行为不符合预期,这些问题几乎每个后端或前端开发者都遇到过至少一次。我会从实际使用的角度,把这两个请求方式的完整细节拆开讲清楚,包括参数怎么传、请求头怎么设、常见错误怎么排查,以及一些在脚本里批量调用接口时的实用技巧。不管你是刚接触接口调试的新手,还是已经用了几年curl但一直靠“复制粘贴改改”过日子的老手,下面这些内容都值得过一遍。因为很多问题的根源,不是curl本身难用,而是shell的解析规则和HTTP协议的细节在背后“捣鬼”,搞清楚这些,你才能真正做到“一条命令解决问题”,而不是反复试错。2. curl发送GET请求的完整拆解2.1 GET请求的基本形态与最简用法GET请求是HTTP协议里最基础的方法,语义是“获取资源”。用curl发一个GET请求,最简单的形式就是直接把URL跟在命令后面:curl https://api.example.com/users这条命令执行后,curl会向目标地址发起一个GET请求,然后把服务端返回的响应体直接打印到终端。默认情况下,curl不会显示响应头,也不会显示请求过程,只输出body内容。如果你在终端里看到了一串JSON或者HTML,说明请求成功了。但这里有个很多人忽略的点:curl默认会把响应体直接输出到标准输出,如果你请求的是一个二进制文件(比如图片),终端会显示一堆乱码。这时候需要用-o参数把结果写到文件里:curl -o response.json https://api.example.com/users或者用-O参数让curl自动以URL的最后一段作为文件名保存:curl -O https://api.example.com/files/report.pdf这两个参数的区别很实用:-o是你自己指定文件名,-O是让curl根据URL自动命名。下载文件的时候用-O省事,但URL里带查询参数时自动命名可能会出问题,这时候还是老老实实用-o。2.2 带查询参数的GET请求怎么写才不出错GET请求传参数的方式是拼在URL后面的查询字符串(query string),格式是?key1=value1&key2=value2。很多人第一次写的时候会直接这样:curl https://api.example.com/search?keyword=hello world&page=1这条命令在终端里执行会出问题。原因在于shell会把空格当作参数分隔符,hello和world被拆成了两个部分,curl实际收到的URL是https://api.example.com/search?keyword=hello,后面的world&page=1被当成了额外的参数。正确的做法是用引号把整个URL包起来:curl "https://api.example.com/search?keyword=hello world&page=1"但即使加了引号,空格在URL里也是不合法的字符,服务端收到后可能会解析异常。更规范的做法是对参数值做URL编码,空格编码成%20:curl "https://api.example.com/search?keyword=hello%20world&page=1"curl本身提供了一个--data-urlencode参数,但它主要用于POST请求。对于GET请求,如果你不想手动编码,可以用-G参数配合--data-urlencode来实现自动编码:curl -G "https://api.example.com/search" --data-urlencode "keyword=hello world" --data-urlencode "page=1"这条命令里,-G告诉curl“把后面用--data-urlencode指定的数据作为查询参数拼到URL上,而不是放在请求体里”。curl会自动把hello world编码成hello%20world,最终发出的URL是https://api.example.com/search?keyword=hello%20world&page=1。这个用法在参数值包含中文、空格、特殊符号时特别省心,强烈建议记住。提示:中文参数一定要编码。虽然很多服务端框架能自动处理未编码的中文,但HTTP协议层面URL只允许ASCII字符,不编码在某些代理或网关环节会直接报400错误。2.3 GET请求中查看响应头和调试信息默认情况下curl只输出响应体,但调试接口时经常需要看响应头,比如检查Content-Type、Set-Cookie、Cache-Control这些字段。用-i参数可以把响应头和响应体一起输出:curl -i https://api.example.com/users如果只想看响应头,不关心body,用-I参数发送一个HEAD请求:curl -I https://api.example.com/users注意-I发送的是HEAD方法,不是GET。有些服务端对HEAD方法的支持不完整,可能返回405或者不返回正确的头信息。如果你需要发送GET请求但只看响应头,可以用-i配合-o /dev/null把body丢弃:curl -i -o /dev/null https://api.example.com/users调试时另一个非常有用的参数是-v,它会输出完整的请求和响应过程,包括TCP连接、TLS握手、发送的请求头、收到的响应头:curl -v https://api.example.com/users-v的输出里,以>开头的行是curl发送的请求内容,以<开头的行是服务端返回的响应内容。当你怀疑请求头没设对、认证信息没带上、或者服务端返回了非预期的状态码时,-v是第一手排查工具。如果觉得-v输出还不够详细,可以用--trace-ascii -把每一个字节的收发都打印出来,不过这个通常只在排查极端诡异的协议问题时才用得上。2.4 GET请求的常见误区与实操心得第一个误区是忽略重定向。curl默认不跟随重定向,如果服务端返回301或302,curl只会把重定向的响应头打印出来,不会自动跳到新地址。很多API在HTTP和HTTPS之间跳转,或者带不带末尾斜杠会跳转,这时候需要加-L参数:curl -L https://api.example.com/users/加了-L之后,curl会自动跟随Location头指向的新地址。但要注意,跟随重定向时curl默认会把认证信息也带到新地址,如果跨域跳转可能会有安全问题,可以用--max-redirs限制跳转次数。第二个误区是不设超时。默认情况下curl会一直等待服务端响应,如果服务端卡住,你的脚本就会挂在那里。生产环境的脚本里一定要加超时参数:curl --connect-timeout 5 --max-time 15 https://api.example.com/users--connect-timeout是连接阶段的超时,--max-time是整个请求的最大耗时。两个都设上,才能保证脚本不会因为一个慢接口而整体卡死。第三个误区是在循环里反复调用时不复用连接。如果你在脚本里连续请求同一个域名的多个接口,每次curl都重新建立TCP连接和TLS握手,开销很大。可以用--keepalive-time或者在一个curl命令里指定多个URL来复用连接:curl https://api.example.com/users https://api.example.com/orders https://api.example.com/products这样curl会在同一个连接上依次请求这三个地址,省去了重复握手的时间。不过要注意,这种方式下每个URL的响应会依次输出,如果某个请求失败,后面的请求默认还是会继续执行。3. curl发送POST请求带JSON参数的实战细节3.1 POST请求的基本结构与Content-Type的重要性POST请求和GET请求最大的区别在于,数据是放在请求体(body)里发送的,而不是拼在URL上。用curl发POST请求,核心参数是-X POST和-d(或者--data):curl -X POST -d '{"name":"张三","age":25}' https://api.example.com/users这条命令看起来没问题,但实际发给服务端后,很多框架会解析失败。原因在于Content-Type。curl用-d发送数据时,默认的Content-Type是application/x-www-form-urlencoded,而不是application/json。服务端看到这个Content-Type,会按照表单格式去解析body,结果就是解析出来的字段全是乱的。正确的做法是显式指定Content-Type为application/json:curl -X POST -H "Content-Type: application/json" -d '{"name":"张三","age":25}' https://api.example.com/users加上-H "Content-Type: application/json"之后,服务端才知道body里是JSON格式,会用JSON解析器去处理。这个细节是新手最容易踩的坑,没有之一。我见过太多人调试了半天,以为是JSON格式写错了,最后发现只是少了一个请求头。注意:-H参数可以多次使用,每次指定一个请求头。curl会自动把多个-H合并到请求里。请求头的名字不区分大小写,但值通常区分大小写,比如application/json不能写成Application/JSON。3.2 JSON参数里的引号问题与shell转义在命令行里写JSON,最头疼的就是引号。JSON本身要求键和字符串值用双引号,而shell里双引号又有特殊含义。上面那条命令用的是单引号包裹整个JSON,这样shell不会对里面的双引号做处理,是最省事的写法。但如果JSON里本身需要包含单引号,比如:{"name":"O'Brien"}单引号包裹就会出问题。这时候有几种处理方式。第一种是用双引号包裹,然后对JSON里的双引号做转义:curl -X POST -H "Content-Type: application/json" -d "{\"name\":\"O'Brien\"}" https://api.example.com/users这种写法可读性很差,JSON稍微长一点就容易写错。第二种方式是把JSON写到文件里,然后用-d @filename读取:curl -X POST -H "Content-Type: application/json" -d @payload.json https://api.example.com/userspayload.json文件里直接写标准的JSON,不需要任何转义。这是处理复杂JSON最推荐的方式,尤其是在脚本里,把JSON和命令分离,维护起来清晰得多。第三种方式是用here-document:curl -X POST -H "Content-Type: application/json" -d @- https://api.example.com/users <<'EOF' { "name": "张三", "age": 25, "tags": ["developer", "backend"] } EOF-d @-表示从标准输入读取数据,here-document里的内容原样传给curl。这种方式在shell脚本里特别实用,既不需要额外文件,也不用担心转义问题。3.3 发送嵌套JSON和数组的注意事项实际接口里,JSON往往不是简单的键值对,而是嵌套对象或者数组。比如:{ "user": { "name": "张三", "contacts": { "email": "zhangsan@example.com", "phone": "13800138000" } }, "roles": ["admin", "editor"] }这种结构用命令行直接写很容易出错,因为括号和引号太多,肉眼很难检查。我的建议是,只要JSON超过两层嵌套,就一律用文件方式发送。如果非要在命令行里写,可以用python -m json.tool或者jq先验证JSON格式是否正确:echo '{"user":{"name":"张三"}}' | python -m json.tool这条命令会把JSON格式化输出,如果格式有误会直接报错。确认无误后再把同样的内容放到curl命令里。另外,jq工具在构造JSON时非常方便,可以用它动态生成JSON再传给curl:jq -n --arg name "张三" --arg email "zhangsan@example.com" '{user:{name:$name,contacts:{email:$email}}}' | curl -X POST -H "Content-Type: application/json" -d @- https://api.example.com/users这种管道组合的写法在自动化脚本里很常见,既保证了JSON的合法性,又避免了手动拼接字符串的转义问题。3.4 POST请求中其他常用参数与行为控制除了-d和-H,POST请求还有几个参数值得掌握。--data-binary和-d的区别在于,-d会去掉数据里的换行符,而--data-binary会原样发送。如果你发送的JSON是格式化过的、带换行的,用-d会把换行去掉,虽然对JSON解析没影响,但如果你需要计算签名或者校验原始body,就必须用--data-binary:curl -X POST -H "Content-Type: application/json" --data-binary @payload.json https://api.example.com/users--data-raw和-d类似,但它不会把@开头的字符串当作文件名处理。如果你的JSON值恰好以@开头,用-d会被curl误认为是读取文件,这时候要用--data-raw。另一个常用的是-u参数,用于HTTP Basic认证:curl -X POST -u "username:password" -H "Content-Type: application/json" -d @payload.json https://api.example.com/userscurl会自动把username:password做Base64编码放到Authorization头里。不过在生产环境里,密码直接写在命令行里会留在shell历史记录中,更安全的做法是用-u "username",curl会交互式提示输入密码,或者用--netrc从配置文件读取。如果服务端需要Bearer Token认证,就用-H手动加Authorization头:curl -X POST -H "Authorization: Bearer your_token_here" -H "Content-Type: application/json" -d @payload.json https://api.example.com/usersToken同样不建议直接写在命令行里,可以在脚本里从环境变量读取:curl -X POST -H "Authorization: Bearer $API_TOKEN" -H "Content-Type: application/json" -d @payload.json https://api.example.com/users这样Token不会出现在命令历史里,也方便在不同环境之间切换。4. 高频问题排查与实战经验汇总4.1 服务端返回400/415错误时怎么快速定位400 Bad Request和415 Unsupported Media Type是POST JSON请求最常见的两个错误码。415通常意味着Content-Type没设对,服务端不支持你发送的媒体类型。排查步骤很简单:先用-v看curl实际发送的请求头里Content-Type是什么,确认是不是application/json。如果Content-Type对了但还是415,检查一下是不是多打了空格或者大小写有问题。400的原因就多了,可能是JSON格式错误、缺少必填字段、字段类型不对。这时候光看curl的输出不够,要把服务端返回的响应体完整打印出来。很多框架会在响应体里给出具体的错误信息,比如“field 'name' is required”或者“invalid JSON at position 15”。用-i参数把响应头和响应体一起输出,或者直接不加任何参数,curl默认就会输出响应体。如果服务端返回的body是空的,那就需要看服务端的日志了。这种情况下,用--trace-ascii -把curl发送的原始数据打印出来,和服务端日志里的请求体做对比,通常能发现是编码问题还是数据被截断。4.2 中文乱码和编码问题的根源分析中文乱码在curl请求里出现,通常有两个原因。第一个是终端本身的编码和curl输出的编码不一致,这个只影响你在终端里看到的显示,不影响实际发送的数据。第二个是请求体或URL里的中文没有正确编码,导致服务端解析出来的字节序列不是合法的UTF-8。对于POST JSON请求,只要Content-Type里声明了charset=utf-8,并且你发送的JSON文件本身是UTF-8编码,一般不会有问题。但有些服务端框架要求Content-Type必须精确匹配application/json;charset=UTF-8,少一个charset就会用默认编码解析。保险起见,可以在Content-Type里显式加上charset:curl -X POST -H "Content-Type: application/json; charset=utf-8" -d @payload.json https://api.example.com/users对于GET请求里的中文参数,一定要用--data-urlencode配合-G,让curl自动做URL编码。手动拼中文到URL里,即使终端显示正常,实际发送的字节也可能不符合HTTP规范。4.3 超时、连接失败与重试策略网络请求失败的原因很多,curl返回的错误码能帮你快速定位。退出码6表示无法解析域名,7表示连接被拒绝,28表示超时,35表示TLS握手失败。在脚本里可以根据退出码做不同的处理:curl -X POST -H "Content-Type: application/json" -d @payload.json https://api.example.com/users if [ $? -eq 28 ]; then echo "请求超时,准备重试" fi对于不稳定的网络环境,可以用--retry参数让curl自动重试:curl --retry 3 --retry-delay 2 --retry-connrefused -X POST -H "Content-Type: application/json" -d @payload.json https://api.example.com/users--retry 3表示最多重试3次,--retry-delay 2表示每次重试间隔2秒,--retry-connrefused表示连接被拒绝时也重试。注意默认情况下curl只对超时和5xx错误重试,对4xx错误不重试,因为4xx通常意味着请求本身有问题,重试也没用。4.4 常见问题速查表问题现象可能原因排查方法解决方案服务端返回415Content-Type未设置或设置错误用-v查看请求头添加-H "Content-Type: application/json"JSON解析失败JSON格式错误或引号转义问题用python -m json.tool验证改用-d @file从文件读取中文显示乱码终端编码或URL未编码检查locale和请求体编码GET用--data-urlencode,POST确保文件为UTF-8请求一直挂起未设置超时观察curl是否卡住添加--connect-timeout和--max-time重定向后请求失败未跟随重定向用-v查看是否返回301/302添加-L参数认证失败Token未正确传递用-v查看Authorization头检查-H或-u参数请求体被截断数据中包含特殊字符用--trace-ascii -查看原始数据改用--data-binary4.5 在脚本中批量调用接口的实用技巧当你需要在shell脚本里循环调用接口时,有几个细节能帮你省不少事。第一,把公共的请求头和URL前缀提取成变量:API_BASE="https://api.example.com" AUTH_HEADER="Authorization: Bearer $API_TOKEN" CONTENT_TYPE="Content-Type: application/json" for id in 1 2 3 4 5; do curl -s -X POST -H "$AUTH_HEADER" -H "$CONTENT_TYPE" -d "{\"id\":$id}" "$API_BASE/process" done-s参数让curl静默执行,不显示进度条和错误信息,适合在脚本里用。如果需要看错误,可以加-S让curl在静默模式下仍然显示错误信息。第二,用-w参数输出请求的耗时和状态码,方便做监控和统计:curl -s -o /dev/null -w "status=%{http_code} time=%{time_total}s\n" -X POST -H "$CONTENT_TYPE" -d @payload.json "$API_BASE/users"-w支持很多变量,常用的有%{http_code}(HTTP状态码)、%{time_total}(总耗时)、%{time_connect}(连接耗时)、%{size_download}(下载字节数)。把这些输出重定向到日志文件,就能对接口的稳定性和性能有一个直观的记录。第三,如果脚本里需要并发调用多个接口,可以用&把curl放到后台执行,然后用wait等待所有请求完成:for id in 1 2 3 4 5; do curl -s -X POST -H "$CONTENT_TYPE" -d "{\"id\":$id}" "$API_BASE/process" & done wait echo "所有请求已完成"不过并发调用时要注意服务端的承受能力,以及输出可能会交错在一起。如果需要收集每个请求的结果,可以把输出写到不同的临时文件里,最后再合并。5. 从GET到POST:把curl真正用顺手的几个习惯5.1 用配置文件管理常用参数如果你经常请求同一个API,每次都敲一长串-H和URL前缀很烦。curl支持从配置文件读取默认参数,配置文件默认路径是~/.curlrc,你也可以用-K指定自定义配置文件:curl -K api-config.txt https://api.example.com/usersapi-config.txt里可以写:header = "Content-Type: application/json" header = "Authorization: Bearer your_token_here" connect-timeout = 5 max-time = 15这样每次请求都会自动带上这些参数,命令行里只需要写URL和-d就行了。配置文件的方式在团队协作时也很有用,可以把公共配置放在项目目录里,大家共用。5.2 把curl输出格式化成可读的JSONcurl返回的JSON默认是一整行,字段多了以后很难看。用jq管道格式化一下,可读性立刻提升:curl -s https://api.example.com/users | jq .如果jq没装,用python -m json.tool也能达到类似效果:curl -s https://api.example.com/users | python -m json.tool这两个工具还能用来提取特定字段,比如只看返回列表里每个元素的id和name:curl -s https://api.example.com/users | jq '.[] | {id, name}'调试接口时,这种“请求+格式化+提取”的组合能让你快速看清数据结构,比在终端里瞪着一行超长的JSON高效得多。5.3 保存和复用请求的实战建议调试成功的curl命令,建议随手保存下来。可以建一个requests.md或者api-examples.sh文件,把常用的请求按接口分类记下来。记录的时候,把URL、方法、请求头、请求体都写清楚,最好再附上一条示例响应。下次需要调同一个接口时,直接复制粘贴改参数就行,不用重新回忆当时是怎么拼的。另外,浏览器开发者工具里的“Copy as cURL”功能很好用,可以把浏览器里发出的请求直接导出成curl命令。但要注意,浏览器导出的命令里可能包含很多不必要的请求头(比如User-Agent、Accept-Encoding、Cookie等),复制过来后建议精简一下,只保留必要的部分。特别是Cookie,里面可能包含敏感信息,分享命令给别人之前一定要删掉。5.4 安全使用curl的几条底线第一,不要把密码和Token硬编码在命令行里。命令行会留在shell历史记录中,用history命令就能看到。用环境变量或者配置文件,并且确保配置文件的权限是600。第二,不要对不信任的地址发送带认证信息的请求。curl的-L跟随重定向时,默认会把Authorization头带到新地址,如果重定向到了一个不受控的域名,认证信息就泄露了。可以用--location-trusted显式控制这个行为,或者用--max-redirs 0禁止重定向。第三,生产环境的脚本里一定要设超时。没有超时的curl请求在服务端无响应时会一直挂着,在CI/CD流水线里可能导致整个任务卡死。--connect-timeout和--max-time是最基本的保护。第四,注意请求体里的敏感数据。用-d发送的JSON如果包含手机号、身份证号、密码等敏感信息,在-v或--trace-ascii的输出里会明文显示。调试完成后记得清理终端输出和日志文件。我个人在实际操作中的体会是,curl这个工具看似简单,但真正用好需要理解shell的解析规则和HTTP协议的细节。很多人用了几年curl,遇到问题还是靠“换一种写法试试”,而不是从原理上定位原因。把GET和POST这两个最基础的方法彻底搞清楚,把Content-Type、引号转义、URL编码、超时重试这几个关键点吃透,后面遇到再复杂的请求场景,比如文件上传、多部分表单、自定义认证,都能举一反三。最后再分享一个小技巧:如果你不确定一条curl命令最终发出的请求长什么样,加一个-v参数,它会把请求行、请求头、请求体(部分情况下)都打印出来,这是排查问题最快的方式,没有之一。