ContentCachingRequestWrapper实现Re

2024-01-22  本文已影响0人  小胖学编程

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

上一篇下一篇

猜你喜欢

热点阅读