异常信息
I/O error while reading input message; nested exception is java.io.IOException: Stream closed
异常原因
首先request.getInputSteam(),只能获取一次,如果重复读就会出现上面异常信息。为什么会出现多次读呢,因前面设置了拦截器,用到了request中的参数,在取 body参数时把request.getInputStream()关闭,导致后面doFilter到@requestBody的对象拿取不到。
解决方案
思想是将inputStream缓存下来,然后后面filter再读的时候,直接从缓存中读取即可。
实现代码
- 缓存实现
public class CachingServletRequestWrapper extends HttpServletRequestWrapper {
private byte[] body;
public CachingServletRequestWrapper(HttpServletRequest request) {
super(request);
}
@Override
public ServletInputStream getInputStream() throws IOException {
if (body == null) {
body = IOUtils.toByteArray(super.getInputStream());
}
return new CachingServletInputStream(new ByteArrayInputStream(body));
}
@Override
public String getCharacterEncoding() {
String enc = super.getCharacterEncoding();
return (enc != null ? enc : StandardCharsets.UTF_8.name());
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream(), getCharacterEncoding()));
}
public static class CachingServletInputStream extends ServletInputStream {
private final ByteArrayInputStream is;
public CachingServletInputStream(ByteArrayInputStream is) {
this.is = is;
}
@Override
public int read() {
return is.read();
}
@Override
public boolean isFinished() {
return true;
}
@Override
public boolean isReady() {
return is.available() <= 0;
}
@Override
public void setReadListener(ReadListener readListener) {
}
}
}
- 过滤器实现
public class FundsCachingStreamRequestFilter implements Filter {
private static final Set<String> SUPPORT_METHODS = ImmutableSortedSet.of("POST");
@Override
public void init(FilterConfig filterConfig) throws ServletException {}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
ServletRequest requestWrapper = null;
if (servletRequest instanceof HttpServletRequest) {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
if (shouldCache(httpServletRequest, servletResponse)) {
//获取请求中的流如何,将取出来的字符串,再次转换成流,然后把它放入到新request对象中。
requestWrapper = new CachingServletRequestWrapper(httpServletRequest);
/*
* 重置RequestContextHolder.setRequestAttributes,
* 因为缓存了stream后,在CommonWebScope里(CookieInfoInjectFilter和APIRequestInfoInjectFilter会用到)
* HttpServletRequest request = ((ServletRequestAttributes) currentRequestAttributes()).getRequest();
* try (InputStream input = request.getInputStream()) {
* 这里又取了当前最原始的request,就导致了获取不到inputstream
* */
httpServletRequest = (HttpServletRequest) requestWrapper;
HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse;
RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(httpServletRequest, httpServletResponse));
}
}
// 在chain.doFiler方法中传递新的request对象
if (requestWrapper == null) {
requestWrapper = servletRequest;
}
filterChain.doFilter(requestWrapper, servletResponse);
}
@Override
public void destroy() {
}
private boolean shouldCache(HttpServletRequest servletRequest, ServletResponse servletResponse) {
return true;
// 只有 SUPPORT_METHODS 中的请求,并且 Content-Type 不是 multipart 时才会包装,避免大文件上传时过高的占用内存
// if (SUPPORT_METHODS.stream()
// .anyMatch(method -> equalsIgnoreCase(method, servletRequest.getMethod()))) {
// return !isMultipart(servletRequest);
// }
// return false;
}
}
- 过滤器注入
顺序要尽量靠前,否则如果先被其他读了,就读不到没法再缓存了。
/**
* 用来解决重复获取getInputStream
*/
@Bean
public FilterRegistrationBean fundsCachingStreamRequestFilterRegistration() {
FundsCachingStreamRequestFilter fundsCachingStreamRequestFilter = new FundsCachingStreamRequestFilter();
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setFilter(fundsCachingStreamRequestFilter);
registrationBean.addUrlPatterns("/*");
registrationBean.setName("fundsCachingStreamRequestFilter");
registrationBean.setOrder(2);
return registrationBean;
}