curl 发送 JSON POST 请求:参数、引号转义与排查实战

发布时间:2026/11/3 20:21:52
curl 发送 JSON POST 请求:参数、引号转义与排查实战 调接口这件事说到底是两拨人在互相较劲写服务端的人觉得文档写清楚了调接口的人觉得文档等于没写。我见过太多人拿到一份接口说明上面第一行就写着curl -X POST ... -d {a:1}复制粘贴到终端回车屏幕上跳出个 400 或者 415然后就卡在那里了。明明参数一个不差怎么就报错了问题往往不在接口本身而在你根本没搞懂这条 curl 命令的每一块在干什么。如何使用 curl 命令发送 json 格式请求体的 post 请求这个问题看起来像是新手入门级别的疑问但我带过的人里工作三五年还在这个坑里反复摔的并不少见。它牵扯到的其实是四件事HTTP 报文长什么样、curl 的参数各自管什么、你所处的 shell 怎么处理引号、以及出问题之后怎么定位。这四件事任意一环没打通你抄来的命令就是一段玄学咒语改一个字就失灵。这篇内容面向所有需要手工调接口的人——后端自测、前端联调、测试造数据、运维写健康检查脚本都能用得上。我会从报文结构讲起把-d、-H、-X这几个参数的差别掰开重点砸在引号转义和编码这两个最容易翻车的地方最后给一套能直接抄走的脚本模板和排查清单。全文基于我在 Linux、macOS、Windows 三种环境下反复踩坑的经验整理示例接口全部用本地搭的测试服务你可以照着复现。1. 一条 curl 命令背后到底发生了什么1.1 把 POST 请求拆成四个零件很多人对 curl 的理解停留在一个能发请求的工具但真正让你少走弯路的心智模型是curl 干的事情就是按照你给的参数手工拼一段文本然后通过 TCP 连接把这段文本发出去。这段文本就是 HTTP 报文它由四部分构成——请求行、请求头、空行、请求体。请求行长这样POST /api/users HTTP/1.1。它告诉服务端三件事——我用什么方法、我要访问哪个路径、我用哪个版本的协议。请求头是一堆Key: Value比如Content-Type: application/json、Content-Length: 27、User-Agent: curl/8.x。空行是一个纯粹的换行符它是头和体之间的分隔符少了它服务端会把你的请求体当成请求头的一部分去解析直接报格式错误。请求体才是你那段 JSON 真正待的地方。对应到 curl 参数上-X POST决定请求行的第一个词URL 决定第二个词-H决定请求头里的每一行-d决定请求体。而Content-Length这个头你不用管curl 会自己算——你把请求体交给它它数一下字节数自动补上。这里有个细节值得记住它数的是字节不是字符一段包含 10 个汉字的 JSON长度会增加 20 而不是 10因为 UTF-8 下每个汉字占 3 字节。这个区别在后面排查服务端只收到半截 JSON的时候会用到。理解了这层你再看任何一条复杂的 curl 命令都能瞬间把它拆成这四块不会觉得是一坨看不懂的字符串。这也是我教新人时的第一课先不急着敲命令先在纸上把你要发的报文写出来然后再去找对应的参数。1.2 为什么 curl 默认不认为你在发 JSON这是新手最容易困惑的点我明明-d里放的是合法的 JSON 字符串为什么服务端说不支持的媒体类型答案在于 curl 的历史包袱。curl 诞生年代很早那时候最主流的表单提交格式是application/x-www-form-urlencoded所以-d这个参数从设计之初就默认使用这个 Content-Type并且它会顺手把你给的数据做一层处理——把换行符去掉、把多个-d用拼起来完全是按照表单的思路在办事。也就是说你写-d {name:张三}curl 发出去的时候会老老实实地带上Content-Type: application/x-www-form-urlencoded。服务端一看这个头转头去按表单格式解析结果发现解析不出来于是返回 415 或者 400。数据和格式都没错错的是那个它自动帮你加的头。所以规矩很简单只要你的请求体是 JSONContent-Type: application/json这个头必须手动写没有例外。有些框架比较宽容会去猜你的 body 长什么样但依赖服务端的宽容是一种赌博换一个服务实现就翻车。养成手动写头的习惯你的命令换到任何环境都能跑。顺带说一个常见误区不少人喜欢写成Content-Type: application/json; charsetUTF-8。这么写不错但通常没必要。JSON 标准里明确规定文本必须是 UTF-8 编码所以服务端看到application/json就知道该按 UTF-8 处理。有些老服务会在你只写application/json的时候报编码问题那种情况下补上 charset 是合理的但这是特例不是规范要求。1.3 一个能救命的最小模型我建议你在脑子里固化一个最小模型遇到任何 curl 报错都先回到它POST /path HTTP/1.1 Host: xxx Content-Type: application/json Content-Length: N {key:value}这五行就是全部真相。你后面学的所有参数都是在这五行上做增减。-H是往第三行区域塞内容-d是填第五行-X是改第一行的方法名-v是让 curl 把它实际拼出来的报文打印给你看。这套模型的价值在于当你遇到服务端说我少传了某个字段的时候你会立刻想到去看-v输出的实际报文确认字段是不是在传输过程中被 shell 吃掉了、被引号截断了、或者被 cmd 的百分号展开搞没了。绝大多数我明明传了但服务端说没有的问题看一眼-v的输出就能定位比反复改参数高效得多。2. 参数选型-d 的几个变体差在哪2.1 -d、--data-raw、--data-binary、--data-urlencode 对照-d有四个亲戚名字很像行为差别不小。选错一个可能就是本地测试好好的换台机器就废了。参数对数据的处理适用场景-d/--data去掉换行符多个-d用连接开头会读文件并去换行短小的单行 JSON、表单--data-raw与-d相同但不把当文件引用符请求体里含字符--data-binary原样发送一个字节都不改多行 JSON、含换行的文本--data-urlencode对数据做 URL 编码后发送表单参数、必须编码的字段这几个里面我最常用的是-d和--data-binary。短的一行 JSON 用-d完全够用一旦你的 JSON 是格式化过的、带缩进和换行的就必须用--data-binary。原因在于-d会把换行符统统删掉你的多行 JSON 会被压成一行——压成一行本身对服务端没有影响JSON 解析器不在乎换行但如果你是想把请求体签名或者做哈希校验换行被删掉会导致签名对不上这个坑我在对接过一家需要 body 摘要校验的服务时踩得结结实实。--data-raw的使用频率低但有一个场景必须用它请求体里包含邮箱地址或者开头的字符串。因为-d file.json的语法是从文件读取你如果直接写-d {email:ab.com}curl 发现第一个字符是{不是不会误判但如果你写--data something它就会去找一个叫 something 的文件。为了彻底避免这类歧义含的请求体我一律用--data-raw。--data-urlencode在发 JSON 的时候基本用不到它服务的是表单场景。但有一个变体值得一提--data-urlencode filebody.json会读取文件内容并做 URL 编码。这个用法在处理老式接口的时候会碰到你在 JSON 场景里看到它的概率不高。2.2 -H 写头的三种风格与各自的坑-H的写法看似简单实际藏着几处容易忽略的细节。最基本的格式是-H Key: Value冒号后面那个空格不是必须的但强烈建议加上因为有些服务端的解析器写得不严谨遇到Content-Type:application/json这种没有空格的写法会解析失败。加上空格成本为零能规避一类莫名其妙的问题。第二个细节是-H可以重复出现写多个头就写多个-H。很多人会尝试在同一个-H里用换行符塞多个头比如-H A: 1\nB: 2这在大多数 shell 下都不成立因为引号里的\n只是个反斜杠加字母 n 两个字面字符不会变成真正的换行。curl 收到的是一个包含奇怪字符的头服务端大概率直接拒绝。第三个细节也是最容易出事的一个不要自己写Content-Length。有人看到抓包里有个Content-Length: 27就想着也手动加上去这样更完整。结果请求体一改长度对不上服务端要么截断请求体、要么直接返回 400。让 curl 自己算它算得比你准。唯一需要你干预长度的情况是发送空 JSON 对象{}长度是 2这个它也能算对。关于-H我还想补一个实战心得除了Content-Type很多接口还要求Accept: application/json。这个头的意思是我希望你返回 JSON。虽然大多数服务端不强制但有些网关或者内容协商做得比较严格的框架会根据 Accept 头来决定返回格式你不写它就给你吐一段 HTML 错误页然后你的 JSON 解析器在那边报unexpected token 。加上这个头能省掉一类排查。2.3 -X POST 到底该不该写关于这个参数社区里有两派意见。一派说必须写理由是明确表达意图另一派说不用写因为-d已经隐含了 POST 方法写了反而容易出错。我的建议是写但要知道它为什么可能出错。写的好处是命令自解释别人看你的脚本一眼就知道这是 POST。风险在于一旦你写了-X POST同时又用了-G把数据拼到 URL 查询串上curl 会真的用 POST 方法去发一个没有请求体的请求某些服务端会因此返回 405。但这种组合本身就不常见。真正需要注意的场景反过来如果你只写了-X PUT而没写-dcurl 会发一个没有请求体的 PUT 请求有些服务端要求 PUT 必须有 body就会返回 411 Length Required。所以配套关系是方法名和请求体要一起考虑不能只改一个。另外一个隐蔽的坑-X是个强制覆盖参数它会覆盖 curl 基于其他参数自动推断出来的方法。比如你写了-X POST -F filea.txt-F本意是发起 multipart 表单上传通常配 POST你这么写没毛病但如果你写-X GET -F filea.txt就会发出一个带 multipart body 的 GET 请求服务端可能直接崩掉。JSON 场景下-X POST配合-d或--data-binary是最稳的组合别乱跨。3. 引号与转义九成的报错出在这里3.1 Linux 和 macOS单引号是首选在 bash、zsh 这类 shell 下写 JSON 请求体的首选方案是用单引号把整段 JSON 包起来curl -X POST http://localhost:8080/api/users \ -H Content-Type: application/json \ -d {name:张三,age:28}为什么首选单引号因为在单引号里shell 不做任何解析——$、反引号、反斜杠、双引号全部当作普通字符原样传递。而 JSON 里天然大量使用双引号用单引号包裹能避免一次转义地狱。代价是单引号里不能出现单引号。这个限制看起来很致命因为 JSON 字符串里确实可能出现英文单引号比如{name:ONeil}。遇到这种情况标准做法是把单引号断出来-d {name:O\Neil}这段看起来像乱码的东西逻辑是{name:O是第一个单引号字符串\是一个被转义的裸单引号Neil}是第二个单引号字符串。三段拼起来shell 最终传给 curl 的就是{name:ONeil}。这个技巧值得背下来写脚本的时候会反复用到。如果你不想记这个还有个更实际的建议含单引号的 JSON别写内联写到文件里去用--data-binary body.json。既避开了转义问题也让命令变得干净。这个习惯我保持了几年凡是请求体超过一行的一律走文件。3.2 Windows cmd双引号地狱与百分号陷阱切到 Windows 的 cmd画风立刻变了。cmd 不认单引号你写-d {a:1}它会把单引号当成普通字符传下去curl 收到的请求体就变成了{a:1}两边带引号JSON 解析直接失败。所以在 cmd 里必须用双引号包裹而 JSON 内部的双引号要用反斜杠转义curl -X POST http://localhost:8080/api/users ^ -H Content-Type: application/json ^ -d {\name\:\张三\,\age\:28}注意 cmd 的续行符是^不是\这是另一个常见的手滑点。而且^后面不能有空格有空格续行就失效了。更大的雷在百分号。cmd 里%是变量引用符%PATH%这种会被展开。如果你请求体里恰好有%比如{discount:20%}cmd 会尝试把它当变量处理。命令行里单独的%通常不会出问题但在批处理文件.bat里%需要写成%%。这个差异导致过一个经典现象同一条命令在命令行窗口跑得好好的写进 bat 脚本就报 JSON 语法错误排查半天最后发现是百分号被吃了。还有一个容易忽略的点cmd 的代码页。默认代码页可能是 936GBK你从别处复制的中文粘进去curl 按当前代码页的字节序列发出去服务端按 UTF-8 解中文就成了乱码。稳妥做法是用chcp 65001把代码页切到 UTF-8或者干脆把 JSON 写成 UTF-8 编码的文件用-d body.json读。3.3 PowerShell 里 curl 不是 curl这一条单独拎出来说因为它是 Windows 上最隐蔽的坑。在 PowerShell 里输入curl实际执行的很可能是Invoke-WebRequest的别名而不是真正的 curl 可执行文件。它的参数体系完全不同你照着网上的教程写-d、-H会收到一堆找不到参数的报错。验证方法很简单跑一下Get-Command curl如果输出显示Alias和Invoke-WebRequest就说明你踩上了。解决办法是显式写curl.execurl.exe -X POST http://localhost:8080/api/users -H Content-Type: application/json -d {\name\:\张三\}PowerShell 的续行符是反引号它的引号规则又和 cmd 不一样单引号是字面量字符串双引号会解析变量。理论上用单引号包 JSON 最省事但 PowerShell 5.1 在调用外部 exe 时对参数的重新解析有一套很别扭的逻辑会二次处理引号导致原本写对的转义在传给 exe 的时候变样。这也是为什么你在网上搜 PowerShell curl json会看到各种五花八门的转义写法每一种都号称能用换台机器就不灵。我的经验是在 Windows 上别跟引号较劲直接把 JSON 写进文件用-d body.json。这条规则适用于 cmd、PowerShell 5.1、PowerShell 7 全部场景一次写好到处能跑。花在转义上的时间足够你写完三个接口的测试脚本了。3.4 变量注入什么时候该用双引号脚本化的时候你一定需要往 JSON 里填变量比如把用户名、时间戳、订单号塞进去。这时候单引号就不行了因为单引号会阻止变量展开。你得改用双引号包裹并注意两点外层用双引号之后JSON 内部的双引号需要转义变量要用${var}的形式包起来避免和后面的字符粘连。name张三 uid1001 curl -X POST http://localhost:8080/api/users \ -H Content-Type: application/json \ -d {\name\:\${name}\,\id\:${uid}}注意id那个位置没有加引号因为它是数字类型加了引号就变成字符串1001服务端如果做了严格的类型校验就会报错。这种细节在拼 JSON 的时候特别容易忽略尤其是从 shell 变量拿到的值本来就是字符串你得自己判断它在目标 JSON 里该是什么类型。如果变量值里可能包含双引号、反斜杠、换行这些特殊字符纯手工拼接就会变得极其脆弱。这种情况我推荐用jq来构造 JSON它是专门干这个的payload$(jq -n --arg name $name --argjson id $uid {name: $name, id: $id}) curl -X POST http://localhost:8080/api/users \ -H Content-Type: application/json \ -d $payload--arg会把值当字符串处理并自动转义--argjson会把值当 JSON 解析数字、布尔、数组都能用。这段代码的可读性比手工拼接高一个量级而且不会因为用户名里有个引号就崩掉。多花五分钟学jq -n能省掉后面无数次的为什么只有某个特定用户的数据提交失败。4. 从零跑通完整实操流程4.1 先准备一个能吃 JSON 的测试端点调接口之前手里得有个能回显请求的端点否则你连自己发出去的东西对不对都不知道。我用 Python 标准库搭一个最小的不需要装任何依赖粘进文件保存成echo_server.py就能用from http.server import BaseHTTPRequestHandler, HTTPServer import json class Handler(BaseHTTPRequestHandler): def do_POST(self): length int(self.headers.get(Content-Length, 0)) raw self.rfile.read(length) info { method: self.command, path: self.path, content_type: self.headers.get(Content-Type), raw_body: raw.decode(utf-8, errorsreplace), } try: info[parsed] json.loads(raw) info[json_ok] True except Exception as e: info[json_ok] False info[error] str(e) body json.dumps(info, ensure_asciiFalse, indent2).encode(utf-8) self.send_response(200) self.send_header(Content-Type, application/json; charsetutf-8) self.send_header(Content-Length, str(len(body))) self.end_headers() self.wfile.write(body) HTTPServer((127.0.0.1, 8080), Handler).serve_forever()跑起来python echo_server.py它会把收到的 Content-Type、原始 body、解析结果原样吐回来。这个回显服务是我每次换电脑都会重新搭一遍的东西因为排查问题时服务端到底收到了什么和我以为我发了什么往往是两回事中间差的那部分就是 bug 所在。用一个回显端点做验证还有一个好处它能明确告诉你 JSON 是不是合法的。上面代码里json_ok那个字段就是干这个的语法错的时候会把解析器的错误信息带回来比如 Expecting property name enclosed in double quotes直接指向问题所在比对着服务端返回的一句参数错误瞎猜高效得多。4.2 最简命令与逐项验证服务跑起来之后先发一条最简单的curl -X POST http://127.0.0.1:8080/api/users \ -H Content-Type: application/json \ -d {name:张三,age:28}预期返回里json_ok应该是trueparsed里能看到你的字段content_type是application/json。如果这三项都对说明整条链路通了。接着做个对照实验把-H那行删掉再发一次。你会发现json_ok依然是true——因为我的测试服务端比较宽容不管你声明什么类型都尝试按 JSON 解析。但content_type会变成application/x-www-form-urlencoded。这正是真实环境里出问题的根源宽容的服务端能容忍严格的服务端会直接 415。这个对照实验建议你自己跑一遍亲眼看到那个头的变化比看十遍文档印象都深。再做一个实验故意把 JSON 写错比如把某个双引号换成单引号或者末尾多一个逗号。观察返回的error字段。这一步是在建立你对错误信息的敏感度——真实环境里服务端可能只返回一个 400 加一句模糊的提示你得能从自己的经验里推断出大概是哪类语法错误。4.3 请求体放文件file 与 -请求体一旦超过一行或者包含大量特殊字符就该搬到文件里。语法是-d 文件名注意和文件名之间不能有空格。curl -X POST http://127.0.0.1:8080/api/users \ -H Content-Type: application/json \ --data-binary body.json这里我用--data-binary而不是-d理由前面说过-d file会删掉文件里的换行符--data-binary原样发送。对普通接口两者没区别但涉及内容校验、日志留痕、幂等签名的场景保留原始格式更安全。既然都要写文件了不如一步到位用更保真的那个。-这个写法是从标准输入读实战中非常有用。它让 curl 能接在管道后面比如jq -n --arg n 张三 {name:$n, ts: (now|floor)} \ | curl -X POST http://127.0.0.1:8080/api/users \ -H Content-Type: application/json \ --data-binary -这段的意思是用 jq 动态生成一个带当前时间戳的 JSON通过管道喂给 curl。好处是 JSON 的构造和发送彻底解耦构造部分出错了你能单独调试不用每次都发一次真实请求。批量造数据的时候这个模式可以塞进for循环一行命令灌几百条。用文件还有两个容易忽略的注意点。第一是编码文件必须是 UTF-8不能带 BOM那个文件开头的不可见字符会让 JSON 解析器报unexpected character。Windows 记事本另存为 UTF-8 的时候有这个选项一定要选不带 BOM 的那个。第二是路径后面的路径是相对于你执行 curl 命令时所在的目录不是相对于脚本文件所在目录。脚本里用相对路径换个目录执行就找不到文件这种问题排查起来很浪费时间建议脚本里一律用绝对路径或者先cd到脚本目录。4.4 变量注入与脚本化模板单个请求跑通之后下一步是把它变成可复用的脚本。我常用的模板长这样#!/usr/bin/env bash set -euo pipefail BASE_URL${BASE_URL:-http://127.0.0.1:8080} TOKEN${API_TOKEN:?缺少 API_TOKEN 环境变量} payload$(jq -n \ --arg name $1 \ --argjson age $2 \ {name: $name, age: $age}) curl -sS -X POST ${BASE_URL}/api/users \ -H Content-Type: application/json \ -H Accept: application/json \ -H Authorization: Bearer ${TOKEN} \ --data-binary $payload \ -w \nHTTP_STATUS:%{http_code}\n几个设计意图说明一下。set -euo pipefail让脚本遇到错误立刻停避免带着错误状态继续往下跑。${VAR:?}的写法在变量缺失时直接报错退出比让空 token 发出去、然后收到一个莫名的 401 好得多。-sS是-s和-S的组合前者静音进度条后者保留错误信息——单独用-s会把错误也吞掉这是很多人脚本调试困难的原因。-w把 HTTP 状态码打到输出末尾方便后续 grep。关于-w它支持的变量比大多数人以为的多。除了%{http_code}还有%{time_total}总耗时秒数、%{size_download}响应体字节数、%{content_type}。做简单的接口性能对比时加个-w %{time_total}\n就能看出哪个接口慢不用上压测工具。4.5 查看响应-i、-o 与 jq 串接默认情况下 curl 只打印响应体看不到响应头。加上-i会把响应行和响应头一起打出来调试的时候很有用因为你的 JSON 里没有的信息往往在头里比如限流剩余次数、请求追踪 ID、缓存状态。反过来如果你只想看头不想看体用-I发一个 HEAD 请求或者-D -把头 dump 到标准输出。这个在排查 3xx 跳转的时候特别有用-i能让你看到Location头从而知道请求被重定向到哪里去了。之前遇到过一个接口本地调一直 200 但数据不对加了-i才发现服务端返回了 302 跳到一个登录页curl 默认不跟随跳转于是你拿到的是跳转响应的空 body。补上-L让它跟随问题立刻暴露成 401。-o是把响应体写到文件-O是用 URL 里的文件名保存。这两个参数在处理大响应的时候有用日常调 JSON 接口用得不多。倒是-s加管道接jq的组合值得一说curl -sS -X POST http://127.0.0.1:8080/api/users \ -H Content-Type: application/json \ -d {name:张三,age:28} | jq .服务端如果返回的是一坨压缩过的长 JSON人眼几乎没法读。jq .会把它格式化成带缩进的结构还能高亮。更进一步jq .data.id直接取某个字段写脚本的时候用得上省掉一次 grep 加 sed 的痛苦。5. 常见返回值与排查路径5.1 状态码速查表下面这张表是我这些年遇到过的、和 JSON POST 直接相关的返回码。看到某个码能立刻对上原因排查效率会高一个档次。状态码典型含义首先怀疑什么400请求格式错误JSON 语法错、内容被 shell 截断、字段类型不符401未认证少了 Authorization 头、token 过期403无权限token 有效但角色不够、IP 白名单404路径不存在URL 拼错、多了一层或少了一层路径、被网关重写405方法不允许用了 GET 调 POST 接口、-X写错411需要 Content-Length发了空 body、某些服务端对 PUT 强制要求413请求体过大超过服务端 body 上限需要分批发送415媒体类型不支持Content-Type 没写或写错最常见500服务端内部错误你的 JSON 结构和服务端模型不匹配也可能是它自己崩了502/504网关问题后端没起来或超时和你发的请求无关看表的时候注意一个思维顺序4xx 是你的问题5xx 是它的问题。但 500 未必真的和你无关——传了一个服务端没做空值校验的字段照样能让它崩。所以收到 500 之后第一件事是把请求体简化到最小一个字段一个字段加回去用二分法定位是哪个字段触发的。5.2 几条典型报错的拆解Unexpected token in JSON at position 1。这个报错的位置信息非常精准position 1 意味着它读到的第一个字符不是{也不是[。结合前面的引号知识基本可以断定是 cmd 环境用了单引号包裹导致单引号被当成内容发过去了。修法就是换双引号加转义或者走文件。Unexpected end of JSON input。意思是 JSON 被截断了读到一半没了。最常见的原因是请求体里有未被转义的双引号导致 shell 提前截断了字符串。比如-d {a:1}这种写法shell 在第二个双引号处就结束了参数curl 实际收到的只有{。另一个可能是 Content-Length 和你实际发的字节数不匹配——但这种情况通常你自己不算 Content-Length更可能是用-d处理了含特殊字符的内容导致字节数变了。用-v看实际发出的报文长度一眼就清楚。415 Unsupported Media Type。字面意思就是 Content-Type 不对。检查三件事-H是不是漏了、冒号后是不是有空格、拼写是不是application/json别写成application/Json或者application/jsonp。还有一种情况是你同时传了两个-H都设置了 Content-Typecurl 会把两个都发出去某些服务端取第一个某些取最后一个行为不一致。这种问题只能靠-v看出来。中文变成问号或者乱码。这几乎一定是编码问题。三个检查点终端代码页Windows 下chcp、文件编码是否 UTF-8 无 BOM、以及你声明的 Content-Type 里有没有冲突的 charset。最稳的做法是把 JSON 写进文件确认文件是无 BOM 的 UTF-8然后用--data-binary file发。我在 Windows 上处理中文 JSON 一律走这条路径从来没出过错。服务端说少了某个字段但我明明传了。这时候不要改代码先去用-v看实际发出的请求体。十有八九是那个字段被 shell 吃掉了。常见凶手是!在交互式 bash 里的历史展开、$被当变量、%在 cmd 里被当变量。另一种可能是那个字段的值是空字符串某些框架会把空字符串当成未传处理这就得看服务端的实现了。5.3 一套固定的排查顺序遇到问题时按固定顺序走别东一榔头西一棒子。我自己的顺序是加-v看 curl 实际发出的请求行、请求头、请求体。确认你发的和你想的一样。用回显端点替代真实接口。如果回显端点收到的内容是对的问题在服务端如果不对问题在命令行。缩小请求体。把 JSON 简化到{}看能不能通。能通就逐字段加回去二分定位。检查环境差异。本地能通、服务器不能通重点看 shell 类型、代码页、curl 版本curl --version。看响应头不看响应体。很多框架把详细错误放在头里或者特定的错误字段里光看 body 那句话会误判。这五步里第一步的价值最高。我见过太多人跳过-v直接开始猜改了半天参数最后发现是 shell 把某个字符吃了。养成先看实际发出什么的习惯能省掉大量时间。6. 工程化把 curl 用进脚本与流水线6.1 超时、重试与退出码默认情况下 curl 没有超时限制遇到一个不响应的服务端它能挂在那里等到天荒地老。脚本里必须加上--connect-timeout和--max-time前者管建立连接后者管整个请求的耗时。curl -sS \ --connect-timeout 3 \ --max-time 10 \ --retry 2 \ --retry-delay 1 \ --retry-connrefused \ -X POST http://127.0.0.1:8080/api/users \ -H Content-Type: application/json \ --data-binary body.json参数选择的依据连接超时设 3 秒因为正常的内网连接建立应该在毫秒级3 秒还没连上基本可以判定不通总超时设 10 秒取决于你的接口正常耗时一般设成平均耗时的 3 到 5 倍。--retry只对网络层错误和 5xx 生效默认不会对 4xx 重试这个设计很合理——400 你重试一百次还是 400。有一个坑必须提curl 默认对 4xx 和 5xx 返回退出码 0。也就是说服务端返回 500curl 认为我成功完成了这次 HTTP 交互脚本里的set -e不会触发你会带着一个错误的响应继续往下走。想让 curl 在 HTTP 错误时返回非零加--fail或--fail-with-body后者在失败时仍然输出响应体更适合调试。这个参数加上之后配合脚本的错误处理才真正可靠。if ! curl -sS --fail-with-body -o resp.json -w %{http_code} \ -X POST $URL \ -H Content-Type: application/json \ --data-binary body.json status.txt; then echo 请求失败状态码 $(cat status.txt) 2 cat resp.json 2 exit 1 fi这段把状态码和响应体分别落到两个文件失败时一起打出来日志里能完整还原现场。写自动化脚本的时候我习惯把这个模式固定下来后面所有接口调用都套这个壳子错误处理逻辑只写一次。6.2 认证头与自定义头的组织认证方式无非几种Authorization: Bearer token、X-API-Key: key、Basic 认证、Cookie。前两种直接用-H加。Basic 认证可以手写-H Authorization: Basic $(echo -n user:pass | base64)也可以用-u user:pass让 curl 帮你编码后者更简洁也更不容易错。关于 token有一条安全习惯值得强调不要把 token 硬编码在命令行里。命令行参数在进程列表里是可见的同一台机器上其他人ps aux就能看到。脚本里一律从环境变量读加上前面说的${VAR:?}保护。curl -sS --fail-with-body \ -X POST $URL \ -H Content-Type: application/json \ -H Authorization: Bearer ${API_TOKEN:?未设置 API_TOKEN} \ -H X-Request-Id: $(uuidgen) \ --data-binary body.jsonX-Request-Id这类追踪头在很多分布式系统里是必备的方便你把一次请求在多个服务的日志里串起来。加上它成本很低排障时收益很大。还有一个环境相关的注意点如果服务端用了自签证书本地测试时-k能跳过校验但这条命令绝对不能进生产脚本。我在代码评审里见过-k被一路复制到线上健康检查脚本里等于把 TLS 校验整个废掉这类问题很难在测试阶段暴露。稳妥做法是本地测试用-k同时留一条注释说明为什么用、什么时候删掉。6.3 踩过的坑与几条个人经验关于请求体大小有一个容易被忽略的行为当 body 超过一定阈值通常是 1KB 左右curl 会自动加上Expect: 100-continue头先问服务端我要发一大坨你准备好接收了吗等服务端回 100 再发。大多数服务端正常处理但有些老网关或者反向代理不认这个流程会导致请求卡住几十秒然后超时。遇到这种大 body 就超时、小 body 就正常的诡异现象加-H Expect:把这个头去掉问题立刻消失。这个坑我踩过一次排查了整整一个下午最后是在一份很老的邮件列表存档里找到的答案。关于-v和-s的冲突-v的输出走标准错误-s静音的是进度条两者可以共存。但如果你把标准错误重定向到文件-v的调试信息和-w的输出会混在一起需要自己分辨。我的习惯是调试阶段不用-s看清楚每个环节确认无误后再加-sS上生产。关于 curl 版本差异--fail-with-body是 7.76 版本才有的--retry-connrefused是 7.52 之后。在老系统上跑脚本先curl --version确认一下别写出一个在自己机器上好好的、到服务器上就报 unknown option 的脚本。这类问题在容器环境里尤其常见基础镜像自带的 curl 版本可能非常老。最后一条是关于要不要用 curl 调接口这件事本身的判断。简单的自测和一次性调试curl 是最快的不用打开任何图形工具复制粘贴就能跑还能直接贴进文档和聊天窗口让别人复现。但如果你需要频繁调整参数、管理多套环境变量、保存请求历史那还是上专门的接口工具更合适。curl 的定位是随手可用的瑞士军刀不是全功能工作台认清这一点你就不会在它不擅长的地方硬刚。我个人的习惯是凡是写进文档、写进脚本、写进工单的接口调用示例一律用 curl因为它是最小公分母谁都能跑凡是需要反复调试的探索性工作用图形工具省下的是心力和时间。两者不冲突各干各的活。最后分享一个小技巧如果你经常需要在不同环境之间切换把 BASE_URL 和 token 都放进环境变量命令本身只保留路径和请求体这样同一段命令在测试环境和预发环境之间来回切只改环境变量不动命令。这个小改动看着不起眼但它能让你少犯一类错误改命令的时候漏改了某个地方结果一半请求打到测试环境、一半打到预发环境日志对不上排查起来极其痛苦。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询