跳转到内容

第 18 章 · HTTP 调试

一句话:在终端里亲手发 HTTP 请求、看清请求和响应每一行的工具(curl、HTTPie、Bruno),加上把本机端口临时开给公网访问的内网穿透工具(cloudflared、ngrok)。 用它还是用别的:一次性的请求、脚本、排错用 curl,哪台机器上都有;参数多、要反复手调的接口用 HTTPie;一组要保存下来、和同事共用的接口请求用 Bruno 存进仓库;网页自己发出的请求先在浏览器开发者工具里看(第 19 章);临时让别人打开本机页面用 cloudflared 快速隧道,要固定地址用 ngrok。 核对版本:curl 8.21(Windows 自带)、HTTPie 3.2、Bruno 4.2、cloudflared 2026.10、ngrok 3,2026-10

curl 一般不用装:Windows 10 起系统自带 curl.exe,macOS 也自带。Linux 上没有时:

终端窗口
sudo apt install curl

想要比系统自带更新的版本,Windows 用 winget install cURL.cURL,macOS 用 brew install curl。查版本,--json 要 7.82 以上:

终端窗口
curl.exe --version
终端窗口
curl --version

PowerShell 里本章一律写 curl.exe,原因见第 4 章 4.6;bash 里把 curl.exe 换成 curl 即可。

HTTPie 是 Python 写的命令行程序,命令名是 http。Windows 用 uv 装,没有 uv 就 pip install httpie(winget 里的 HTTPie.HTTPie 是它的桌面版,不是命令行):

终端窗口
uv tool install httpie
终端窗口
brew install httpie # macOS
sudo apt install httpie # Ubuntu、Debian

查版本:http --version。

Bruno 是保存接口请求的桌面程序。Postman 功能相近,要登录账号,集合同步到它的云端,本章不展开:

终端窗口
winget install Bruno.Bruno
终端窗口
brew install bruno # macOS
sudo snap install bruno # Linux

cloudflared 做快速隧道不需要账号:

终端窗口
winget install Cloudflare.cloudflared
终端窗口
# macOS
brew install cloudflared
# Ubuntu、Debian:从 GitHub 下载 deb 包再安装
curl -LO https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb && sudo dpkg -i cloudflared-linux-amd64.deb

查版本:cloudflared --version。ngrok 要先在官网注册账号拿到令牌(authtoken),Windows 从 ngrok 官网下载页下载,macOS 用 brew install ngrok,Linux 按官网下载页加 apt 源;查版本:ngrok version。

  • 请求(request):客户端发出的一段文本,四部分:方法(GET 取、POST 新建、PUT 整体替换、PATCH 改一部分、DELETE 删)、URL、请求头(header,一行一个 名字: 值)、正文(body,GET 一般没有)。浏览器、代码里的 HTTP 库、curl 发的都是同一种东西,所以“浏览器能通、代码不通”一定是这四部分里有一处不一样。
  • 响应(response):服务器回的一段文本:状态码(status code)、响应头、正文。
  • 状态码:三位数,第一位是分组,看第一位就知道该查客户端还是服务器,见下表。401 是没认出你是谁,403 是认出来了但没权限,处理方向不同。
  • 内容类型(Content-Type):请求头里说明正文是什么格式。application/json 是 JSON;application/x-www-form-urlencoded 是 name=a&age=3 这种表单;multipart/form-data 用来上传文件。后端按这个头决定怎么解析正文,头和正文对不上,后端就当成没收到参数。
  • 认证头(Authorization):带身份凭据的请求头。Bearer <令牌> 放登录后拿到的令牌(token),大部分 Web API 用它;Basic 后面是“用户名:密码”的 Base64 编码,只是编码不是加密,只该在 HTTPS 上用。
  • 内网穿透(tunneling):你的电脑在路由器和防火墙后面,外面的人连不进来。隧道客户端从本机主动连到服务商的边缘服务器,服务商给你一个公网 HTTPS 地址,访问这个地址的请求顺着那条连接送回本机端口。本机不用开防火墙、不用动路由器,客户端一停,地址就失效。

curl -v 把一次请求的整个来回都打印出来,每行开头的符号告诉你这一行是什么:

curl -v 的输出:星号行是连接和 TLS 过程,大于号行是发出去的请求,小于号行是收到的响应

常见状态码按分组:

