遇到问题
在将我的项目打包放到服务器上运行时我遇到了这个问题
上面显示我的服务端没有接收到文件
但是我的本地测试是正常的
问题排查
我为了检查在哪出了问题在这个接口加上了一些检查
并观察我的控制台报错
本地测试
服务器测试
可见这个文件都在进入我resourceService的uploadFile方法之前就已经是空了,这个“Required request part 'file' is not present” 是MultipartFile这个类收到空值抛出的错误;
我又进行了一次绕过代理的测试
结果是正常的,可见并不是我代码的问题,大概率是服务器代理层对 multipart 请求体的处理异常
发现问题
然后我就检查了Nginx代理的配置,发现了这一行
这一行把我的Content-Length请求头设成了空
分析问题
MultipartFile:拆解文件上传时后端解析的核心逻辑
前端上传文件时,发送的 HTTP 请求会分为两部分:请求头 + 请求体
- 请求头:包含
Content-Length(请求体字节大小)、Content-Type: multipart/form-data; boundary=xxx(标识请求体是多部分表单,boundary是分隔符)等核心字段; - 请求体:不是单纯的文件二进制数据,而是按
multipart/form-data规范拼接的结构化内容,格式大致如下:
--boundary分隔符
Content-Disposition: form-data; name="file"; filename="test.png"
Content-Type: image/png
【文件的二进制数据】
--boundary分隔符--
然后这个Content-Length请求头会参与整个MultipartFile接收上传文件
1:读取请求头,确定解析规则
后端接收到请求后,首先读取请求头:
- 从
Content-Type中提取boundary分隔符(比如----WebKitFormBoundary7MA4YWxkTrZu0gW),知道 “用什么标记分割不同的表单字段”; - 读取
Content-Length的值,明确 “需要从请求体中读取多少字节的数据”—— 这是避免 “读多” 或 “读少” 的核心标尺。
2:按 Content-Length 读取完整请求体
后端会根据Content-Length的数值,从 TCP 连接中读取对应字节数的请求体数据,存入内存(小文件)或临时文件(大文件):
-
如果
Content-Length为空 / 缺失,后端无法确定 “该读多少字节”:- 要么一直等待读取,直到连接超时;
- 要么提前终止读取,拿到不完整的请求体;
- 甚至直接判定 “请求体为空”,抛出
Required request part 'file' is not present。
3:按 boundary 分割请求体,定位文件部分
拿到完整的请求体后,框架会用boundary分隔符拆分内容:
- 跳过非文件的表单字段(如果有);
- 定位到
name="file"的部分,提取filename(文件名)、Content-Type(文件类型)等元信息; - 截取分隔符之间的二进制数据,这就是文件的原始内容。
4:封装为 MultipartFile 对象
框架将二进制数据、文件名、文件类型等信息封装成MultipartFile对象:
- 我们调用
multipartFile.getBytes()/transferTo()等方法时,本质是读取这部分二进制数据; - 如果步骤 2 中请求体读取不完整(比如 Content-Length 为空导致解析中断),
MultipartFile就会是 “空对象”,调用方法时可能报空指针或 “文件不存在”。
为什么清空 Content-Length 会让 MultipartFile 解析失败?
结合上面的原理,核心原因有两个:
- 读取请求体的 “标尺” 丢失:后端不知道该读取多少字节的请求体,要么读不到完整的文件二进制数据,要么直接放弃读取,导致找不到
name="file"的部分; - 破坏 multipart/form-data 的解析规范:
multipart/form-data属于 “有长度的请求体”,HTTP 规范要求这类请求必须携带Content-Length(或用Transfer-Encoding: chunked分块传输,但前端上传文件很少用分块)。清空Content-Length后,请求体变成 “无长度的非法结构”,Spring 的MultipartResolver(MultipartFile 的解析器)直接判定 “无有效文件参数”。
补充:特殊场景的兜底机制(为什么偶尔清空也能传?)
有同学可能会问:“我清空 Content-Length 后,偶尔也能传文件?”这是因为部分服务器支持Transfer-Encoding: chunked(分块传输)—— 当没有Content-Length时,服务器会按 “块” 读取请求体,直到遇到 “结束块”。但:
- 前端上传文件时,默认不会用分块传输,大概率还是依赖
Content-Length; - 分块传输的解析逻辑更复杂,容易出现 “部分块丢失”,导致文件损坏或解析失败;
- Spring Boot 的
MultipartResolver对分块传输的支持不如Content-Length稳定,生产环境绝对不推荐依赖这种兜底机制。
解决问题
最后把proxy_set_header Content-Length "";这行删除文件就能正常接收了