SpringBoot通过请求对象获取输⼊流⽆数据
在做⽂件上传通过post⽅式进⾏binary上传开发的时候能正常处理,⽼的设备采⽤同样的协议进⾏传输的时候遇到了奇怪的事情,在SpringBoot的Controller⾥⾯直接使⽤HttpServletRequest的getInputStream()⽅法的时候获得的输⼊流⽆数据,通过getContentLength()获得内容长度的时候⼜是有值的,但是写⼊⽂件时通过UltraEdit打开16进制发现全部是0,同时使⽤DataInputStream解析ServletInputStream流报EOFException异常,接下来开始排查:
分析完后和我们现有问题没有什么关系,继续排查别的⽅向。
2、通过tcpdump抓包
tcpdump -i eth0 -tnn dst port 80 -w 1234.pcap
然后通过wireshark打开终于发现问题所在,接⼊如下:
问题出现了:由于之前⽼的协议采⽤application/x-www-form-urlencoded,之前定义协议的使⽤说过要采⽤oct-stream⽅式,结果前端开发⼈员没有注意⽽采⽤默认application/x-www-form-urlencoded,在springmvc框架下并没有影响。
总结:定义协议的时候⼀定确认请求类型和边界。
问题解决:
出现这种情况,⾸先怀疑输⼊流已经被使⽤了,由于请求输⼊流是不带缓存的,使⽤⼀次后流就⽆效了,通常触发解析输⼊流就是调⽤了getParameter()等⽅法,经过检查代码确认没有做过相关处理,所以怀疑SpringBoot底层做了处理。
查了⼀下SpringBoot的⾃动装配配置,在WebMvcAutoConfiguration中初始化了⼀个OrderedHiddenHttpMethodFilter,默认这个过滤器是⽣效的,相关代码如下:
@Bean
@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
@ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled", matchIfMissing = true)
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
return new OrderedHiddenHttpMethodFilter();
}
这是⽼版本的配置:
新版本:
@Bean
@ConditionalOnMissingBean({HiddenHttpMethodFilter.class})
@ConditionalOnProperty(
prefix = "spring.mvc.hiddenmethod.filter",
name = {"enabled"},
matchIfMissing = false
)
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
return new OrderedHiddenHttpMethodFilter();
}
默认开启的过滤器部分:
@Bean
@ConditionalOnMissingBean({FormContentFilter.class})
@ConditionalOnProperty(
prefix = "spring.mvc.formcontent.filter",
name = {"enabled"},
matchIfMissing = true
)
public OrderedFormContentFilter formContentFilter() {
return new OrderedFormContentFilter();
}
OrderedHiddenHttpMethodFilter继承了OrderedHiddenHttpMethodFilter,⽽OrderedHiddenHttpMethodFilter⼜继承了HiddenHttpMethodFilter,在该类的doFilterInternal()⽅法中发现有对参数做处理,相关代码如下:
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
HttpServletRequest requestToUse = request;
if ("POST".Method()) &&
String paramValue = hodParam);
if (StringUtils.hasLength(paramValue)) {
String method = UpperCase(Locale.ENGLISH);
if (ains(method)) {
requestToUse = new HttpMethodRequestWrapper(request, method);
}
}
}
filterChain.doFilter(requestToUse, response);
}
⾄此就可以定位问题的所在了,到了问题下⾯就来看看如何解决。
⽹上流传⽐较多的三种解决⽅案:
⽅案⼀:禁⽤默认的过滤器
SpringBoot在⾃动装配的时候注⼊了OrderedHiddenHttpMethodFilter,如果我们不需要该功能,在配置⽂件中显⽰的将其设置为false。配置如下:spring.mvc.abled=false
和⽅案三⼀样,springboot是为了解决rest开发过程中兼容put、delete等接⼝使⽤,新版本钟默认不启⽤改过滤器,只有标识为true才⽣效,所以此处置为false⽆意义。
⽅案⼆:使⽤@RequestBody注解
在需要获取请求输⼊流的⽅法上添加字节数组的参数,并添加@RequestBody注解,⽰例代码如下:
@RequestMapping("/**")
public 返回值⽅法名(@RequestBody byte[] body) {
// ...
}
此⽅案也不是很靠谱,针对获取流的应⽤场景⽆意义。
⽅案三:⾃定义HiddenHttpMethodFilter过滤器
参考代码如下:
@Bean
public HiddenHttpMethodFilter hiddenHttpMethodFilter() {
return new OrderedHiddenHttpMethodFilter(){
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
filterChain.doFilter(request, response);
}
};
}
跟进源码其实可以发现,在springboot中不只有此⼀处使⽤了,所以这个⽅案不能很好解决此类问题,和⽅案⼀中类似,只有配置true才⽣效。
后记:
不过遗憾的是上诉三个⽅案都不⽣效,最终通过重写OrderedHiddenHttpMethodFilter并添加业务逻辑后完成(暂时解决)。
完美解决⽅案如下,核⼼是重写HttpServletRequestWrapper已实现getInputStream多次利⽤的特点,对于后续的过滤器等处理也不受影响。
代码如下:
1、RequestWrapper.java
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;
/**
* @date 2021/5/15
* 类说明:
*/
public class RequestWrapper extends HttpServletRequestWrapper {
//参数字节数组
private byte[] buffer;
public RequestWrapper(HttpServletRequest request) throws IOException {
super(request);
InputStream is = InputStream();
ByteArrayOutputStream os = new ByteArrayOutputStream();
byte[] tmp = new byte[1024];
int read = 0;
while ((read = is.read(tmp)) > 0){
os.write(tmp,0,read);
}
this.buffer = os.toByteArray();
}
@Override
public ServletInputStream getInputStream() throws IOException {
return new BufferServletInputStream(this.buffer);
}
}
class BufferServletInputStream extends ServletInputStream{
private ByteArrayInputStream inputStream;
public BufferServletInputStream(byte[] buffer) {
this.inputStream = new ByteArrayInputStream(buffer);
}
@Override
public int read() throws IOException {
ad();
}
}
2、重写Filter,RequestWrapperFilter.java
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
/**
* @date 2021/5/15
* 类说明:
*/
public class RequestWrapperFilter implements Filter {
private FilterConfig filterConfig = null;
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException, IOException { RequestWrapper request = new RequestWrapper((HttpServletRequest) servletRequest);
filterChain.doFilter(request, servletResponse);
}
@Override
public void init(FilterConfig filterConfiguration) throws ServletException {
this.filterConfig = filterConfiguration;
}
@Override
public void destroy() {
this.filterConfig = null;
}
}
3、添加@Configyration
import com.alibaba.fastjson.JSONObject;
import org.apachemons.beanutils.BeanUtils;
import org.apachemons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.filter.OrderedHiddenHttpMethodFilter;
import t.annotation.Bean;
import t.annotation.Configuration;
import org.springframework.web.filter.HiddenHttpMethodFilter;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipartmons.CommonsMultipartResolver;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;filter过滤对象数组
import java.io.*;
import flect.InvocationTargetException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
/**
* @date 2021/5/14
* 类说明:多媒体上传配置
*/
@Configuration
public class MultipartResolverConfig {
private static final Logger logger = Logger(MultipartResolverConfig.class); @Bean
public CommonsMultipartResolver multipartResolver() {
CommonsMultipartResolver resolver = new CommonsMultipartResolver();
resolver.setMaxUploadSize(10485760);
resolver.setDefaultEncoding("UTF-8");
return resolver;
}
@Bean(name = "uploadFileFilter")
public RequestWrapperFilter uploadFileFilter() {
return new RequestWrapperFilter();
}
@Bean(name = "uploadFileBean")
public FilterRegistrationBean uploadFileBean() {
FilterRegistrationBean registration = new FilterRegistrationBean(uploadFileFilter());
registration.setOrder(0);
registration.addUrlPatterns("/upload/v1/*");
return registration;
}
}
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论