一次 Postman 报错 Unexpected End of File 的排查复盘
最近遇到一个挺绕的接口问题:Postman 请求状态码明明是 200,但就是没有响应数据,同时还会报一个看起来很奇怪的错误:
|
|
一开始看到这个报错,很容易怀疑是服务端没启动、返回体格式不对,甚至是接口代码本身有异常。但这次真正的问题,不在业务逻辑,而是在“测试环境请求链路 + 压缩编码 + 代理转发”这几个环节叠在了一起。
这篇文章就把这次排查过程完整梳理一下,重点讲清楚这几个问题:
- 为什么
curl能正常返回,而 Postman 不行 - 为什么有些接口正常,只有部分接口出问题
- 为什么最终定位到的是
content-encoding: br - 测试环境里的反向代理到底起了什么影响
- 最后是怎么处理掉的
1. 现象是什么:状态码 200,但是没有响应数据
最开始碰到的问题很怪:
- Postman 请求接口返回
200 - 但是响应体没有正常展示出来
- 同时报错:
Error: unexpected end of file
这类问题最麻烦的地方在于,它不是典型的 4xx / 5xx,也不是服务直接挂掉,而是“看起来成功了,但实际结果不完整”。
如果只盯着状态码,很容易误判成:
- 服务端逻辑已经执行完了
- 只是 Postman 显示有点问题
- 或者接口偶发不稳定
但实际上,unexpected end of file 这种报错,往往意味着一件事:
响应体在传输或解压过程中,被提前截断了,客户端没有拿到一份完整的数据。
2. 第一步排查:先用 curl 验证是不是服务端问题
遇到这种问题,我第一反应不是改服务端代码,而是先做一件最简单但很关键的事:
|
|
结果很重要:
curl请求是正常的- 响应数据能完整返回
这一步基本可以先排除两类问题:
- 接口本身不是完全不可用
- 服务端并没有普遍性地返回坏数据
也就是说,这时候问题已经开始收敛了:
不是“接口一定有问题”,而是“某个客户端或者某条请求链路在处理这个响应时出了问题”。
这也是这次排查里最关键的转折点。
因为如果 curl 也失败,那排查方向应该优先放在:
- 服务端程序
- 网关
- 上游接口
- 数据库或超时
但既然 curl 成功,就说明我们更应该去对比“成功请求”和“失败请求”之间到底差在哪。
3. 第二步排查:为什么有些接口正常,有些接口不正常
继续看现象,会发现它不是“所有接口都挂了”,而是:
- 有些接口在 Postman 里可以正常请求
- 只有一部分接口会出现
unexpected end of file
这说明网络、域名、基本服务能力都不是全局异常,否则应该全部接口一起出问题。
所以这时候最合理的思路不是继续猜,而是做对比。
我这里重点对比了两类接口:
- 正常接口
- 异常接口
排查重点放在响应头上,尤其是下面几个字段:
Content-TypeContent-LengthTransfer-EncodingContent-Encoding
结果很快就看到了一个非常关键的差异。
4. 关键线索:失败接口响应头里有 content-encoding: br
异常接口返回的响应头里,出现了下面这段信息:
|
|
其中最关键的是:
|
|
这里的 br 指的是 Brotli 压缩。
也就是说,这个接口实际返回的不是裸 JSON,而是:
- 服务端生成 JSON
- 中间链路对响应做了 Brotli 压缩
- 客户端再去解压和解析
而 unexpected end of file 恰好非常像一种典型现象:
- 压缩流没有被完整接收
- 或者客户端在解压时拿到的是一段不完整的 Brotli 内容
- 最终解压失败,报 EOF 类错误
到了这里,问题已经从“业务接口异常”收缩成了:
某些响应在 Brotli 压缩链路下,被 Postman 这一侧处理失败了。
5. 第三步排查:为什么 curl 能成功,Postman 却失败
这里就出现了一个很有价值的对照:
curl能正常拿到响应- Postman 不行
这说明“响应不是绝对坏的”,而是“不同客户端对这份响应的处理结果不一样”。
进一步想,就有几个高概率方向:
curl和 Postman 发出的请求头不同- 两者对压缩编码的支持和处理方式不同
- 某个代理或中间层会根据请求头返回不同编码方式
于是接下来做了一个很直接的验证:在请求头里主动限制压缩算法,不让服务端返回 Brotli。
例如:
|
|
或者更激进一点:
|
|
结果很明确:
- 当禁止
br之后,请求恢复正常 - 问题立刻消失
这一步其实已经足够说明:
真正触发问题的,不是接口业务逻辑本身,而是 Brotli 响应在当前测试链路中的兼容性问题。
6. 第四步排查:为什么测试环境有问题,生产环境没问题
如果问题只停留在“Postman 在当前 Brotli 响应下处理失败”,其实解释还不完整。
因为还有一个事实:
- 测试环境有问题
- 生产环境没有问题
继续往环境差异里看,发现一个非常关键的区别:
- 测试环境机器上多了一层 Nginx
- 生产环境没有这一层 Nginx 反向代理
这一下,排查方向就更明确了。
6.1. 当时的链路大概是什么样的
这次的请求链路可以近似理解成这样:
flowchart LR
A[Postman / curl] --> B[域名入口]
B --> C[Cloudflare]
C --> D[Nginx 反向代理]
D --> E[后端服务]
flowchart LR
A[Postman / curl] --> B[域名入口]
B --> C[Cloudflare]
C --> D[Nginx 反向代理]
D --> E[后端服务]
flowchart LR
A[Postman / curl] --> B[域名入口]
B --> C[Cloudflare]
C --> D[Nginx 反向代理]
D --> E[后端服务]flowchart LR
A[Postman / curl] --> B[域名入口]
B --> C[Cloudflare]
C --> D[Nginx 反向代理]
D --> E[后端服务]
已知条件有几个:
- 域名入口支持 HTTP/2
- 测试环境前面多了一层 Nginx
- Nginx 代理到上游时,常见配置仍然是 HTTP/1.1
- 异常接口返回体较大,并触发了 Brotli 压缩
把这些条件拼起来,就能看出问题大概率发生在哪:
测试环境新增的 Nginx 反向代理,改变了请求和响应在链路中的传输方式,让
br响应在这一条链上更容易出现兼容性问题。
6.2. 这里要注意一个容易说偏的点
这次排查里,最容易被一句话带偏的是:
“浏览器 / Postman 不支持 Brotli。”
这个说法并不严谨。
更准确地说应该是:
- 浏览器本身通常是支持 Brotli 的
curl在较新版本和正确编译参数下,也可能支持 Brotli- 真正出问题的是“当前测试环境这一整条链路下,Postman 对返回内容的处理失败了”
所以这次的根因不是“Brotli 这个技术本身有问题”,而是:
Brotli 响应叠加测试环境中的 Nginx 代理链路后,导致当前客户端请求出现了解压 / 接收不完整的问题。
7. 最终定位:问题不在接口代码,而在测试环境的代理链路
到这里,其实已经可以把问题定性了。
7.1. 现象层
- Postman 状态码是
200 - 但没有正常拿到响应体
- 报
unexpected end of file
7.2. 验证层
curl正常- 限制
Accept-Encoding后恢复正常 - 只有部分接口出问题,且这些接口响应更大
- 异常接口响应头里存在
content-encoding: br
7.3. 环境层
- 测试环境有 Nginx
- 生产环境没有 Nginx
- 问题只在测试环境暴露
所以这次更准确的结论应该写成:
测试环境中的 Nginx 反向代理链路,叠加 Brotli 压缩响应,导致部分大响应接口在 Postman 中出现接收或解压异常,最终表现为状态码 200 但没有响应数据,并报
unexpected end of file。
8. 最后是怎么处理的
既然已经确认问题和 Brotli 有关,那处理方式就很直接了:
8.1. 在 Nginx 代理层限制上游压缩算法
在测试环境的 Nginx 配置里,我在 location / 中增加了下面几行。为了避免暴露真实环境信息,下面用的是脱敏后的示例配置:
|
|
这里最关键的是这一行:
|
|
它的作用是:
- Nginx 在向后端发请求时,不再声明自己接受
br - 上游就不会优先返回 Brotli 压缩内容
- 响应改成更稳定的
gzip/deflate - Postman 这边也就恢复正常了
8.2. 修改后结果
加完配置以后,问题就解决了:
- Postman 能正常拿到响应体
- 不再出现
unexpected end of file - 状态码和响应内容都恢复正常
这也进一步证明,前面的定位方向是对的。
9. 这次问题到底该怎么总结
如果用一句话概括这次问题,我会这样写:
这不是一次“接口业务报错”,而是一次“测试环境代理链路与 Brotli 压缩响应产生兼容性问题”的排查。
如果拆开来说,可以归纳成下面这条链:
- 域名入口支持 HTTP/2
- 测试服务器前面多了一层 Nginx
- Nginx 反向代理改变了请求链路
- 某些接口响应较大,命中了 Brotli 压缩
- 返回头里出现
content-encoding: br - Postman 在当前链路下处理这类响应失败
- 最终表现为
200但无响应体,并报unexpected end of file - 通过在 Nginx 层限制
Accept-Encoding,改用gzip/deflate后恢复正常
10. 这次排查里最有价值的几个经验
最后把这次最有价值的经验单独记一下。
10.1. 先用 curl 做对照测试
curl 能不能成功,往往能帮你非常快地把问题分成两类:
- 服务端根本不通
- 某个客户端 / 某条链路有问题
这一步特别省时间。
10.2. 遇到 200 但没数据,不要只盯状态码
状态码 200 只能说明“请求到达并被处理过”,不代表“响应体一定被完整、正确地接收和解析了”。
10.3. 响应头经常比响应体更重要
尤其是这些字段:
Content-EncodingContent-LengthTransfer-EncodingContent-Type
很多问题,答案其实就藏在头里。
10.4. 测试环境和生产环境的中间件差异,足够制造一类独立问题
这次生产没问题、测试有问题,本质就是因为测试环境多了一层 Nginx。
很多时候不是代码不一致,而是基础设施路径不一致。
11. 最后的结论
这次问题最终不是数据库、不是业务代码、不是接口返回值结构,也不是简单的 Postman 配置错误,而是:
测试环境中 Nginx 反向代理引入的链路差异,叠加部分大响应接口使用 Brotli 压缩,导致 Postman 在当前链路下无法正常处理响应体,最终报出
unexpected end of file。
处理方式也不复杂:
- 不去改业务接口
- 不先怀疑服务端逻辑
- 直接从代理层下手
- 在 Nginx 中限制上游
Accept-Encoding,避免返回br
问题就解决了。
如果以后你也碰到下面这种情况:
- Postman 返回
200 - 但没有响应数据
curl正常- 只有部分接口异常
- 响应头里有
content-encoding: br
那可以优先从 Brotli 压缩 + 代理链路兼容性 这个方向查起,命中率会很高。