SpringMVC 中 request.getInputStream() 为空解惑
Contents
起缘
最近公司跟美图对接DSP系统, 我们公司使用的是Java语言的 Spring Boot 框架, 美图给的是一个 Google 的 proto 文件.
对接的时候, 我们在Spring的 Controller 里使用
public Object recBid(HttpServletRequest request) throws IOException {
InputStream is = request.getInputStream();
OpenRtb.BidRequest bidRequest = OpenRtb.BidRequest.parseFrom(is);
...
}
不过, 问题来了. 美图那边的技术人员说他们发的HTTP请求, 是没有 Content-Type: application/x-protobuf
请求头(或类似).
但是, 我们发现, 接收的 inputStream 一下没数据!!!
调试
远程调试的时候发现, 美图那边发过来的请求的 Content-Type
为 application/x-www-form-urlencoded
并且报如下的异常
然后我自己新建了一个裸的 Spring Boot 项目来接收这个, 并用内置的Tomcat来启动, 却是没问题的.(我的天….)
这个问题我纠结了好几天, 最后记忆中隐约记得以前看过 Servlet 规范文档, 记得会有些情况下, 会导致 request 对象中的 inputStream 会被清空的.
所以, 就按这个思路继续排查.
原因
赶紧翻了以前写的博客, 还好记得有笔记.
Post表单时要满足以下条件时 parameter 集合才可用
请求是 http 或 https 请求的方法是 POST content type 为: application/x-www-form-urlencoded servlet 已经在 request 对象上调用了相关的 getParameter 方法。 当以上条件不满足时,POST 表单的数据并不会设置到 parameter 集合中,但依然可以通过 request 对象的 inputstream 来获取。 当以上条件满足时,POST 表单的数据在 request 对象的 inputstream 将不再可用了。
注: 美图那边发过来的就是 http + post + application/x-www-form-urlencoded
那也就是说, 肯定是在进入到 Controller 之前, 已经有其他地方调用过了 getParameter
方法, 才会导致 inputStream 不可用了. 这个极其重要的线索, 指导了我. 所以, 我在调试的时候, 在 request 对象的所有 getParameter
相关的地方, 都打上了断点, 看看到底是哪里导致这个问题的.
最后发现
是在 HiddenHttpMethodFilter
这个 filter 里调用了.
Spring自带的 filters 有:
其中, 会调用 getParameters
方法的有:
HttpPutFormContentFilter
和 HiddenHttpMethodFilter
至于为什么 Spring Boot 会在使用外部Tomcat时自动添加这两个 filter, 这个原因还没有仔细研究. 但使用内置的Tomcat启动时, 却发现没自动注册这些 filters .
解决
知道了原因, 那解决它就容易了. 如果实在不需要这些 filters 的话, 那就直接禁用这两个 filter 就可以了. 在Spring Boot 的配置里添加以下配置:
@Bean
public HttpPutFormContentFilter httpPutFormContentFilter() {
return new HttpPutFormContentFilter();
}
@Bean
public FilterRegistrationBean disableSpringBootHttpPutFormContentFilter(HttpPutFormContentFilter filter) {
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
filterRegistrationBean.setFilter(filter);
filterRegistrationBean.setEnabled(false);
return filterRegistrationBean;
}
@Bean
public HiddenHttpMethodFilter hiddenHttpMethodFilter() {
return new HiddenHttpMethodFilter();
}
@Bean
public FilterRegistrationBean disableSpringBootHiddenHttpMethodFilter(HiddenHttpMethodFilter filter) {
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
filterRegistrationBean.setFilter(filter);
filterRegistrationBean.setEnabled(false);
return filterRegistrationBean;
}
即可禁用这两个 filters.
这时再测试, 发现可以正确接收 inputStream
对象了.
另类解决
因为我们系统是使用 nginx 做代理的, 最简单的方式, 其实就是在 nginx 代理的时候, 直接让 nginx 设置一下相应的 content-type
请求头就可以了.