InputStream流本身是单向的,无法重复读取,为了实现流的可重复读,需要对流进行装饰。但是对流的装饰,却引发了系统OOM
装饰方案:
public class AdBizTraceLogFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
ContentCachingRequestWrapper reqWrapper = getRequestWrapper(request);
response = getResponseWrapper(response);
try {
filterChain.doFilter(reqWrapper, response);
} finally {
// 业务层读取一次body后, 后续都通过getContentAsByteArray读取
String reqBody = new String(reqWrapper.getContentAsByteArray(), "UTF-8");
ContentCachingResponseWrapper respWrapper = (ContentCachingResponseWrapper) response;
String respBody = new String(respWrapper.getContentAsByteArray(), "UTF-8");
respWrapper.copyBodyToResponse();
}
}
private ContentCachingRequestWrapper getRequestWrapper(HttpServletRequest request) {
ContentCachingRequestWrapper requestWrapper;
//此处有bug,会导致大数据的请求或者响应对象的占用双份内存!!!
if (request instanceof ContentCachingRequestWrapper) {
requestWrapper = (ContentCachingRequestWrapper) request;
} else {
requestWrapper = new ContentCachingRequestWrapper(request);
}
return requestWrapper;
}
private ContentCachingResponseWrapper getResponseWrapper(HttpServletResponse response) {
ContentCachingResponseWrapper responseWrapper;
if (response instanceof ContentCachingResponseWrapper) {
responseWrapper = (ContentCachingResponseWrapper) response;
} else {
responseWrapper = new ContentCachingResponseWrapper(response);
}
return responseWrapper;
}
}
将流装饰成可重复读的流。但是如果系统存在大批量文件上传,需要做一下控制,判断contentType。
if (request.getContentLength() > MAX_CONTENT_LENGTH) {
PerfUtils.perf(LOG_NS, "ignorePath", "max length").logstash();
return true;
}
String contentType = request.getHeader("Content-Type");
if (contentType == null || !contentType.toLowerCase().contains("application/json")) {
PerfUtils.perf(LOG_NS, "ignorePath", "content type").logstash();
return true;
}
在新版的Spring中ContentCachingRequestWrapper中使用FastByteArrayOutputStream来取代ContentCachingRequestWrapper,不需要初始化ContentCachingRequestWrapper的时候就申请大块内存,lazy化的。
https://github.com/spring-projects/spring-framework/commit/f83c6094362b7e16fe08a4307cbcb0015e203d23