从 TCP 到 JSON一次 FastAPI LLM 生产环境 “Unexpected end of JSON input” 的底层剖析关键词TCP 连接断开、HTTP 分块传输、Nginx 超时机制、FastAPI、LLM 长耗时任务、生产环境架构一、问题现象线上部署 FastAPI AsyncOpenAI 接口后前端报错Failed to execute json on Response: Unexpected end of JSON input浏览器控制台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 配置proxy_read_timeout 60s;含义如果后端在 60 秒内没有返回完整响应Nginx 会主动关闭 TCP 连接五、长耗时 LLM 调用触发条件LLM 生成耗时≈ 100 秒而 Nginx 超时60 秒发生的实际网络行为时间线TCP 级别t0 Browser 发起请求 t1 请求到达 Nginx t2 Nginx 转发给 FastAPI t3 FastAPI 调用 LLM t60 Nginx 等待超时 t60 Nginx 发送 FIN/RST t100 FastAPI 准备返回 JSON t100 连接已断开六、TCP 层发生了什么当 Nginx 超时发送 TCP FIN 或 RST 包这意味着TCP 连接被关闭FastAPI 仍然在计算Browser 收到半截 HTTP 响应七、为什么会出现 JSON 解析错误假设服务器本来要返回{status:success,html:!DOCTYPE html...}但连接在传输过程中被中断浏览器实际收到{status:success,ht于是执行awaitresponse.json()解析到一半 → JSON 结构不完整 → 抛出Unexpected end of JSON input八、为什么服务器日志看不出问题因为FastAPI 没报错LLM 调用成功文件写入成功错误发生在TCP 连接已经被 Nginx 提前关闭属于网络层错误而不是应用层错误九、如何验证是 TCP 层问题可以使用tcpdump-iany port8000或者ss-tnstate established你会看到连接在 60 秒时被关闭后端仍然在运行十、HTTP 与 TCP 的分层问题很多开发者会混淆JSON 错误实际上是TCP 连接异常这属于典型的上层协议误报底层错误十一、为什么不能简单改超时时间你可以修改proxy_read_timeout 300s;但会产生问题长时间占用 worker高并发时资源耗尽连接池阻塞服务器扩展性差本质问题仍然存在HTTP 请求生命周期过长十二、正确架构脱离 HTTP 生命周期改造为提交任务 → 秒回 job_id 后台执行 → 更新状态 前端轮询 → 查询结果网络层变化改造后的 TCP 行为Browser ↓ POST (50ms) Nginx ↓ FastAPI → create_task ↓ 立即返回 JSONjob_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