问题出现的前提
为了提高zuul的相应速度(原因可以看这篇文章zuul一次性能优化),禁用了FormBodyWrapperFilter,
zuul:
FormBodyWrapperFilter:
pre:
disable: true
由于公司使用Zuul作为Gateway的默认实现,在开发新业务时,有一个接口定义为POST请求,SpringMVC通过RequestParameter注解来获取请求发送参数,然后问题就出现了。
1.Zuul提示无法获取到Request Content
Gateway抛出异常
com.netflix.zuul.http.HttpServletRequestWrapper [168] -| Content-length different from byte array length! cl=112, array=0
在Filter层自己写代码尝试获取InputStream的内容,发现RequestBody数据为空。
然后查询资料看到这样一段:
在servlet规范3.1.1节里,对POST数据何时会被当做parameters有描述:
1. The request is an HTTP or HTTPS request.
2. The HTTP method is POST.
3. The content type is application/x-www-form-urlencoded.
4. The servlet has made an initial call of any of the getParameter family of methods on the request object.
- If the conditions are met, post form data will no longer be available for reading directly from the request object’s input stream.
所以不论tomcat、jetty还是其他servlet容器都遵循这个方式, 在获取流之前有过request.getParameter,数据会被解析,并且后续数据流不可再被读取.
而在Tomcat的实现中,由于使用了门面模式,一个request在被Servlet处理之前,Tomcat有预置的HiddenHttpMethodFilter
用来处理request请求,这其中就有用到request.getParameter()
方法,至于最底层的getParameter()方法的实现,可以参考:
org.apache.catalina.connector.Request.parseParameters()
因此对于以Post请求发送的x-www-form-urlencoded编码数据,zuul的Filter在处理时,无法从InputStream中获取到RequestBody进行转发,而请求头中的Content-Length又大于0导致下游服务无法解析到请求输入数据,请求处理出错
2.为什么application/x-www-form-urlencoded编码的数据会被当做parameter来解析呢?
使用http上传数据可以用GET或POST,使用GET的话,只能通过uri的queryString形式,这会遇到长度的问题,各个浏览器或server可能对长度支持的不同,所以到要提交的数据如果太长并不适合使用GET提交。
采用POST的话,既可以在uri中带有queryString也可以将数据放在body中。body内容可以有多种编码形式,其中application/x-www-form-urlencoded编码其实是基于uri的percent-encoding编码的,所以采用application/x-www-form-urlencoded的POST数据和queryString只是形式不同,本质都是传递参数。
在tomcat的Request.parseParameters方法里,对于application/x-www-form-urlencoded是有做判断的,对这种编码会去解析body里的数据,填充到parameters里,所以后续想再通过流的方式读取body是读不到的(除非你没有触发过getParameter相关的方法)。
在HTML4之前,表单数据的编码方式只有application/x-www-form-urlencoded这一种(现在默认也是这种方式),因为早期的时候,web上提交过来的数据也是非常简单的,基本上以key-value形式为主,所以表单采用application/x-www-form-urlencoded这种编码形式也没什么问题。
在HTML4里又引入了multipart/form-data编码,对于这两种编码如何选择,请参考这里。