1. curl 到底解决了什么问题接口联调最尴尬的一幕是对方甩给你一句我这边测着没问题然后就没了下文。你把浏览器打开、地址栏粘上、参数一个个敲回去结果因为少带了一个请求头返回的是另一套错误。这种时候一行能原样复现问题的 curl 命令价值远超十页文档。curl 是命令行下的网络传输工具本质做的事情非常朴素按你的指令拼出一个 HTTP 请求发出去再把服务器吐回来的东西原样交给你。它不懂业务、不猜语义、不帮你美化数据正因为这份笨它成了排查问题、验证接口、做自动化脚本时最可信的那把尺子。这篇内容面向三类人刚接触后端接口、还停留在浏览器点来点去的新手写了几年业务代码、但对 curl 只停留在curl 网址这一层的开发者以及需要把网络请求写进部署脚本、定时任务的运维或测试同学。全篇围绕一个目标展开就是让你在看到任意一条 curl 命令时能读懂它的每一个参数在需要构造一条命令时能一次写对在请求出问题时知道从哪个开关下手。所有示例都基于公开保留的示例域名与本地地址你换成自己的接口即可直接跑。1.1 一行 curl 命令在真实工作流里的位置我习惯把 curl 定位成请求的可执行快照。浏览器里的操作是不可复现的你点了几次、中间经过了哪些跳转、带了哪些自动附加的头很难讲清楚图形化工具里的配置是存在本地工程里的发给别人还得导出一个文件。curl 不一样它就是一段纯文本可以贴在工单里、写进文档里、塞进 CI 脚本里还能被版本管理追溯。一份缺陷报告里如果附了一行可执行的 curl复现成本能从半小时压缩到十秒。更实际的一点是curl 处在最接近协议的那一层。你看到的响应就是服务器真发出来的东西没有浏览器帮你解析 HTML、没有前端框架帮你解 JSON、没有工具帮你重定向到别的地址。排查跨域、缓存、压缩、编码、重定向链这类问题时中间层越少越好判断。我曾经为了确认一个响应到底是服务端没返回某个头还是前端在某处把它吃掉了直接用 curl 把原始响应头打出来两分钟就有了结论。当然它也有明确的能力边界。curl 不是浏览器不执行 JavaScript不做页面渲染不维护完整的会话状态除非你显式让它保存 Cookie。所以抓取动态渲染页面的内容、模拟复杂的登录跳转流程用它会很别扭。清楚它擅长什么、不擅长什么比背参数表重要得多。1.2 几个替代品的取舍对照工具没有绝对优劣只有场景匹配度。下面这张表是我在带新人时常用的对照列的是实际判断依据而不是功能清单。工具适合场景主要短板我的使用频率curl接口调试、脚本自动化、问题复现、文件传输无可视化、复杂参数需要记忆每天浏览器开发者工具观察真实页面发起的请求、复制为命令行难复现、依赖页面操作路径排查前端问题时图形化 API 调试工具多人协作维护请求集合、环境变量管理命令不便嵌入脚本与流水线中等wget整站镜像、递归抓取请求构造能力弱于 curl偶尔语言内置 HTTP 库需要业务逻辑编排、结果解析写一次性验证脚本太重写正式代码时有个细节值得说浏览器开发者工具里右键复制出来的请求本质上就是一条 curl 命令这说明了 curl 的参数体系已经成为描述 HTTP 请求的通用语法。学会它等于学会了一门跨工具的请求方言很多平台的导入导出都认这个格式。1.3 版本差异比你想的更重要在动手之前先跑一次curl --version这是我养成的第一个习惯。输出里除了版本号还有一行 features标明了这个二进制支持哪些协议与能力。很多网上抄来的参数在你的机器上不生效八成是版本太老。几个关键的时间节点值得记住--fail-with-body在 7.76 之后才有--parallel是 7.66 引入的--json这个便捷参数要 7.82 以上--url-query则更晚。如果你在容器镜像里跑脚本基础镜像自带的 curl 可能比本机老好几年本地测通、线上报错的情况大多源于此。我的做法是在脚本开头做一次能力探测缺关键参数就直接失败退出而不是让它跑到一半给出莫名其妙的结果。提示升级 curl 时注意它对 TLS 后端库的依赖不同后端在证书校验、协议支持上的表现不完全一致。生产脚本里如果需要固定行为把版本写进依赖清单别依赖运行环境的默认版本。2. 请求构造的基本功URL、方法、头与体绝大多数 curl 用不明白的问题根源不在参数多而在对 HTTP 请求的四个组成部分没有清晰的模型请求行方法加路径、请求头、空行、请求体。curl 的每个参数本质上都是在往这四块里填东西。脑子里有这张结构图参数就不是死记硬背而是我想改哪一块。2.1 URL 里每个字符都在说话先把最容易被忽略的一件事说清楚URL 里的查询参数需要编码。空格、中文、、、#这些字符出现在值里时不编码就会把参数结构冲垮。手写编码既费神又易错--data-urlencode配合-G是更稳的做法它会把数据编码后拼到查询串上curl -G https://api.example.com/search \ --data-urlencode keyword命令行 工具 \ --data-urlencode page1请求方法上有个坑得提前说-d会自动把方法从 GET 改成 POST-I会改成 HEAD-T会改成 PUT。所以当你只想加个请求体、但方法必须是 PUT 时-X PUT是必要的。反过来如果你用-X POST又加了-Lcurl 在跟随 301、302 重定向时会把方法改成 GET这是 HTTP 规范的行为不是 bug。真要保留方法和请求体用--post301、--post302、--post303显式声明。主机解析也常被拿来做排查手段。当你不确定问题出在 DNS 还是服务本身--resolve可以绕过解析直接指定 IP同时保留 Host 头与证书校验所需的域名信息curl --resolve api.example.com:443:203.0.113.10 https://api.example.com/health这招在切换机房、验证新节点、排查解析污染类问题时特别顺手比改 hosts 文件干净得多也不会污染本机环境。2.2 请求体的三条路-d、--data-binary 与 -F这三个参数混用是新手最常见的错误来源。它们的区别不在发不发数据而在怎么编码。-d会把内容当作表单数据处理多个-d之间用拼接并且会去掉换行。这意味着你用-d payload.json发一个格式化过的 JSON 文件里面的换行会被吃掉——多数情况下无害但如果服务端对原始字节做了签名校验就会直接失败。--data-binary保留原始字节不做任何加工是发文件内容的首选。发 JSON 目前最省事的写法是--json它会自动设置Content-Type: application/json和Accept: application/jsoncurl --json {name:demo,enabled:true} https://api.example.com/items老版本就用显式头加二进制数据效果一致curl -X POST https://api.example.com/items \ -H Content-Type: application/json \ --data-binary payload.json-F走的是完全不同的路multipart/form-data。只要涉及文件上传基本都得用它。它支持给单个字段追加元信息比如指定文件名和类型curl -F file./report.pdf;typeapplication/pdf;filenamefinal.pdf \ -F note季度汇总 \ https://api.example.com/upload注意-d和-F不要混在同一条命令里。混用会让 curl 以 multipart 为准之前用-d加的数据可能被静默丢弃而响应看起来还挺正常排查起来相当折磨人。2.3 认证、Cookie 与会话保持-u user:pass做的是 HTTP Basic 认证把凭据做 Base64 编码后放进请求头。Base64 不是加密任何能看到报文的人都能还原出原文所以这条命令只在 HTTPS 下使用。如果密码里有特殊字符加引号或者用-u user让 curl 交互式询问避免密码进 shell 历史。需要保持会话时-c写 Cookie、-b读 Cookie两个配合就相当于一个极简的会话管理器curl -c cookies.txt -X POST https://api.example.com/login \ -H Content-Type: application/json \ --data-binary {user:demo,pass:***} curl -b cookies.txt https://api.example.com/profileBearer 令牌这类认证本质就是改请求头-H Authorization: Bearer $TOKEN即可。这里提醒一句令牌别直接写死在脚本里从环境变量读并且注意别让-v把完整的认证头打到日志里去。我在一次排查中就把带令牌的调试输出贴进了公共频道虽然及时发现并轮换了但那次教训之后所有调试脚本我都默认加了一层脱敏处理。3. 调试与输出控制把黑盒打开请求发出去没反应是最让人抓狂的状态。curl 提供的调试手段其实相当丰富只是很多人只记得一个-v遇到 TLS 层或者重定向链的问题就卡住了。这一章按看得多细来分层讲。3.1 详细模式的三层-v、--trace 与抓包之外-v是最常用的它把请求头、响应头、连接建立过程打到 stderr用和区分方向。够用但有个限制响应体不打且只给概要。当你需要看完整的字节流、包括那些不可打印字符时--trace上场curl --trace trace.log --trace-time https://api.example.com/health--trace给的是十六进制加 ASCII 的双栏输出--trace-ascii只给可打印的文本部分日志更易读。--trace-time会给每一行加时间戳这个在分析卡在哪一步时非常关键你能直观看到 DNS 解析花了多久、TLS 握手花了多久、服务器思考了多久才吐第一个字节。有些慢请求根本不是服务端算得慢而是 TLS 协商反复重试。-v还有一个隐藏用法是看它到底带了哪些默认头。你会发现 curl 默认加了User-Agent、Accept: */*如果是 POST 还会加Content-Length或Expect: 100-continue。最后这个头在大请求体时经常惹麻烦某些老旧网关处理不好会直接挂住-H Expect:把它去掉就能通。提示-v的输出走的是 stderr如果你把 stdout 重定向到文件终端上照样能看到调试信息两者互不干扰。这个特性在写脚本时很有用。3.2 让输出变成可编程的数据-w 实战如果说-v是给人看的那-w就是给程序看的。它能把你关心的指标格式化成一行方便后续解析入库。常用的变量有%{http_code}、%{time_total}、%{time_starttransfer}、%{size_download}、%{remote_ip}等。curl -s -o /dev/null \ -w code%{http_code} connect%{time_connect} first_byte%{time_starttransfer} total%{time_total}\n \ https://api.example.com/health-o /dev/null意味着响应体扔掉只留指标。做接口健康巡检时我把这条命令塞进循环几十个接口的耗时能一次性拉出表格比逐个打开工具快得多。指标之间存在勾稽关系读懂它们能省下大量猜测time_namelookup是 DNS 耗时time_connect是 TCP 握手完成的时间点time_appconnect是 TLS 完成的时间点time_starttransfer是收到第一个字节的时间点time_total是全部结束。如果time_connect到time_appconnect差值很大问题在 TLS 协商如果time_starttransfer到time_total差值很大说明服务端在吐数据的过程中卡住了通常是响应体太大或带宽受限。4. 文件传输与批量任务的实用配置curl 最初是以用 URL 语法传文件出名的这块能力到今天依然很能打只是被 HTTP 调试的光环盖住了。下载大文件、断点续传、限速、批量拉取几个参数组合起来就是一套轻量的传输方案。4.1 下载、续传与限速最省事的下载是-O用远端文件名存到本地想指定名字就用-o。如果服务端返回了Content-Disposition-J能让 curl 用它给出的文件名三个组合起来是curl -OJ https://mirror.example.com/releases/app-1.2.0.tar.gz-O有个坑必须知道它取的是 URL 最后一段如果 URL 带查询串文件名会变得又长又怪甚至包含非法字符。稳妥做法永远是显式-o。断点续传用-C -让 curl 自己探测本地已有文件的长度并从那个位置接着下。这个参数只在服务端支持 Range 请求时有效不支持的话 curl 会从头下。判断方法很简单看-v里服务端有没有返回Accept-Ranges: bytes。限速用--limit-rate单位可以是 k、m、g。在共享带宽的机器上跑批量下载不限速会影响到同机器的其他服务这是我踩过的坑一次深夜跑备份脚本把整台机器的出网带宽占满导致同机上的监控采集全部超时告警。加上--limit-rate 2m之后风平浪静。4.2 重试策略与并发网络抖动导致的失败靠重试解决最经济。--retry N会在遇到瞬时性错误超时、连接重置、5xx时重试默认还有退避。几个配套参数决定行为细节--retry-delay固定间隔--retry-max-time总时长上限--retry-connrefused把连接被拒也算作可重试。有一个必须提醒的点默认情况下curl 只对幂等请求GET、HEAD、PUT、DELETE 等重试非幂等的 POST 不会自动重试这是保护机制。如果你确定某接口可以安全重试用--retry-all-errors放开限制但请先确认服务端做了幂等处理否则重复下单、重复扣款这类事故就是这么来的。curl --retry 3 --retry-delay 2 --retry-connrefused \ --connect-timeout 5 --max-time 60 \ -o app.tar.gz -C - https://mirror.example.com/app.tar.gz并发下载是 7.66 之后才有的能力-Z开启并行配合多个 URL 和--parallel-max控制并发度。几十个小文件一起拉的时候比串行快一个数量级。但要留意并发对目标服务的压力也留意本地文件描述符上限。至于上传-T走 PUT--upload-file是它的长写法。上传大文件时建议加--progress-bar换一个更紧凑的进度显示日志里更好看。5. 把 curl 用进脚本与工程里命令在终端里敲通只是第一步真正发挥价值是在脚本和流水线里。这一层关心的就三件事配置怎么复用、失败怎么判断、输出怎么解析。5.1 .curlrc 与 -K把重复参数收起来每条命令都带一串超时、重试、代理类设置很快就没人愿意手打了。curl 支持配置文件默认读家目录下的.curlrc也可以用-K显式指定。文件里一行一个参数长参数可以省略前面的两条横线# .curlrc connect-timeout 5 max-time 60 retry 2 silent show-error这里有个细节默认读取.curlrc意味着你的脚本行为会随开发者的本机配置漂移。写自动化脚本时我通常用-q作为第一个参数把它关掉然后用-K加载脚本自己维护的配置文件。这样在任何机器上跑出来的行为都是一致的也避免了同事本机某个奇怪的配置把流水线搞挂。配置文件还支持注释和变量式的写法把常用的几个接口地址、公共头抽出来脚本主体会干净很多。不过别把它当成密钥仓库凭据还是走环境变量或者独立的凭据管理方式。5.2 退出码脚本判断成败的正确姿势新手写脚本常见的错误是解析响应内容来判断成功与否这既脆弱又慢。curl 本身就给了退出码$?拿到就是结果。常用的几个0 表示一切正常6 是域名解析失败7 是连接失败28 是超时35 是 TLS 握手失败52 是服务端没返回任何数据56 是接收数据时连接断开。还有一个特别容易误解的默认情况下 HTTP 404、500 这类响应curl 依然返回 0因为它认为我成功完成了这次传输服务器回了内容。要让非 2xx 变成失败得加-f。而-f会把响应体也丢掉看不到服务端的错误详情。7.76 之后的--fail-with-body解决了这个矛盾状态码非成功时退出码非零但响应体照样输出。这段在排查第三方接口时救过我很多次。curl -sS --fail-with-body --max-time 30 \ -o response.json -w %{http_code} \ https://api.example.com/orders || { echo request failed, exit$? 2 cat response.json 2 exit 1 }另外-sS这个组合值得养成习惯-s关掉进度条-S保留错误信息。只用-s会让真正的报错也消失排查时一片空白非常难受。6. 常见问题与排查实录参数记得再熟实际跑起来还是会遇到各种状况。下面这些是我这几年反复遇到、并且每次都有人问的。6.1 报错速查表现象/退出码大概率原因首选排查动作6 无法解析主机域名拼错、DNS 不可用用--resolve绕过解析验证服务本身7 连接失败端口不通、服务未监听、被网络策略拦截换--connect-timeout缩短等待确认端口28 超时连接慢或服务处理慢拆开看time_connect与time_starttransfer35 TLS 握手失败证书链不全、协议版本不匹配-v看协商细节--tlsv1.2等显式指定版本52 空响应服务端直接断开或返回了 0 字节检查服务端日志与网关策略56 接收失败传输中途连接被重置加--retry检查中间设备是否有空闲超时返回 301/302 但拿不到内容没跟随重定向加-L并用%{num_redirects}确认跳转次数中文乱码编码不一致检查Content-Type里的 charset必要时用原始字节日志比对卡住不动无输出Expect: 100-continue未被正确处理-H Expect:去掉该头上传大文件失败中间层有请求体大小限制与运维确认限制考虑分片上传6.2 几个印象深刻的坑第一个是重定向把方法改掉了。有个接口用 POST 提交服务端配了 301 跳转结果-L之后变成了 GET服务端返回 405日志里看着像是方法不对实际上是 curl 遵循规范的结果。加--post301解决。这类问题不知道规范的人会怀疑人生很久。第二个是-d file吃掉了换行。签名校验接口对请求体字节敏感我把格式化过的 JSON 文件用-d发出去服务端一直说签名不匹配。排查了快两个小时最后用--trace-ascii打印原始字节才发现换行被删掉了。换成--data-binary立刻就好。从那以后凡是涉及签名、哈希、字节级校验的请求体我一律用--data-binary。第三个是-k的滥用。跳过证书校验在测试环境确实方便但很多人把它写进了生产脚本然后某天证书过期了、被中间设备替换了脚本照跑不误问题被完全掩盖。我的做法是只在临时排障时用-k并且加注释标明原因和有效期脚本里的默认值永远是严格校验。第四个是并发下载把目标服务打挂。-Z很爽但没注意并发的 URL 都指向同一台机器几十个连接同时建立触发了对端的限流或者直接把小机器压满。后来给批量任务加了并发上限和启动间隔稳定得多。这个道理跟写爬虫一样控制节奏比追求速度重要。还有一个小技巧分享一下把常用的请求存成一个纯文本的集合文件用-K加载再配合 shell 的循环做批量验证。这比每次重新拼命令行高效得多也比维护一套图形化工程的导出文件轻量。我现在的做法是每个服务一个.curlrc片段文件里面放公共头、超时和重试参数具体请求写在脚本里引用它接口地址一旦变更只改一处。最后一句个人体会curl 的参数多到没人能背全我到现在也经常--help加 grep 找参数。真正需要记住的不是参数列表而是那套模型——请求由方法、头部、主体构成出问题就往这三块和连接过程上找。模型稳了剩下的事情查文档就行。
阅读完成 · 觉得有帮助?