文章目录
一、遇到的问题
拦截器(interceptor)中通过request获取的流读出body数据后,
request.getInputStream();
controller方法接收body时出现报错
Resolved [org.springframework.http.converter.HttpMessageNotReadableException: I/O error while reading input message; nested exception is java.io.IOException: Stream closed]
二、解决方法
1. 创建HttpServletRequest包装类
- 继承HttpServletRequestWapper类
package chances.epg.user.filter;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.Charset;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import chances.epg.user.util.HttpHelper;
/**
* @author <a href="mailto:[email protected]">lanxing</a>
*
*/
public class BodyReaderHttpServletRequestWrapper extends HttpServletRequestWrapper {
private final byte[] body;
public BodyReaderHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
String bodyString = HttpHelper.getBodyString(request);
body = bodyString.getBytes(Charset.forName("UTF-8"));
}
@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream bais = new ByteArrayInputStream(body);
return new ServletInputStream() {
@Override
public int read() throws IOException {
return bais.read();
}
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) {
}
};
}
}
2. 创建过滤器
- 新建一个过滤器(Filter),对request进行包装
package chances.epg.user.interceptor;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
/**
* @author <a href="mailto:[email protected]">lanxing</a>
*
*/
@Component
@WebFilter(filterName = "httpServletRequestWrapperFilter", urlPatterns = { "/*" })
public class HttpServletRequestWrapperFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
ServletRequest requestWrapper = null;
if (request instanceof HttpServletRequest) {
HttpServletRequest httpRequest = (HttpServletRequest) request;
// 遇到post方法才对request进行包装
String methodType = httpRequest.getMethod();
// 上传文件时同样不进行包装
String servletPath = httpRequest.getRequestURI().toString();
if ("POST".equals(methodType) && !servletPath.contains("/material/upload")) {
requestWrapper = new BodyReaderHttpServletRequestWrapper(
(HttpServletRequest) request);
}
}
if (null == requestWrapper) {
chain.doFilter(request, response);
} else {
chain.doFilter(requestWrapper, response);
}
}
@Override
public void destroy() {
}
}
3. 流读取工具类
- 通过流(inputStream)获取字符串的工具类
package chances.epg.user.interceptor;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import javax.servlet.http.HttpServletRequest;
/**
* @author <a href="mailto:[email protected]">lanxing</a>
*
*/
public class HttpHelper {
public static String getBodyString(HttpServletRequest request) throws IOException {
StringBuilder sb = new StringBuilder();
InputStream inputStream = null;
BufferedReader reader = null;
try {
inputStream = request.getInputStream();
reader = new BufferedReader(
new InputStreamReader(inputStream, Charset.forName("UTF-8")));
char[] bodyCharBuffer = new char[1024];
int len = 0;
while ((len = reader.read(bodyCharBuffer)) != -1) {
sb.append(new String(bodyCharBuffer, 0, len));
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return sb.toString();
}
}
4. 拦截器中获取body数据
- 数据获取
String body = HttpHelper.getBodyString(request);
三、依然失效的可能问题
1. filter类没有加注解@Component
@Component
@WebFilter(filterName = "httpServletRequestWrapperFilter", urlPatterns = { "/*" })
public class HttpServletRequestWrapperFilter implements Filter {
这里之前我遗漏了
2. 如果仍然没有生效,则考虑类是否补扫描到(生成Bean)
启动类增加扫描包说明
@SpringBootApplication
@ServletComponentScan(basePackages = {"chances.wechat.filter"})
public class UcenterApplication {
public static void main(String[] args) {
SpringApplication.run(UcenterApplication.class, args);
}
四、注解WebFilter 参数urlPatterns 不生效问题
1.目的:不希望所有的请求都进入这个filter
- 不在filter本身判断请求路径
- @WebFilter(filterName = "httpServletRequestWrapperFilter", urlPatterns = { "/*" })
2. 解决方案
- 去掉过滤器@Component注解
- 配置类中注入bean (加了@Configuration的类)
@Bean
public FilterRegistrationBean<HttpServletRequestWrapperFilter> postBodyFilterRegistration() {
FilterRegistrationBean<HttpServletRequestWrapperFilter> registration = new FilterRegistrationBean<HttpServletRequestWrapperFilter>();
registration.setFilter(httpServletRequestWrapperFilter());
registration.setName("HttpServletRequestWrapperFilter");
registration.addUrlPatterns("/auth");
// registration.addInitParameter("excludeUrls", "/web/login");
registration.setOrder(0);
return registration;
}
@Bean
public HttpServletRequestWrapperFilter httpServletRequestWrapperFilter() {
return new HttpServletRequestWrapperFilter();
}
五、总结
1. 原理上目前的理解
搜索了许多类似的文章,思路都类似,以上代码是搜索的代码基础上做得一些调整,但感觉原理上的解释有一些误差
许多文章在这个问题上提到过流的复制或者说重新把数据写入到流之中,我有被这些说法误导
在我看来,这一思路的解决原理:
- 把request中的body保存在HttpServletRequest包装类的属性body中,
private final byte[] body;
public BodyReaderHttpServletRequestWrapper(HttpServletRequest request) {
super(request);
String sessionStream = getBodyString(request);
body = sessionStream.getBytes(Charset.forName("UTF-8"));
}
- 包装类中重写了getInputStream方法,每次通过传入保存的body返回一个新的流
@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream bais = new ByteArrayInputStream(body);
return new ServletInputStream() {
@Override
public int read() throws IOException {
return bais.read();
}
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) {
}
};
}