关于Spring 报错“Required request part 'file' is not present”

148 阅读4分钟

遇到问题

在将我的项目打包放到服务器上运行时我遇到了这个问题

服务器报错.jpg

上面显示我的服务端没有接收到文件

但是我的本地测试是正常的

本地测试.png

问题排查

我为了检查在哪出了问题在这个接口加上了一些检查

代码检查.png

并观察我的控制台报错

本地测试

本地控制台.png

服务器测试

服务器端控制台.png

可见这个文件都在进入我resourceService的uploadFile方法之前就已经是空了,这个“Required request part 'file' is not present” 是MultipartFile这个类收到空值抛出的错误;

我又进行了一次绕过代理的测试

测试.png

结果.png

结果是正常的,可见并不是我代码的问题,大概率是服务器代理层对 multipart 请求体的处理异常

发现问题

然后我就检查了Nginx代理的配置,发现了这一行

nginx配置.png

这一行把我的Content-Length请求头设成了空

分析问题

MultipartFile:拆解文件上传时后端解析的核心逻辑

前端上传文件时,发送的 HTTP 请求会分为两部分:请求头 + 请求体

  1. 请求头:包含Content-Length(请求体字节大小)、Content-Type: multipart/form-data; boundary=xxx(标识请求体是多部分表单,boundary是分隔符)等核心字段;
  2. 请求体:不是单纯的文件二进制数据,而是按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 解析失败?

结合上面的原理,核心原因有两个:

  1. 读取请求体的 “标尺” 丢失:后端不知道该读取多少字节的请求体,要么读不到完整的文件二进制数据,要么直接放弃读取,导致找不到name="file"的部分;
  2. 破坏 multipart/form-data 的解析规范multipart/form-data属于 “有长度的请求体”,HTTP 规范要求这类请求必须携带Content-Length(或用Transfer-Encoding: chunked分块传输,但前端上传文件很少用分块)。清空Content-Length后,请求体变成 “无长度的非法结构”,Spring 的MultipartResolver(MultipartFile 的解析器)直接判定 “无有效文件参数”。

补充:特殊场景的兜底机制(为什么偶尔清空也能传?)

有同学可能会问:“我清空 Content-Length 后,偶尔也能传文件?”这是因为部分服务器支持Transfer-Encoding: chunked(分块传输)—— 当没有Content-Length时,服务器会按 “块” 读取请求体,直到遇到 “结束块”。但:

  1. 前端上传文件时,默认不会用分块传输,大概率还是依赖Content-Length
  2. 分块传输的解析逻辑更复杂,容易出现 “部分块丢失”,导致文件损坏或解析失败;
  3. Spring Boot 的MultipartResolver对分块传输的支持不如Content-Length稳定,生产环境绝对不推荐依赖这种兜底机制。

解决问题

最后把proxy_set_header Content-Length "";这行删除文件就能正常接收了

image.png