一次 413 Content Too Large 排查:真正的问题可能不在你的后端

一次 413 Content Too Large 排查:真正的问题可能不在你的后端

最近在接一个 AI 视频生成接口时,碰到了一个很典型的问题。

前端发起 POST 请求后,接口没有正常返回业务结果,而是直接报:

text 复制代码
Request Method: POST
Status Code: 413 Content Too Large

第一眼看到 413,很多开发者的反应都是:

文件太大了,把 Nginx 的上传限制调大不就行了?

这个思路不能说错,但只对了一部分。

因为在现在的 Web 系统里,一个请求从浏览器发出去,到真正进入业务代码,中间可能经过本地代理、CDN、WAF、Nginx、API Gateway、Ingress、负载均衡以及应用服务器。

任何一层限制了 Request Body 大小,都可能直接返回 413。

更麻烦的是,在 AI 视频生成、图生视频、多模态模型这些场景里,我们经常会把图片转换成 Base64 塞进 JSON。原本几 MB 的图片经过 Base64 编码之后会进一步膨胀,非常容易撞上网关的请求体限制。

所以这篇文章不只是讲怎么解决一个 413,而是把这类问题完整拆开。


一、413 Content Too Large 到底是什么意思?

HTTP 413 的含义其实很直接:

text 复制代码
413 Content Too Large

简单理解就是:

服务器或者中间代理认为你提交的 HTTP Request Body 太大,因此拒绝继续处理。

例如前端提交:

http 复制代码
POST /api/video/create
Content-Type: application/json

Body 里面可能是:

json 复制代码
{
  "prompt": "生成一段视频",
  "image": "data:image/jpeg;base64,..."
}

如果整个 Request Body 达到了十几 MB,而其中某一层只允许 10MB,那么请求就可能直接得到:

text 复制代码
413 Content Too Large

这里有一个非常重要的概念:

413 不等于你的 Java 后端拒绝了请求。

因为这个请求可能压根没有进入 Java。


二、现代系统里,一个 HTTP 请求到底经过多少层?

我们平时开发时看到的调用关系可能只有:

text 复制代码
前端
 ↓
后端

但线上环境通常没有这么简单。

一个比较常见的请求链路可能是:

text 复制代码
浏览器
 ↓
本机网络代理
 ↓
CDN / WAF
 ↓
负载均衡
 ↓
Nginx
 ↓
API Gateway
 ↓
Kubernetes Ingress
 ↓
Spring Boot
 ↓
业务代码

如果调用的是第三方 AI 服务,链路甚至可能变成:

text 复制代码
自己的前端
 ↓
自己的后端
 ↓
自己的 Nginx
 ↓
第三方 API Gateway
 ↓
第三方负载均衡
 ↓
第三方 AI 服务

问题就在这里。

假设:

text 复制代码
Nginx              最大 100MB
Spring Boot        最大 100MB
API Gateway        最大 10MB

即使前两层全部允许 100MB,只要你的请求达到:

text 复制代码
15MB

到了 API Gateway 还是会被拒绝。

所以排查 413 最重要的不是一上来就改配置,而是回答一个问题:

到底是谁返回的 413?

只要找到这一层,问题基本就解决一半了。


三、第一步:先确认请求到底有多大

遇到 413,我通常不会第一时间登录服务器。

先打开浏览器开发者工具:

text 复制代码
F12
→ Network
→ 找到失败请求

重点查看:

text 复制代码
Request Headers
Request Payload
Content-Length
Content-Type

尤其注意 Content-Length

例如:

text 复制代码
Content-Length: 12582912

换算一下:

text 复制代码
12582912 Byte
≈ 12 MB

如果请求已经达到十几 MB,就要开始检查请求体里面到底装了什么。

对于普通 JSON API:

json 复制代码
{
  "userId": 10001,
  "prompt": "生成一个视频"
}

理论上只有几 KB。

突然变成十几 MB,通常意味着里面塞进去了:

text 复制代码
Base64 图片
Base64 视频
Base64 音频
大文件
超长文本
二进制内容