分组 意思 最常见的三个
2xx 成功 200 成功;201 已创建,POST 新建后返回;204 成功但没有正文
3xx 跳转或用缓存 301 永久跳转;302 临时跳转;304 没变,用浏览器缓存
4xx 请求有问题,改客户端 400 请求格式不对,常见于正文不是合法 JSON;401 没带或带错身份凭据;404 路径不存在
5xx 服务器出错,查服务端日志 500 应用代码抛了异常;502 代理连不上后端;504 后端太久没回

502、504 和上传时的 413 在 Nginx 背后怎么查,见第 16 章 16.5、16.6。

下面的 curl 命令按 bash 写,PowerShell 里把 curl 换成 curl.exe,其余照抄(PowerShell 7 里单引号包 JSON 能原样传过去,5.1 不行,见 18.6)。URL 里有 & 时整个 URL 加引号。

任务 命令 说明
curl:看请求与响应
取正文 curl <url> 只打印响应正文
带上响应头 curl -i <url> 状态行和响应头在正文前面
只要响应头 curl -I <url> 发的是 HEAD 请求,个别接口不支持,会返回 405
看完整来回 curl -v <url> 输出在标准错误里,要过滤先加 2>&1
跟随跳转 curl -L <url> 不加时遇到 301、302 就停下,正文是空的或只有一段跳转提示
存成文件 curl -o <文件> <url> 下载用;-O 用 URL 里的文件名
只看状态码和耗时 curl -s -o /dev/null -w "%{http_code} %{time_total}s\n" <url> 输出如 200 0.012s;Windows 的 curl.exe 也认 /dev/null
拆开耗时 curl -s -o /dev/null -w "dns %{time_namelookup} connect %{time_connect} total %{time_total}\n" <url> 看慢在域名解析、建连还是等后端
curl:发数据
发 JSON curl --json '{"name":"a"}' <url> 自动 POST,并加 Content-Type 和 Accept: application/json
从文件发 JSON curl --json @body.json <url> 正文长时用;PowerShell 里写 '@body.json',带引号
发表单 curl -d "name=a&age=3" <url> 自动 POST,类型是 application/x-www-form-urlencoded
上传文件 curl -F "file=@photo.png" <url> multipart/form-data,file 是后端约定的字段名
指定方法 curl -X DELETE <url> 带 -d、--json 时已经是 POST,不用再写 -X POST;要 PUT 写 -X PUT --json …
加请求头 curl -H "Accept: application/json" <url> 可以写多个 -H
带 Cookie curl -b "session=<值>" <url> 模拟已登录的浏览器
curl:认证与连接
Bearer 令牌 curl -H "Authorization: Bearer <令牌>" <url> 令牌会留在命令历史里,用完清掉
Basic 认证 curl -u <用户>:<密码> <url> 只写 -u <用户> 会提示输入密码,不进历史
跳过证书校验 curl -k https://<地址> 只在测试时用:等于不验证对方是谁
信任自签证书 curl --cacert <根证书.pem> https://<地址> 比 -k 稳妥,只多信这一个证书
域名临时指到某个 IP curl --resolve example.com:443:<IP> https://example.com 不改 hosts,直接测新服务器或某一台后端
HTTPie
GET http GET <url> 方法可省;本机可简写成 http :3000/users
POST JSON http POST <url> name=a age:=3 默认就是 JSON;= 是字符串,:= 是数字、布尔等原样 JSON
发表单 http -f POST <url> name=a 类型换成表单
加请求头 http <url> "Authorization:Bearer <令牌>" 名字:值 是请求头,名字==值 是 URL 参数
只看响应头 http --headers <url> 不打印正文
连请求一起看 http -v <url> 先打印发出去的请求,再打印响应
内网穿透
快速隧道 cloudflared tunnel --url http://localhost:3000 不要账号,打印一个随机的 trycloudflare.com 地址
保存 ngrok 令牌 ngrok config add-authtoken <令牌> 每台机器做一次,写进 ngrok 配置文件
ngrok 隧道 ngrok http 3000 免费账号分到一个固定的开发域名

接口在浏览器里正常,代码里调就报错

Section titled “接口在浏览器里正常,代码里调就报错”

