新项目准备使用最新的springboot(2.7.2),但是发现文件上传报错,后台获取到的文件为空。问题排查过程记录下。
如果需要看原因,可直接跳转原因
1. 问题表现
获取到的文件为空
同时控制台报错json解析失败
2.问题分析
2.1 第一次问题分析
- 看到控制台报json解析失败。报错位置位于xsswrapper中,作用为重复读取post请求body数据,并进行非法字符过滤。
因为文件上传content-type为multipart/form-data,所以此处添加不是application/json时,直接返回。发现不起作用
- 看到request类型为StandardMultipartHttpServletRequest,手动搜索了一下该类,断点parseRequest,发现request.getParts()获取到的为0。对比springboot旧版本项目,发现此处有问题。
所以,在显示时xsswrapper处理body数据之前,手动调用一下,看下返回什么(因为xsswrapper中报错了)
手动调用一下后发现,此处可以正确获取到文件parts,同时后面setRequestBody(从流中读取body数据)正常,不会读取到文件信息。
至此,能够解决问题,但是不知道什么原因。
2.2 第二次问题分析
后来,刚好有点空闲时间,准备找下什么原因。
花了好长一段时间追踪Tomcat源码,看下request流转过程是怎样的,为什么会从流中读取到文件信息(结果没找到…)。
看到一篇博文,Tomcat如何处理文件上传的,其中提到Request类中有个parseParameters处理参数,里面有处理multipart/form-data
断点发现,旧版本中会调用该方法,而新版本(2.7.2)却不会。通过查看旧版本中调用该方法时的调用栈,发现最终是在HiddenHttpMethodFilter中
HiddenHttpMethodFilter中会调用request.getParameter()
对比新版本和旧版本filter信息,发现两者filter数量存在差异
新版本:
旧版本:
至此,问题原因浮出水面,因为新版本中没有HiddenHttpMethodFilter。
通过旧版本中点击HiddenHttpMethodFilter中usages(不得不说,idea功能真的强大)
发现是在WebMvcAutoConfiguration中注册的,对比新旧版本发现了问题所在
左侧为新版本(2.7.2),右侧为旧版本(2.0.9.RELEASE)
旧版本中默认注册HiddenHttpMethodFilter,而新版本中默认不注册,需要手动注册spring.mvc.hiddenmethod.filter.enabled=true。
添加该配置后,文件上传正常。
至此问题解决,如果不需要该filter,可以手动调用一下request.getParameterMap(),初始化参数处理
3.原因
新版本中没有自动注册HiddenHttpMethodFilter,导致没有调用request.parseParameters(对参数进行处理,包括multipart/form-data)
左侧为新版本(2.7.2),右侧为旧版本(2.0.9.RELEASE)
4.解决
需要org.apache.catalina.connector.Request.parseParameters()执行到
- 通过添加spring.mvc.hiddenmethod.filter.enabled=true配置
- 或者手动调用下request.getParameterMap(),初始化参数处理
Map<String, String[]> parameterMap = request.getParameterMap();
5.涉及版本
springbootv2.2.0.M5及之后
看了下WebMvcAutoConfiguration记录,发现从v2.1.0.M2开始添加了@ConditionalOnProperty注解,matchIfMissing=true。
从v2.2.0.M5之后,matchIfMissing变为false