从 TCP 到 JSON:一次 FastAPI + LLM 生产环境 “Unexpected end of JSON input” 的底层剖析

🔥 从 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


相关推荐
cheems95272 小时前
【网络原理】网络编程基础:TCP Echo Server 的底层逻辑与实现
网络·tcp/ip·php
享誉霸王15 小时前
15、告别混乱!Vue3复杂项目的规范搭建与基础库封装实战
前端·javascript·vue.js·前端框架·json·firefox·html5
小灰灰搞电子18 小时前
ESP32 使用ESP-IDF实现Modbus TCP从机通信源码分享
modbustcp·tcp/ip·esp32
百锦再19 小时前
Java的TCP和UDP实现详解
java·spring boot·tcp/ip·struts·spring cloud·udp·kafka
SmartBrain1 天前
技术洞察:SpringAI与LangGraph选型对比
人工智能·spring boot·架构·langchain·aigc·fastapi
那个松鼠很眼熟w1 天前
python fastapi 快速创建web应用
python·fastapi
cur1es1 天前
【TCP 协议的相关特性】
java·网络·网络协议·tcp/ip·tcp·滑动窗口·连接管理
今心上1 天前
关于json的理解测试!!
开发语言·json
tianyagukechat2 天前
rockylinux9.5 配置IP
java·网络·tcp/ip