从 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


相关推荐
想成为优秀工程师的爸爸6 小时前
第十九篇技术笔记:UDP——相思传得快,飞鸽传书在
笔记·网络协议·tcp/ip·udp·信息与通信
0xR3lativ1ty9 小时前
关闭公网IP的两种方式
网络协议·tcp/ip·php
2401_8734794012 小时前
固件升级如何按地区分批推送?IP地址查询定位决定升级策略
网络协议·tcp/ip·php
Jiangxl~13 小时前
IP数据云如何为不同行业提供精准IP查询与风险防控解决方案?
网络·网络协议·tcp/ip·算法·ai·ip·安全架构
曲幽16 小时前
FastAPI配置管理避坑指南:从硬编码到 .env 与 pydantic_settings 类,连路由用法都给你捋清楚
python·fastapi·web·settings·config·pydantic·.env·dotenv·.env.prod
忡黑梨16 小时前
eNSP_ACL原理及应用
运维·服务器·网络·tcp/ip·github·负载均衡
世界尽头与你18 小时前
FastAPI Swagger Api 接口未授权访问漏洞
安全·网络安全·渗透测试·fastapi
TechWayfarer18 小时前
离线IP数据库内网部署:场景选型与热更新落地实践
网络·数据库·python·网络协议·tcp/ip
科技牛牛18 小时前
离线IP数据库推荐:风控合规场景怎么选
网络·数据库·tcp/ip·离线ip数据库·.数据安全
IPDEEP全球代理19 小时前
动态IP能防止账号关联吗?
网络·网络协议·tcp/ip