在Filter中获取请求体,就得解析一次request的inputStream。
而在tomcat的设计中,request的inputStream只能读取一次,读取完一次后,虽然inputStream还在那,也不会变成空,但里面的内容已经被没有了。
解决方案很简单,就是继承HttpServletRequestWrapper,缓存request中流的内容。
比如实现一个类:ServletRequestReadWrapper
整个类的结构是这样的:
然而世事总不那么一帆风顺。
当请求头Content-Type值为 multipart/form-data 时,情况就出现了问题。在https://www.zhihu.com/question/434950674 这个回答中有说明。
在使用multipart/form-data上传文件时,controller类似这样的
1 | @PostMapping |
想要得到一个MultipartFile类型的内容,原理与其他类型一样,spring从request中获得流内容,并反射创建MultipartFile。
但相对于普通类型,Multipart并没有从request.getInputstream中获取内容,而是从另一个方法getParts里面获取,但getParts返回的内容也是解析的request.getInputStream中的内容。
是不是有点奇怪,为什么在wrapper里面已经缓存了inputstream内容,为什么到了右下解再去getInputStream时,却没有了呢?
tomcat的request类结构是一个装饰器模式
requestWrapper中的getInputStream其实还得来源于真实的Request对象。而在整个处理过程的末端,获取inputStream,并不是从requestWrapper中获取的,而是从真实的Request对象中获取。此时流内容已经被读取过,自然就读取不到了。
再回味下主要的源码,HttpServletRequestWrapper中getParts()
1 | @Override |
到了RquestFacade的getParts()
1 | @Override |
再到Request的getParts()
1 | public Collection<Part> getParts() throws IOException, IllegalStateException, |
到此,原理已经讲清楚了。怎么解决呢?
1、在Wrapper中,非multipart缓存inputstream,是multipart时,则缓存parts
1 | public ServletRequestReadWrapper(final HttpServletRequest request) throws IOException, ServletException { |
2、放弃multipart的消息体。
得到消息体的内容,有时只是为了记录日志,像文件上传,其实得到的消息体也没啥好记录的,所以放弃记录mulipart类型消息体。