最近项目上有一个需求,在api上收到的请求,需要在springmvc转化成实体参数之前把request body读取出来记录日志。
在通常的响应流程上,使用了request.getInputStream()之后,流就会失效,即这个request body的流只能读取一次,这也是流本身的特性所致(当然,还有一种特殊的流——推回输入流PushbackInputStream)。
通过servlet api,可以通过继承 HttpServletRequestWrapper 这个类,在 filter 中 对 request 进行一次封装,再传进chain中,如此便可以实现日志的记录。
首先是 HttpServletRequestWrapper 的子类:
public class BodyReaderHttpServletRequestWrapper extends HttpServletRequestWrapper {
private final byte[] bytes;
public BodyReaderHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
try (BufferedInputStream bis = new BufferedInputStream(request.getInputStream());
ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
byte[] buffer = new byte[1024];
int len;
while ((len = bis.read(buffer)) > 0) {
baos.write(buffer, 0, len);
}
bytes = baos.toByteArray();
String body = new String(bytes);
System.out.println(body);
} catch (IOException ex) {
throw ex;
}
}
@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
return new ServletInputStream() {
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) {
}
public int read() throws IOException {
return byteArrayInputStream.read();
}
};
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(this.getInputStream()));
}
}
接下来是filter的实现
@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 {
if (request instanceof HttpServletRequest) {
ServletRequest requestWrapper = new BodyReaderHttpServletRequestWrapper((HttpServletRequest) request);
chain.doFilter(requestWrapper, response);
} else {
chain.doFilter(request, response);
}
}
@Override
public void destroy() {
}
}
此处是使用了spring boot的一个注解@WebFilter,在spring boot中,如果要扫描带@WebFilter的类,记得还要在Application入口类中加上@ServletComponentScan这个注解,才可以正常运行