后端在 localhost:3000,浏览器里新建用户成功,代码里调同一个接口返回 400。下面按 bash 写,Windows 上在 Git Bash 里做,因为浏览器复制出来的 cURL 命令也是 bash 写法,PowerShell 不认其中的 \ 续行:

  1. 确认服务在、路径对,先看状态码:curl -i http://localhost:3000/api/users。
  2. 按代码的写法发一次,只看发出去的请求行和请求头:curl -sv -o /dev/null -d '{"name":"a"}' http://localhost:3000/api/users 2>&1 | grep '^>'。
  3. 输出里是 Content-Type: application/x-www-form-urlencoded:代码没设 JSON 类型,后端按表单解析,读不到字段。换成 JSON 再发,成功就说明问题在这个头:curl -i --json '{"name":"a"}' http://localhost:3000/api/users。
  4. 类型没问题时,在浏览器开发者工具里把成功的那条请求“复制为 cURL”(第 19 章),粘进终端运行,确认能复现成功。
  5. 在复制来的命令末尾加上 -sv -o /dev/null 2>&1 | grep '^>',和第 2 步的输出逐行对比 Content-Type、Authorization、Cookie。
  6. 浏览器那边多了 Cookie 或 Authorization,就是代码没带登录凭据;带上令牌再试:curl -i -H "Authorization: Bearer <令牌>" --json '{"name":"a"}' http://localhost:3000/api/users。
  7. 请求对了但很慢,看时间花在建连还是等后端:curl -s -o /dev/null -w "connect %{time_connect} total %{time_total}\n" http://localhost:3000/api/users。

把本机正在开发的网页临时给别人看

Section titled “把本机正在开发的网页临时给别人看”

下面按 PowerShell 写,bash 里把 curl.exe 换成 curl。

  1. 起本机服务,这里用 Python 自带的文件服务代替你的开发服务器:python -m http.server 3000。
  2. 另开一个终端,确认本机能访问:curl.exe -I http://localhost:3000。
  3. 起快速隧道,几秒后输出里出现 https://<随机词>.trycloudflare.com:cloudflared tunnel --url http://localhost:3000。
  4. 从本机走公网地址访问一次,返回 200,第 1 步的终端里多一行访问日志:curl.exe -I https://<随机词>.trycloudflare.com。
  5. 把地址发给同事。看完在隧道终端按 Ctrl+C,再访问就打不开了:curl.exe -I https://<随机词>.trycloudflare.com。
  6. 下次启动是另一个随机地址。要固定地址就用 ngrok,先保存令牌:ngrok config add-authtoken <令牌>。
  7. 起隧道,终端里的 Forwarding 一行就是公网地址:ngrok http 3000。

内网穿透:本机客户端先主动连到 Cloudflare 或 ngrok 的边缘服务器,外部访问者的请求到公网地址后,沿这条连接送回本机,再转给 localhost

图里箭头 ⓪ 是关键:连接是本机连出去的,所以不用在路由器上做端口映射。反过来,拿到地址的任何人都能访问这个端口,别对着有真实数据的服务开。

用 Vite 开发服务器时,外面打开页面看到 Blocked request. This host (…) is not allowed.,在 vite.config.js 的 server.allowedHosts 里加 .trycloudflare.com。

在 Bruno 里存一组接口请求,提交到仓库

Section titled “在 Bruno 里存一组接口请求,提交到仓库”

Bruno 的集合(collection)就是一个目录,每个请求一个纯文本文件(.bru,3.0 起也可选 YAML),能像代码一样 diff、评审、合并:

  1. 在 Bruno 里点 Create Collection,名字填 shop-api,位置选项目仓库的根目录。
  2. 新建请求,URL 写 {{baseUrl}}/api/users,按 Ctrl+S 保存。
  3. 在 Environments 里新建 local,变量 baseUrl 填 http://localhost:3000;令牌变量勾上 Secret,值只存在本机,不写进文件。
  4. 回到终端看 Bruno 生成了哪些文件:git status。
  5. 提交整个集合目录:git add shop-api。
  6. 写提交说明:git commit -m "添加用户接口的 Bruno 集合"。
  7. 同事 git pull 后,在 Bruno 里点 Open Collection 选这个目录,填上自己的令牌就能发。
  8. 想在命令行或 CI 里跑整个集合,装 Bruno 的命令行:npm install -g @usebruno/cli。
  9. 进入集合目录,用 local 环境依次发出全部请求:bru run --env local。