AI 多模态接口尤其容易出现这个问题。


四、最容易被忽略的坑:Base64 会让文件变大

这是这次问题里非常值得注意的一点。

假设有一张图片:

text 复制代码
原始 JPG:6 MB

开发的时候为了方便,直接:

text 复制代码
图片
 ↓
读取二进制
 ↓
Base64
 ↓
塞进 JSON
 ↓
POST

看起来很方便,但 Base64 并不是无损的"体积转换"。

Base64 通常会带来大约三分之一左右的体积膨胀。

也就是说:

text 复制代码
6 MB JPG
 ↓
Base64
 ↓
大约 8 MB

如果是图生视频接口,需要同时上传:

text 复制代码
首帧:6 MB
尾帧:7 MB

原始文件一共:

text 复制代码
13 MB

转换成 Base64 后,很可能已经接近:

text 复制代码
17 MB+

再加上:

text 复制代码
JSON 结构
Prompt
模型参数
其他素材
请求元数据

整个 HTTP Body 很容易超过十几 MB甚至几十 MB。

这时候如果网关限制是:

text 复制代码
10 MB

那么请求还没有真正进入 AI 服务,就已经被网关挡住了。


五、为什么 AI 视频接口特别容易出现 413?

传统业务接口一般不会传特别大的 Request Body。

比如:

json 复制代码
{
  "username": "test",
  "page": 1,
  "pageSize": 20
}

这种请求可能连 1KB 都不到。

但现在 AI 应用越来越多之后,请求体发生了明显变化。

例如:

文生视频

通常只有:

text 复制代码
Prompt
模型名称
视频长度
分辨率
比例

请求不会特别大。

但图生视频就完全不同了。

可能包含:

text 复制代码
首帧图片
尾帧图片
参考人物图片
参考物体图片
虚拟素材
角色参考图
风格参考图

如果全部采用 Base64:

text 复制代码
JSON Request
├── prompt
├── first_frame_base64
├── last_frame_base64
├── reference_image_1_base64
├── reference_image_2_base64
└── parameters

请求体可能瞬间从:

text 复制代码
5 KB

膨胀到:

text 复制代码
20 MB
50 MB
甚至 100 MB

这也是为什么很多 AI API 更推荐传:

text 复制代码
URL

而不是直接传:

text 复制代码
Base64

六、如果是自己的 Nginx,怎么解决?

如果最终确认 413 是自己的 Nginx 返回的,那么处理就比较简单了。

首先检查当前配置:

bash 复制代码
nginx -T | grep client_max_body_size

如果没有相关配置,可以在 httpserverlocation 中增加限制。

例如:

nginx 复制代码
server {
    client_max_body_size 100M;

    location / {
        proxy_pass http://backend;
    }
}

也可以全局配置:

nginx 复制代码
http {
    client_max_body_size 100M;
}

修改完成后不要直接重启。

先检查:

bash 复制代码
nginx -t

确认配置没有问题后:

bash 复制代码
nginx -s reload

如果 Nginx 跑在 Docker 中,可以执行类似:

bash 复制代码
docker exec nginx nginx -t
docker exec nginx nginx -s reload

生产环境里建议优先 reload,而不是为了修改一个参数直接把整个服务停掉再启动。


七、Spring Boot 也可能有限制

如果请求确实已经进入 Spring Boot,但是上传文件时报错,那么还需要检查应用层。

例如:

yaml 复制代码
spring:
  servlet:
    multipart:
      max-file-size: 100MB
      max-request-size: 100MB

这里两个参数不要混淆。

max-file-size 控制的是:

text 复制代码
单个文件最大允许大小

max-request-size 控制的是:

text 复制代码
整个 multipart 请求最大大小

例如一次上传:

text 复制代码
图片 A:8MB
图片 B:8MB
图片 C:8MB

虽然每一个都没有超过:

text 复制代码
10MB

但整个请求已经:

text 复制代码
24MB+

如果:

text 复制代码
max-file-size: 10MB
max-request-size: 20MB

依然可能失败。

