问题
最近做请求切面日志,发现无法读取request的body内容,会提示java.io.IOException: Stream closed。 记录下处理方式
问题原因
httpServletRequest中的流只能读取一次的原因
处理思路
先读取流当中的数据存下来,然后获取流的时候,将存下来的数据重新转换成流输出出去。
具体方式
1 重写HttpServletRequestWrapper
package cn.hjljy.fastboot.autoconfig.filter;
import lombok.extern.slf4j.Slf4j;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;
import java.nio.charset.Charset;
@Slf4j
public class RequestReaderHttpServletRequestWrapper extends HttpServletRequestWrapper {
private final byte[] body;
public RequestReaderHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
body = inputStream2String(request.getInputStream()).getBytes(Charset.forName("UTF-8"));
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
@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) {
}
};
}
/**
* 将inputStream里的数据读取出来并转换成字符串
*
* @param inputStream inputStream
* @return String
*/
private String inputStream2String(InputStream inputStream) {
StringBuilder sb = new StringBuilder();
BufferedReader reader = null;
try {
reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8")));
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
} catch (IOException e) {
sb.append("body参数获取失败");
log.error(e.getMessage());
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
log.error(e.getMessage());
}
}
}
return sb.toString();
}
}
2 新建过滤器
package cn.hjljy.fastboot.autoconfig.filter;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
public class RequestBodyFilter implements Filter {
@Override
public void destroy() {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
ServletRequest requestWrapper = null;
if (request instanceof HttpServletRequest) {
HttpServletRequest httpServletRequest = (HttpServletRequest)request;
String method = httpServletRequest.getMethod();
String contentType = httpServletRequest.getContentType()==null?"":httpServletRequest.getContentType();
//如果是POST请求并且不是文件上传
if(HttpMethod.POST.name().equals(method)&&!contentType.equals(MediaType.MULTIPART_FORM_DATA_VALUE)){
//重新生成ServletRequest 这个新的ServletRequest 获取流时会将流的数据重写进流里面
requestWrapper = new RequestReaderHttpServletRequestWrapper((HttpServletRequest) request);
}
}
if (requestWrapper == null) {
chain.doFilter(request, response);
} else {
chain.doFilter(requestWrapper, response);
}
}
@Override
public void init(FilterConfig arg0) throws ServletException {
System.out.println("init");
}
}
3 注册过滤器即可
@Configuration
public class WebMvcConfiguration {
@Bean
public FilterRegistrationBean setLogServiceFilter(){
FilterRegistrationBean<RequestBodyFilter> registrationBean = new FilterRegistrationBean<>();
RequestBodyFilter requestBodyFilter = new RequestBodyFilter();
registrationBean.setFilter(requestBodyFilter);
registrationBean.setName("过滤器处理请求body参数");
registrationBean.addUrlPatterns("/*");
registrationBean.setOrder(1);
return registrationBean;
}
}
上述操作完毕之后即可多次获取到request的body内容了。
注意事项
1 在网上查找资料的时候发现有资料说如果是上传文件会导致流的数据变多(本人未测试),所以这里进行了判断,同时如果是上传文件好像也没有什么情况会多次读取,所以就排除掉上传文件