Java OpenResty Spring Spring Boot MySQL Redis MongoDB PostgreSQL Linux Android Nginx 面试 小程序 Arthas JVM AQS juc Kubernetes Docker DevOps


Spring 使用 filter 过滤器、ContentCachingWrapper 包装类获取请求参数和返回值

Spring Spring Boot 大约 7304 字

HttpServletRequest 获取异常

HttpServletRequest的流只能被读取一次,在filterInterceptor中调用了getInputStream()后,会报错:

DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotReadableException: I/O error while reading input message; nested exception is java.io.IOException: Stream closed]

或者

DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotReadableException: Required request body is missing: public com.example.filter.dto.Response com.example.filter.controller.HelloController.helloPost(com.example.filter.dto.ReqBody)]

ContentCachingWrapper

Spring提供的Servlet包装类。

ContentCachingRequestWrapper

ContentCachingRequestWrapper中缓存的内容需要在请求处理之后才能读取到。

ContentCachingRequestWrapper还可以限制请求字节数。

ContentCachingRequestWrapper cachingRequestWrapper = new ContentCachingRequestWrapper(req, 30) { // 限制30个字节
    @Override
    protected void handleContentOverflow(int contentCacheLimit) {
        throw new RuntimeException("over limit#" + contentCacheLimit);
    }
};

ContentCachingResponseWrapper

使用ContentCachingResponseWrapper包装过的HttpServletResponse,在使用完之后需要调用下copyBodyToResponse()方法。

说明

同样适用于@ControllerAdvice有全局捕获异常的场景。

示例代码

@Slf4j
@WebFilter(urlPatterns = {"/filter/*"})
@Order(Integer.MIN_VALUE)
public class MyOncePreRequestFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

        String requestMethod = request.getMethod();
        boolean shouldWrapMethod = Objects.equals(requestMethod, HttpMethod.PUT.name()) || Objects.equals(requestMethod, HttpMethod.POST.name());

        boolean isFirstRequest = !isAsyncDispatch(request);

        boolean shouldWrapRequest = isFirstRequest && !(request instanceof ContentCachingRequestWrapper) && shouldWrapMethod;
        HttpServletRequest requestToUse = shouldWrapRequest ? new ContentCachingRequestWrapper(request) : request;

        boolean shouldWrapResponse = !(response instanceof ContentCachingResponseWrapper) && shouldWrapMethod;
        HttpServletResponse responseToUse = shouldWrapResponse ? new ContentCachingResponseWrapper(response) : response;

        long startTime = System.currentTimeMillis();
        Throwable t = null;
        try {
            filterChain.doFilter(requestToUse, responseToUse);
        } catch (Exception e) {
            t = e;
            throw e;
        } finally {
            doSaveAccessLog(requestToUse, responseToUse, System.currentTimeMillis() - startTime, t);
        }

    }

    private void doSaveAccessLog(HttpServletRequest request, HttpServletResponse response, long useTime, Throwable t) {
        if (isAsyncStarted(request)) {
            copyResponse(response);
            return;
        }
        try {
            String requestUri = request.getRequestURI();
            String requestHeaders = getRequestHeaders(request);
            String requestParams = getRequestParams(request);
            String requestString = getRequestString(request);
            String responseString = getResponseString(response);
            int responseStatus = response.getStatus();

            List<String> logs = new ArrayList<>();
            logs.add("time=" + useTime + "ms");
            logs.add("uri=" + requestUri);
            logs.add("headers=" + requestHeaders);
            logs.add("status=" + responseStatus);
            logs.add("requestContentType=" + request.getContentType());
            logs.add("responseContentType=" + response.getContentType());
            logs.add("params=" + requestParams);
            logs.add("request=" + requestString);
            logs.add("response=" + responseString);
            if (t != null) {
                logs.add("exception=" + t.getCause().getMessage());
            }

            // TODO persist log
            log.info(String.join(",", logs));
        } catch (Throwable e) {
            log.error("got an exception when saving access log", e);
        } finally {
            copyResponse(response);
        }
    }

    private void copyResponse(HttpServletResponse response) {
        ContentCachingResponseWrapper wrapper = WebUtils.getNativeResponse(response, ContentCachingResponseWrapper.class);
        if (wrapper != null) {
            try {
                wrapper.copyBodyToResponse();
            } catch (IOException ignored) {
            }
        }
    }

    private String getRequestHeaders(HttpServletRequest request) {
        Enumeration<String> headerNames = request.getHeaderNames();
        List<String> headers = new ArrayList<>();
        while (headerNames.hasMoreElements()) {
            String key = headerNames.nextElement();
            headers.add(key + ':' + request.getHeader(key));
        }
        return '[' + String.join(",", headers) + ']';
    }

    private String getRequestParams(HttpServletRequest request) {
        Map<String, String[]> requestParams = new HashMap<>(request.getParameterMap());
        List<String> pairs = new ArrayList<>();
        if (!CollectionUtils.isEmpty(requestParams)) {
            for (Map.Entry<String, String[]> entry : requestParams.entrySet()) {
                String name = entry.getKey();
                String[] value = entry.getValue();
                if (value == null) {
                    pairs.add(name + "=");
                } else {
                    for (String v : value) {
                        pairs.add(name + "=" + v.trim());
                    }
                }
            }
        }
        String requestParamsStr = CollectionUtils.isEmpty(pairs) ? "" : String.join("&", pairs);
        if (Objects.equals(request.getContentType(), MediaType.APPLICATION_FORM_URLENCODED_VALUE)) {
            try {
                requestParamsStr = URLDecoder.decode(requestParamsStr, StandardCharsets.UTF_8.name());
            } catch (UnsupportedEncodingException ignore) {
            }
        }
        return requestParamsStr;
    }

    private String getRequestString(HttpServletRequest request) {
        ContentCachingRequestWrapper wrapper = WebUtils.getNativeRequest(request, ContentCachingRequestWrapper.class);
        if (wrapper != null) {
            try {
                byte[] buf = wrapper.getContentAsByteArray();
                return new String(buf, wrapper.getCharacterEncoding()).replaceAll("[\n\r]", "");
            } catch (UnsupportedEncodingException e) {
                return "[UNKNOWN]";
            }
        }
        return "";
    }

    private String getResponseString(HttpServletResponse response) {
        ContentCachingResponseWrapper wrapper = WebUtils.getNativeResponse(response, ContentCachingResponseWrapper.class);
        if (wrapper != null) {
            try {
                byte[] buf = wrapper.getContentAsByteArray();
                return new String(buf, wrapper.getCharacterEncoding()).replaceAll("[\n\r]", "");
            } catch (UnsupportedEncodingException e) {
                return "[UNKNOWN]";
            }
        }
        return "";
    }

}

参考

https://www.jb51.net/article/221738.htm

https://github.com/howardliu-cn/effective-spring/tree/main/spring-filter

阅读 158 · 发布于 2022-10-12

————        END        ————

Give me a Star, Thanks:)

https://github.com/fendoudebb

扫描下方二维码关注公众号和小程序↓↓↓

扫描二维码关注我
昵称:
随便看看 换一批