优雅的记录http请求或响应的数据

栏目: 后端 · 前端 · 发布时间: 6年前

内容简介:经常会遇到需要处理http请求以及响应body的场景。而这里比较大的一个问题是servlet的requestBody或responseBody流一旦被读取了。就无法二次读取了。针对这个问题,spring本身提供了解决方案,即ContentCachingRequestWrapper/ContentCachingResponseWrapper。我们编写一个过滤器:这样自定义一个filter继承HttpBodyRecorderFilter,重写recordBody方法就能自定义自己的处理逻辑了。另外,record

经常会遇到需要处理http请求以及响应body的场景。而这里比较大的一个问题是servlet的requestBody或responseBody流一旦被读取了。就无法二次读取了。针对这个问题,spring本身提供了解决方案,即ContentCachingRequestWrapper/ContentCachingResponseWrapper。

我们编写一个过滤器:

public abstract class HttpBodyRecorderFilter extends OncePerRequestFilter {
    private static final int DEFAULT_MAX_PAYLOAD_LENGTH = 1024 * 512;

    private int maxPayloadLength = DEFAULT_MAX_PAYLOAD_LENGTH;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        if (this.codeMatched(response.getStatus(), recordCode())) {
            boolean isFirstRequest = !isAsyncDispatch(request);
            HttpServletRequest requestToUse = request;

            if (isFirstRequest
                    && !(request instanceof ContentCachingRequestWrapper)
                    && (request.getMethod().equals(HttpMethod.PUT.name()) || request.getMethod().equals(HttpMethod.POST.name()))) {
                requestToUse = new ContentCachingRequestWrapper(request);
            }

            HttpServletResponse responseToUse = response;
            if (!(response instanceof ContentCachingResponseWrapper)) {
                responseToUse = new ContentCachingResponseWrapper(response);
            }

            try {
                filterChain.doFilter(requestToUse, responseToUse);
            } finally {
                if (!isAsyncStarted(requestToUse)) {
                    recordBody(createRequest(requestToUse), createResponse(responseToUse));
                }
            }
        } else {
            filterChain.doFilter(request, response);
        }
    }

    protected String createRequest(HttpServletRequest request) {
        String payload = "";
        ContentCachingRequestWrapper wrapper =
                WebUtils.getNativeRequest(request, ContentCachingRequestWrapper.class);
        if (wrapper != null) {
            byte[] buf = wrapper.getContentAsByteArray();
            payload = genPayload(payload, buf, wrapper.getCharacterEncoding());
        }

        return payload;
    }

    protected String createResponse(HttpServletResponse resp) {
        String response = "";
        ContentCachingResponseWrapper wrapper = WebUtils.getNativeResponse(resp, ContentCachingResponseWrapper.class);
        if (wrapper != null) {
            byte[] buf = wrapper.getContentAsByteArray();
            try {
                wrapper.copyBodyToResponse();
            } catch (IOException e) {
                e.printStackTrace();
            }
            response = genPayload(response, buf, wrapper.getCharacterEncoding());
        }
        return response;
    }

    private String genPayload(String payload, byte[] buf, String characterEncoding) {
        if (buf.length > 0 && buf.length < getMaxPayloadLength()) {
            try {
                payload = new String(buf, 0, buf.length, characterEncoding);
            } catch (UnsupportedEncodingException ex) {
                payload = "[unknown]";
            }
        }
        return payload;
    }

    public int getMaxPayloadLength() {
        return maxPayloadLength;
    }

    private boolean codeMatched(int responseStatus, String statusCode) {
        if (statusCode.matches("^[0-9,]*$")) {
            String[] filteredCode = statusCode.split(",");
            return Stream.of(filteredCode).map(Integer::parseInt).collect(Collectors.toList()).contains(responseStatus);
        } else {
            return false;
        }
    }

    protected abstract void recordBody(String payload, String response);

    protected abstract String recordCode();
}

这样自定义一个filter继承HttpBodyRecorderFilter,重写recordBody方法就能自定义自己的处理逻辑了。另外,recordCode可用于定义在请求响应码为多少的时候才会去记录body,例如可以定义为只有遇到400或500时才记录body,用于错误侦测。

过滤器的匹配规则比较简单,如果想要像springmvc那样进行匹配,我们可以使用 AntPathMatcher

class PatternMappingFilterProxy implements Filter {
    private final Filter delegate;
    private final List<String> pathUrlPatterns = new ArrayList();

    private PathMatcher pathMatcher;

    public PatternMappingFilterProxy(Filter delegate, String... urlPatterns) {
        Assert.notNull(delegate, "A delegate Filter is required");
        this.delegate = delegate;
        int length = urlPatterns.length;
        pathMatcher = new AntPathMatcher();

        for (int index = 0; index < length; ++index) {
            String urlPattern = urlPatterns[index];
            this.pathUrlPatterns.add(urlPattern);
        }

    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        String path = httpRequest.getRequestURI();
        if (this.matches(path)) {
            this.delegate.doFilter(request, response, filterChain);
        } else {
            filterChain.doFilter(request, response);
        }

    }

    private boolean matches(String requestPath) {
        for (String pattern : pathUrlPatterns) {
            if (pathMatcher.match(pattern, requestPath)) {
                return true;
            }
        }
        return false;
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        this.delegate.init(filterConfig);
    }

    @Override
    public void destroy() {
        this.delegate.destroy();
    }

    public List<String> getPathUrlPatterns() {
        return pathUrlPatterns;
    }

    public void setPathUrlPatterns(List<String> urlPatterns) {
        pathUrlPatterns.clear();
        pathUrlPatterns.addAll(urlPatterns);
    }
}

这样子,PatternMappingFilterProxy装饰了真正的HttpBodyRecorderFilter,支持传入urlPatterns,从而实现像springmvc那样的ant style的匹配。例如对于以下接口:

   @PostMapping("/test/{id}")
    public Object test(@PathVariable(value = "id",required = true) final Integer index) {
        //do something
    }

可以设置urlPattern为 /test/{id:[0-9]+}

以上代码存在于 httpBodyRecorder

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

The Everything Store

The Everything Store

Brad Stone / Little, Brown and Company / 2013-10-22 / USD 28.00

The definitive story of Amazon.com, one of the most successful companies in the world, and of its driven, brilliant founder, Jeff Bezos. Amazon.com started off delivering books through the mail. Bu......一起来看看 《The Everything Store》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

SHA 加密
SHA 加密

SHA 加密工具

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具