上周产品跑来说客户投诉了,业务系统里传一份 80 多兆的 Excel,页面直接报错,弹个 413 Request Entity Too Large 出来。
我一开始不慌,本地跑了下,好好的,能传上去。心想估计又是测试环境哪个配置没跟上。结果一排查排了两三个小时,中间还怀疑过是不是 Spring Boot 有什么隐藏限制,是不是 Tomcat 在拦,最后发现 Nginx 和后端都得改,只改一边不够。
写下来记录一下,下次自己再遇到也不用重新想一遍。
报错长啥样
前端 F12 一看,赤裸裸的一行:
413 Request Entity Too Large
然后我去后端应用日志里翻,啥也没有。这一点特别关键 —— 后端连日志都没打,说明请求压根没到应用,被前面的 Nginx 拦下来了。
我们的架构
浏览器 ──> Nginx(反代 + SSL)──> Spring Boot(内嵌 Tomcat)
一个上传请求得穿过 Nginx 和 Spring Boot 两道关。任何一道设了限制,超了就跪。这也是为什么下面要改好几个地方。
第一步:Nginx 放开
Nginx 默认 client_max_body_size 就 1M,你稍微大点的文件都过不去。413 基本就是它拦的。
vim /etc/nginx/nginx.conf
http 块里加一句:
http {
client_max_body_size 200m;
# 其他配置...
}
如果只想给某个域名放开,写 server 块或者 location 块里也行:
server {
listen 443 ssl;
server_name upload.xxx.com;
client_max_body_size 200m;
location /api/upload {
client_max_body_size 500m;
proxy_pass http://127.0.0.1:8080;
}
}
优先级: location > server > http,就近原则,越具体越管用。
改完记得先 -t 检查语法再 reload,别直接干:
nginx -t nginx -s reload
第二步:超时时间也要改(很多人栽在这)
我改完大小限制以为搞定了,结果传 80 多 M 的文件又报错,这次变成 504 Gateway Timeout。
一看 Nginx 日志,超时了。Nginx 默认代理超时 60 秒,公司内网还行,客户那边网速一般,60 秒根本传不完。
在 location 里再加上超时:
location /api/upload {
client_max_body_size 500m;
proxy_pass http://127.0.0.1:8080;
proxy_connect_timeout 300s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;
client_body_timeout 300s;
send_timeout 300s;
}
这几个参数我一开始也搞不清区别,简单说下:
-
proxy_connect_timeout:Nginx 连后端的超时 -
proxy_send_timeout:Nginx 往后端发数据的超时 -
proxy_read_timeout:Nginx 读后端响应的超时 -
client_body_timeout:Nginx 读客户端请求体的超时 -
send_timeout:Nginx 往客户端发响应的超时
大文件上传主要卡在 client_body_timeout 和 proxy_send_timeout 这俩,前三个改了没用别忘了这两个。 我第一次就只改了前三个,白改半天。
第三步:Spring Boot 的限制
Nginx 放开后,如果还报错,那就是应用自己拦的。Spring Boot 默认单文件才 1M,整个请求 10M。
application.yml 加:
spring: servlet: multipart: enabled: true max-file-size: 200MB max-request-size: 500MB file-size-threshold: 2KB
参数意思:
-
max-file-size:单个文件最大多大 -
max-request-size:整个请求最大多大(一次传多文件的时候用这个限总量) -
file-size-threshold:超过多大就写磁盘临时文件,防止全放内存把服务撑爆
这里有个特别坑的地方: 老版本 Spring Boot 1.x 的配置路径是 spring.http.multipart.maxFileSize(还是驼峰),2.x 之后变成 spring.servlet.multipart.max-file-size(横杠)。网上一堆过时博客混着写的,你抄错了程序照跑不误,就是不生效,还查不出问题,气死人。
如果不确定自己是啥版本,两个都配一遍也没啥毛病。
第四步:Tomcat 层限制
绝大多数情况上面三步就够了。但有时候你会发现前三步都改了还是报错,那就得看 Tomcat 了。
内嵌 Tomcat 一般不用管,但保险起见 yml 里也可以加:
server:
tomcat:
max-http-form-post-size: 500MB
max-swallow-size: 500MB
-
max-http-form-post-size:POST 请求体最大 -
max-swallow-size:Tomcat 拒了请求之后要"吞掉"多少数据,避免连接卡住不释放。设成 -1 就是不限
排查思路记一下
碰到 413 或者大文件上传失败,我一般按这个顺序看,基本能覆盖 99% 的情况:
1. 看错误码
-
413 → Nginx 或者应用层大小限制
-
504 → 超时问题
-
500 加堆栈 → 到应用了,看 Spring 的错
2. 看后端日志有没有这个请求进来
-
没有 → 前面的 Nginx 拦的
-
有但报错 → Spring 或 Tomcat 层的问题
3. 逐层放
-
先 Nginx(大小 + 超时)
-
再 Spring Boot(multipart)
-
最后 Tomcat(一般用不到)
附一个完整可用的 Nginx 配置
server {
listen 443 ssl;
server_name upload.xxx.com;
ssl_certificate /etc/nginx/ssl/server.crt;
ssl_certificate_key /etc/nginx/ssl/server.key;
client_max_body_size 500m;
client_body_timeout 300s;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_connect_timeout 300s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;
send_timeout 300s;
}
}
说两句
这问题看着小,排查起来还挺绕的。核心就是要搞明白请求经过哪几层,每一层都有自己的限制,任何一层没放开都不行。
另外一个建议:生产环境 client_max_body_size 别开太大,一开几个 G,万一有人恶意刷你磁盘一晚上就满了。我们的做法是全局设个小一点的(比如 20M),单独在需要上传的 location 里放大,比较稳。
有类似问题可以评论区聊。

833

被折叠的 条评论
为什么被折叠?