所以多文件上传场景一定要同时关注两个参数。


八、最关键的问题:如果是第三方 API 返回 413 呢?

这才是很多开发者最容易绕进去的地方。

假设调用链路是:

text 复制代码
自己的系统
 ↓
第三方视频生成 API

第三方限制:

text 复制代码
最大 Request Body:10MB

你的请求:

text 复制代码
18MB

这时候你去修改:

text 复制代码
自己的 Nginx:100MB
自己的 Spring Boot:100MB
自己的 Tomcat:100MB

全部没有意义。

因为真正拒绝请求的是:

text 复制代码
第三方网关

这个时候真正应该改变的是:

请求方式,而不是服务器参数。


九、最推荐的方案:不要直接把大文件塞进 JSON

对于 AI 视频、AI 图片、OCR、语音识别等涉及媒体素材的 API,我现在更推荐这种架构:

text 复制代码
浏览器
 ↓
上传图片
 ↓
对象存储
 ↓
获得文件 URL
 ↓
调用 AI API
 ↓
只提交 URL

例如原来:

json 复制代码
{
  "prompt": "让画面中的人物向前行走",
  "image": "data:image/jpeg;base64,/9j/4AAQSk..."
}

改成:

json 复制代码
{
  "prompt": "让画面中的人物向前行走",
  "image_url": "https://static.example.com/images/xxx.jpg"
}

这时候 HTTP Request Body 可能从:

text 复制代码
8 MB

直接下降到:

text 复制代码
几 KB

差距非常明显。


十、为什么 URL 方案通常比 Base64 更合理?

除了避免 413,它还有很多额外收益。

1. 请求更轻

原来:

text 复制代码
POST 20MB JSON

现在:

text 复制代码
POST 3KB JSON

API Gateway、Nginx、应用服务器的压力都会明显下降。

2. 减少内存占用

Base64 通常需要经历:

text 复制代码
读取文件
 ↓
加载到内存
 ↓
转换 Base64
 ↓
生成 String
 ↓
拼装 JSON
 ↓
HTTP 序列化

如果一个请求有多个高清素材,并发再一高,内存压力会非常明显。

3. 降低网络重复传输

如果素材已经存在对象存储,再转换成 Base64 从业务服务器上传一次,本质上属于重复搬运。

URL 模式则可以让目标服务自己拉取资源。

4. 更适合异步任务

AI 视频生成通常不是同步完成,而是:

text 复制代码
提交任务
 ↓
返回 taskId
 ↓
模型异步生成
 ↓
查询任务状态
 ↓
返回视频 URL

既然整个架构本身就是资源 URL + 异步任务,那么输入素材同样采用 URL 往往更加自然。


十一、对象存储也不能直接"裸奔"

当然,把 Base64 改成 URL 并不意味着直接把所有素材永久公开。

实际生产环境可以采用:

text 复制代码
临时签名 URL

例如:

text 复制代码
上传对象存储
 ↓
生成 30 分钟有效的临时 URL
 ↓
提交 AI 任务
 ↓
AI 服务下载素材
 ↓
URL 自动失效

这样同时解决:

text 复制代码
大 Request Body
安全性
访问控制
生命周期管理

对于用户上传的图片、视频等内容,比永久公网 URL 更合理。


十二、看到本地代理地址,不代表代理就是罪魁祸首

还有一个很容易造成误判的现象。

浏览器 Network 中可能看到:

text 复制代码
Remote Address:
127.0.0.1:xxxx

很多人的第一反应是:

是不是代理软件把我的请求限制了?

不一定。

如果电脑启用了系统代理,那么浏览器实际连接的确实可能是:

text 复制代码
Browser
 ↓
127.0.0.1:代理端口
 ↓
Internet

所以 DevTools 显示本地地址很正常。

这只能证明:

浏览器通过本机代理发送了这个请求。

不能证明:

413 是本机代理返回的。

如果想快速排除代理,可以临时关闭代理后测试,或者使用 curl 绕过代理。

例如:

bash 复制代码
curl --noproxy "*" \
  -X POST \
  "https://api.example.com/v1/task"

