🔥 从 TCP 到 JSON:一次 FastAPI + LLM 生产环境 "Unexpected end of JSON input" 的底层剖析
关键词:TCP 连接断开、HTTP 分块传输、Nginx 超时机制、FastAPI、LLM 长耗时任务、生产环境架构
一、问题现象
线上部署 FastAPI + AsyncOpenAI 接口后,前端报错:
text
Failed to execute 'json' on 'Response': Unexpected end of JSON input
浏览器控制台:
text
SyntaxError: Unexpected end of JSON input
但:
- 后端日志显示 LLM 请求成功
- HTML 文件已成功生成
- 本地 curl 测试完全正常
这意味着:
JSON 本身没有问题
服务器代码没有报错
但客户端收到了不完整的响应
二、问题的真正入口:TCP 连接被强制关闭
我们从最底层开始分析。
1️⃣ HTTP 本质上是 TCP 之上的协议
一次请求的完整链路:
Browser
↓
TCP 三次握手
↓
HTTP 请求发送
↓
Server 处理
↓
HTTP 响应返回
↓
TCP 连接关闭
只要 TCP 连接被中断:
- HTTP 响应体会被截断
- 客户端会收到不完整数据
三、线上环境与本地环境的差异
本地结构
Browser
↓
Uvicorn (FastAPI)
↓
LLM
线上结构
Browser
↓
Nginx
↓
Uvicorn
↓
LLM
这里多了一层:
Nginx 反向代理
四、Nginx 的超时机制(关键)
默认 Nginx 配置:
nginx
proxy_read_timeout 60s;
含义:
如果后端在 60 秒内没有返回完整响应
Nginx 会主动关闭 TCP 连接
五、长耗时 LLM 调用触发条件
LLM 生成耗时:
≈ 100 秒
而 Nginx 超时:
60 秒
发生的实际网络行为:
时间线(TCP 级别)
t=0 Browser 发起请求
t=1 请求到达 Nginx
t=2 Nginx 转发给 FastAPI
t=3 FastAPI 调用 LLM
t=60 Nginx 等待超时
t=60 Nginx 发送 FIN/RST
t=100 FastAPI 准备返回 JSON
t=100 连接已断开
六、TCP 层发生了什么?
当 Nginx 超时:
发送 TCP FIN 或 RST 包
这意味着:
- TCP 连接被关闭
- FastAPI 仍然在计算
- Browser 收到半截 HTTP 响应
七、为什么会出现 JSON 解析错误?
假设服务器本来要返回:
json
{
"status": "success",
"html": "<!DOCTYPE html>..."
}
但连接在传输过程中被中断:
浏览器实际收到:
json
{
"status": "success",
"ht
于是执行:
javascript
await response.json()
解析到一半 → JSON 结构不完整 → 抛出:
Unexpected end of JSON input
八、为什么服务器日志看不出问题?
因为:
- FastAPI 没报错
- LLM 调用成功
- 文件写入成功
错误发生在:
TCP 连接已经被 Nginx 提前关闭
属于:
网络层错误,而不是应用层错误
九、如何验证是 TCP 层问题?
可以使用:
bash
tcpdump -i any port 8000
或者:
bash
ss -tn state established
你会看到:
- 连接在 60 秒时被关闭
- 后端仍然在运行
十、HTTP 与 TCP 的分层问题
很多开发者会混淆:
JSON 错误
实际上是:
TCP 连接异常
这属于典型的:
上层协议误报底层错误
十一、为什么不能简单改超时时间?
你可以修改:
nginx
proxy_read_timeout 300s;
但会产生问题:
- 长时间占用 worker
- 高并发时资源耗尽
- 连接池阻塞
- 服务器扩展性差
本质问题仍然存在:
HTTP 请求生命周期过长
十二、正确架构:脱离 HTTP 生命周期
改造为:
提交任务 → 秒回 job_id
后台执行 → 更新状态
前端轮询 → 查询结果
网络层变化:
改造后的 TCP 行为
Browser
↓ POST (50ms)
Nginx
↓
FastAPI → create_task
↓
立即返回 JSON(job_id)
TCP 正常关闭
后台任务独立执行:
FastAPI worker
↓
LLM
前端每 1.5 秒发起新请求:
GET /status/{job_id}
每次都是短连接。
十三、架构对比(TCP 视角)
❌ 同步模式
一个 TCP 连接持续 100 秒
✅ 异步模式
多个短 TCP 连接
每个 < 100ms
十四、底层原理总结
当你看到:
Unexpected end of JSON input
请优先怀疑:
- 代理层超时
- TCP 被中断
- 连接被重置
- 网关断开
而不是:
- JSON 编码
- Unicode 问题
- FastAPI 序列化
十五、深入一点:RST 与 FIN 的区别
- FIN:正常关闭连接
- RST:强制重置连接
Nginx 在超时场景下通常发送 RST。
客户端收到 RST:
- 立即中断读取
- 抛出异常
十六、工程启示
1️⃣ 生产环境必须理解网络层
开发者往往只关注:
Python 代码是否报错
但生产问题往往发生在:
TCP / 代理层 / 负载均衡层
2️⃣ 长耗时任务必须异步
只要:
任务 > 30 秒
就应该:
脱离 HTTP 生命周期
十七、最终架构图
Browser
↓ POST (短连接)
Nginx
↓
FastAPI (create_task)
↓
后台 Worker
↓
LLM