配置项 Linux macOS Windows
curl 默认参数 ~/.curlrc ~/.curlrc %USERPROFILE%\.curlrc(也认 _curlrc)
HTTPie 配置 ~/.config/httpie/config.json ~/.config/httpie/config.json %APPDATA%\httpie\config.json
Bruno 集合 集合目录里的 bruno.json(YAML 格式为 opencollection.yml)和请求文件 同左 同左
Bruno 环境 集合目录下 environments/<环境名>.bru 或 .yml 同左 同左
cloudflared ~/.cloudflared/,命名隧道的凭据和 config.yml 在这里 同左 %USERPROFILE%\.cloudflared\
ngrok ~/.config/ngrok/ngrok.yml ~/Library/Application Support/ngrok/ngrok.yml %LOCALAPPDATA%\ngrok\ngrok.yml

.curlrc 对每一条 curl 命令生效,包括别人写的脚本,所以别放 -H、-d 这类改变请求内容的选项;临时不想读它,在命令最前面加 -q。HTTPie 的 config.json 用 default_options 列表写默认参数;在脚本、定时任务里调用 http 时把 --ignore-stdin 放进去,见 18.6。快速隧道不需要配置文件,固定域名的命名隧道才在 .cloudflared 里放 config.yml;ngrok config check 打印 ngrok 实际读的是哪个文件。

一个最小的 .curlrc,一行一个选项,长选项去掉前面的 --:

# 自动跟随 301、302 跳转,等于每条命令都加了 -L
location
# 最多跟随 5 次跳转,防止两个地址来回跳
max-redirs = 5
# 10 秒连不上就放弃,不干等
connect-timeout = 10
# 加了 -s 静默时,出错照样打印原因
show-error
  • Windows PowerShell 5.1 里 curl -I <url> 报缺少参数 Uri,或停下来让你输入 Uri。 原因:5.1 的 curl 是 Invoke-WebRequest 的别名(第 4 章 4.6)。处理:一律写 curl.exe,或改用 PowerShell 7。
  • PowerShell 里发 JSON,后端收到 {name:a};或 URL 带 & 时报“术语 ‘b=2’ 不会被识别为 cmdlet…”。 原因:5.1 吃掉参数里的双引号(同第 5 章 5.6);& 是后台运行符,URL 在此截断。处理:JSON 在 7 里用单引号 '{"name":"a"}' 或 --json '@body.json';URL 整个加引号。
  • curl: (60) SSL certificate problem: self-signed certificate,Windows 上是 schannel: SEC_E_UNTRUSTED_ROOT。 原因:对方证书是自签的或由公司内部 CA 签发,curl 不信任。处理:--cacert <根证书.pem> 指定根证书;本机测试可临时加 -k,不要写进脚本。
  • 报 curl: (7) Failed to connect to localhost …,连不上服务器。 原因:服务没起、端口写错,或服务只监听在另一个地址(如容器里)。处理:先看服务的终端日志确认端口,再 curl -v 看它实际连的是哪个 IP 和端口。
  • 用 -d 发 JSON,后端返回 400 或字段全是空的。 原因:-d 默认带 Content-Type: application/x-www-form-urlencoded,后端不按 JSON 解析。处理:改用 --json,旧版 curl 加 -H "Content-Type: application/json"。
  • curl -i 只看到 301 或 302 和一个 Location,正文是空的或只有一段跳转提示。 原因:curl 默认不跟随跳转。处理:加 -L;要看跳了几次、跳去哪,用 -L -v。
  • 隧道地址昨天还能用,今天打不开。 原因:快速隧道的地址只在 cloudflared 运行时有效,每次启动都换一个。处理:重新运行 cloudflared tunnel --url http://localhost:3000,把新地址发给对方;要固定地址用 ngrok。
  • 脚本或定时任务里的 http 命令一直卡住不返回。 原因:标准输入不是终端时,HTTPie 以为正文要从标准输入读,一直在等。处理:加 --ignore-stdin,或写进 HTTPie 配置的 default_options。
  • curl 手册:curl 全部选项的说明,查 –json、-w 能打印的变量、.curlrc 的查找顺序。
  • Everything curl:curl 作者写的完整教程,从发 GET 讲到认证、Cookie、代理和 TLS 证书。
  • HTTPie 文档:请求项写法(= 和 := 的区别)、表单和上传、会话、配置文件和 –ignore-stdin。
  • Bruno 文档:集合与请求文件格式、环境变量和 Secret 变量、Git 协作、命令行 bru run。
  • Cloudflare 快速隧道:cloudflared tunnel –url 的用法、只许指定邮箱访问的写法和快速隧道的限制。
  • HTTP 响应状态码(MDN):每个状态码的含义和典型场景,遇到不认识的状态码来这里查。