Required request part ‘file‘ is not present
问题
SpringBoot Controller (@RequestParam(“file”) MultipartFile file),报错:Required request part ‘file‘ is not present
定位
- 前端使用了Swagger接口文档和curl分别尝试,同样的报错,那么就是后端问题了。
- F12再次检查 Request的 Content-Type 是否为multipart/form-data 类型,请求类型确实没错。
- 在Controller参数中加上HttpServletRequest requet,通过request.getInputStream()能拿到数据。
- 开始Google,看了众多文章,最有用的一篇是这个:https://segmentfault.com/a/1190000022635405 。通过它说的解决办法,确实可以解决我当下的问题。
- spring.mvc.hiddenmethod.filter.enabled=true配置后,问题解决。
在此基础上 AI Check
ChatGPT告诉我,很可能是因为安全Filter或缓存Wrapper提前读了一次Stream,后续MultipartResolver解析数据时,就读不到了。
确实,我的项目中自定义了
public class RepeatableRequestWrapper extends HttpServletRequestWrapper {
private final byte[] body;
public RepeatableRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
this.body = getRequestBodyBytes(request);
}
private byte[] getRequestBodyBytes(HttpServletRequest request) throws IOException {
try (InputStream inputStream = request.getInputStream();
ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
return outputStream.toByteArray();
}
}
}
spring.mvc.hiddenmethod.filter.enabled=true 启用该配置会注册 OrderedHiddenHttpMethodFilter,它的默认顺序很靠前(在 Spring Security 之前)。这会改变过滤器链的时序;某些组合下,它会间接改变谁先触碰请求体(例如先尝试参数解析/方法覆盖判断,或让你的分支逻辑提前短路),从而 恰好避开了你在自定义过滤器里读取body的路径,造成“看起来修好了”的错觉。官方文档也特别提醒该过滤器与 multipart 的顺序关系(应当在 multipart 处理之后),如果顺序不当,反而会带来更多意外。因此,依赖它来规避问题并不可靠。
小结:真正的根因仍是你在自定义过滤器中读取了请求体而未做缓存/复用处理;HiddenMethod 的开启只是“时序变化”带来的旁路效应。
关于这个配置,我还是一知半解。
修改后的RepeatableRequestWrapper
public class RepeatableRequestWrapper extends HttpServletRequestWrapper {
private final byte[] body;
private final boolean isMultipart;
public RepeatableRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
String contentType = request.getContentType();
this.isMultipart = contentType != null && contentType.toLowerCase().startsWith("multipart/");
if (isMultipart) {
// multipart请求不缓存 body,防止StandardMultipartHttpServletRequest request.getParts时,拿不到请求体
this.body = null;
} else {
this.body = getRequestBodyBytes(request);
}
}
...
}
这样改了之后,MultipartFile就能正常接收到了。
但是
为什么只有multipart请求,按照老的RequestWrapper不能拿到请求体,其他请求都可以拿到呢?
打断点看源码
-
根据 [refer1] 中提到的
StandardMultipartHttpServletRequest#parseRequest()方法中的request.getParts()
为空 -
FilterChain在doDispatch之前
-
我在filter中,读了request的InputStream,存到了body中。我自定义的Wrapper没有实现getParts()
-
-
到这里的时候,parts里面已经有数据了。parts中的数据什么时候set进来的?
-
getParameter()
解析Request参数的时候,就解析了parts -
到这里先偷懒,放弃继续挖下去。
总结
我自定义的RepeatableRequestWrapper没有实现getParts()方法。
这里getMatchingRequest() 从自定义的RepeatableRequestWrapper 一直找到了Request才找到match的。
于是,getParameter()就到了Request,Request就解析了parts并存储。
后面到了DispatcherServlet的逻辑,解析Multipart,回到了一开始的StandardMultipartHttpServletRequest#parseRequest()方法中的request.getParts()
这里先调用RepeatableRequestWrapper的 getParts,由于没有实现,层层向上,最终到了Request的getParts()
最终的解决方案: RepeatableRequestWrapper内,对于mutipart类型的请求,不要去读Stream,不做缓存。
--- 完 ---