也可以临时取消环境变量:

bash 复制代码
unset http_proxy
unset https_proxy
unset HTTP_PROXY
unset HTTPS_PROXY

如果绕过代理之后依然返回 413,那么问题大概率在服务端链路。


十三、怎么快速判断 413 到底是哪一层返回的?

我现在排查这类问题,一般按照下面的顺序。

第一步:

text 复制代码
检查 Request Payload

确认有没有:

text 复制代码
Base64
大 JSON
文件
视频
图片
音频

第二步:

text 复制代码
检查 Content-Length

先知道请求到底有多大。

第三步:

text 复制代码
检查自己的后端日志

如果请求发生的时候:

text 复制代码
Java 一条访问日志都没有

那么就要高度怀疑:

text 复制代码
Nginx
Ingress
Gateway
CDN
WAF
第三方网关

因为请求可能根本没有进入应用。

第四步:

检查 Nginx:

bash 复制代码
nginx -T | grep client_max_body_size

第五步:

检查 Spring Boot:

yaml 复制代码
spring.servlet.multipart.max-file-size
spring.servlet.multipart.max-request-size

第六步:

如果是 Kubernetes:

text 复制代码
Client
 ↓
Ingress
 ↓
Service
 ↓
Pod

还要检查 Ingress Controller 有没有 Request Body 限制。

第七步:

如果最终请求的是第三方 API:

text 复制代码
查看官方接口文档
 ↓
确认单文件限制
 ↓
确认 Request Body 限制
 ↓
确认是否支持 URL
 ↓
确认是否支持 multipart
 ↓
确认是否允许 Base64

到这里基本就能把问题定位出来。


十四、一个很实用的判断方法:看请求有没有到业务服务

这是排查 413 时我认为最好用的技巧之一。

假设:

text 复制代码
14:32:10

前端请求接口并收到:

text 复制代码
413

马上查看后端 access log。

如果:

text 复制代码
14:32:10 没有任何对应请求

说明请求很可能死在:

text 复制代码
后端之前

接着查 Nginx access/error log。

如果 Nginx 出现:

text 复制代码
client intended to send too large body

基本就可以直接锁定了。

反过来,如果 Nginx 已经成功转发:

text 复制代码
Nginx
 ↓
Spring Boot

而 Spring Boot 日志出现 multipart 相关异常,那么继续查应用配置。

不要一看到 HTTP 错误就直接扎进 Java 代码里 Debug。

很多时候代码根本没机会运行。


十五、生产环境不要无脑把限制改成 1GB

还有一个值得单独提醒的问题。

有些开发者遇到 413 之后直接:

nginx 复制代码
client_max_body_size 1024M;

问题确实可能暂时消失。

但这并不一定是一个好的解决方案。

如果你的业务本来只允许上传:

text 复制代码
20MB

结果网关允许:

text 复制代码
1GB

意味着异常客户端可以不断发送超大请求。

可能增加:

text 复制代码
带宽占用
临时文件占用
磁盘 IO
连接占用
内存压力
网关压力
攻击面

所以正确思路应该是:

text 复制代码
业务最大文件大小
        ↓
合理增加一定余量
        ↓
配置网关限制

例如业务最大允许:

text 复制代码
50MB

那么可以结合实际情况配置:

text 复制代码
60MB / 80MB / 100MB

而不是没有上限地往上加。


十六、AI 系统的媒体上传,建议从架构层面重新设计

如果现在正在做:

text 复制代码
AI 视频
AI 绘画
数字人
图生视频
视频理解
语音识别
多模态大模型

那么最好不要继续沿用传统的小 JSON API 思维。

推荐:

text 复制代码
                   ┌──────────────┐
                   │   Web / App  │
                   └──────┬───────┘
                          │
                    获取上传凭证
                          │
                          ↓
                 ┌─────────────────┐
                 │   Object Storage│
                 │ 图片 / 视频 / 音频 │
                 └────────┬────────┘
                          │
                       File URL
                          │
                          ↓
                   ┌─────────────┐
                   │ API Backend │
                   └──────┬──────┘
                          │
                  Prompt + File URL
                          │
                          ↓
                   ┌─────────────┐
                   │ AI Provider │
                   └──────┬──────┘
                          │
                       Task ID
                          │
                          ↓
                   异步查询任务结果

