先贴报错
因为我使用了 MultipartFile 作为文件上传的接收入参,前端通过请求网关,网关路由请求到后端服务,导致后端服务接受流失败意外结束 具体报错信息:
Could not parse multipart servlet request; nested exception is java.io.IOException: org.apache.tomcat.util.http.fileupload.FileUploadBase$IOFileUploadException: Processing of multipart/form-data request failed. Stream ended unexpectedly
我看了好多网上的帖子,有说是 tomcat 的 bug,说是要将 tomcat 升级到 9.33 + 的版本(我用的是 8.5 的),有说是 tomcat 配置连接超时等说法的,全部试下来发现并没有解决问题
不通过网关,直接调用后端接口是可以上传文件成功,通过 zuul 上传则会导致流异常关闭,于是定位网关问题通过 debug 源码的方式来追溯问题
首先 zuul 网关的核心类是 com.netflix.zuul.http.ZuulServlet 将 debug 放在 zuulServlet
preRoute:就是我们继承 zuulfilter 后 通过 filterType 分为路由前、路由后、异常路由等
通过痛苦的追溯源码,发现当 tomcat 接受到请求后,封装为 HttpServletRequest,和通过层层过滤后到达 zuulServlet 的 HttpServletRequest 请求头信息不同了
图一为 tomcat 封装的 HttpServletRequest
(图一)
图二为当请求到达 zuulServlet 的时候,获取到的 HttpServletRequest
(图二)
由此判断从 tomcat 到 zuulServlet 之间有环节篡改了 HttpServletRequest , 经过堆栈信息的调试,定位问题在于我依赖的一个组件,该组件有一个过滤器 实现了 OncePerRequestFilter , 在这个过程中他通过 HttpServletRequestWrapper 重写了请求头,并将重写后的请求头放入后续的过滤链
因为文件上传是通过流的方式,同时流的特性是读过一次就没有了,该组件在重写 HttpServletRequest 的时候读了流,导致 zuul 后续路由请求到后端服务的时候,后段服务的 tomcat 发现 Content-Type:multipart/form-data 类型,开始读流读不到,就会导致最开始的贴图的报错
后端服务 tomcat 源码报错的位置
总结
请求在前面的流程中已经被读取处理了,流是不可重复读取的,这意味着 zuul 在转发这个 Request 的时候,已经丢失了原本的内容,因此需要把放回去。这也是开发 spring cloud gateway 的原因之一,因为它没有这些问题