而不是:

text 复制代码
浏览器
 ↓
几十 MB 文件
 ↓
Base64
 ↓
巨大 JSON
 ↓
自己的后端
 ↓
再次上传第三方

后一种架构随着图片分辨率、视频大小、并发量上升,很快就会出现各种问题:

text 复制代码
413
超时
内存暴涨
GC 频繁
网关压力
带宽浪费
接口响应变慢

413 很多时候只是第一个暴露出来的问题。


十七、最后总结:以后看到 413,按照这张图排查

整个排查流程可以浓缩成:

text 复制代码
出现 413
   │
   ↓
检查 Request Body 大小
   │
   ├── 很小
   │     ↓
   │   检查网关异常配置
   │
   └── 很大
         │
         ↓
   是否包含 Base64 / 文件?
         │
         ├── 是
         │    ↓
         │  优先考虑 URL / 对象存储
         │
         └── 否
              ↓
          继续定位来源
              │
              ↓
      请求有没有进入后端?
          │           │
         没有         有
          │           │
          ↓           ↓
   CDN / WAF       Spring Boot
   Nginx           Multipart
   Gateway         Tomcat
   Ingress         应用配置
          │
          ↓
    是否第三方服务?
          │
         是
          ↓
   查看第三方 Body 限制
          ↓
   URL / Multipart / 压缩素材

写在最后

413 Content Too Large 本身并不复杂。

真正容易浪费时间的地方,是我们习惯把:

text 复制代码
HTTP 请求失败

直接等价成:

text 复制代码
后端代码出问题

但现在的系统链路越来越长。

一个请求从浏览器到业务代码,中间可能经过五六层甚至更多基础设施。任何一层都有能力拒绝这个请求。

特别是在 AI 视频、多模态应用快速普及之后,图片、音频、视频开始频繁进入 API 调用链路,过去那些只处理几 KB JSON 的设计方式已经不一定适用了。

所以以后再遇到 413,不要第一时间无脑把:

nginx 复制代码
client_max_body_size

调到几百 MB。

先问三个问题:

第一,请求到底有多大?

第二,413 到底是谁返回的?

第三,这个大文件真的有必要跟着 JSON 一起传吗?

如果第三个问题的答案是"不一定",那么真正值得优化的可能就不是一个 Nginx 参数,而是整个媒体资源的传输方式。

很多时候:

text 复制代码
Base64 塞 JSON

改成:

text 复制代码
对象存储 + 临时 URL + 异步任务

不仅解决了 413,也顺手解决了后面可能出现的带宽、内存、超时、网关压力和扩展性问题。

一个 413,看起来只是一次请求失败,往深了排查,其实是在提醒你重新审视整个文件传输链路。

相关推荐
程序员贺加贝18 分钟前
一次 SaaS ERP 主数据生命周期设计:Policy、PreCheck 与结构化阻断原因
java·后端·架构·saas
苏三说技术30 分钟前
Redis已正式接入AI
后端
思考着亮36 分钟前
11.Redis与Cache-Aside旁路缓存策略
后端
catino37 分钟前
spring-Bean
java·后端·spring
深入云栈1 小时前
Netty 4.2.x 源码深度解析 (十一):EventExecutor 业务线程池 —— 防止 IO 线程阻塞的并发模型
java·后端
柠檬味拥抱1 小时前
鸿蒙 AI 对话实战:用蓝耘 MaaS 给礼物 App 装上「多轮对话选礼大脑」
后端
唐青枫1 小时前
别再把 build.zig 当配置文件:Zig 构建系统从零到实战
后端
Naylor1 小时前
只借不占:Rust 的引用与借用
后端·rust
大勇前进1 小时前
Python装饰器、生成器、上下文管理器:3个必会的"魔法"机制
后端