美文网首页
Spring在Filter中记录Web请求Request和返回R

Spring在Filter中记录Web请求Request和返回R

作者: 南瓜慢说 | 来源:发表于2023-02-05 20:18 被阅读0次

    1 简介

    Spring MVC中,我们有时需要记录一下请求和返回的内容,方便出现问题时排查。比较Header、Request Body等。这些在Controller也可以记录,但在Filter中会更方便。而我们使用的是OncePerRequestFilter

    2 记录请求

    2.1 流重复读的问题

    可以通过下面的代码来读取请求Body:

    byte[] requestBody = StreamUtils.copyToByteArray(request.getInputStream());
    log.info("request body = {}", new String(requestBody, StandardCharsets.UTF_8));
    

    但是这里从流读取了一次内容后,后续不可再读了。这就造成了真正处理请求的时候,报错失败,我们需要把Request对象改造成可重复读的类。

    2.2 通过Wrapper解决流重复读的问题

    为了可以让流重复读,加了以下Wrapper:

    public class PkslowRequestWrapper extends HttpServletRequestWrapper {
        private final byte[] body;
        public PkslowRequestWrapper(HttpServletRequest request) throws IOException {
            super(request);
            body = StreamUtils.copyToByteArray(request.getInputStream());
        }
    
        @Override
        public ServletInputStream getInputStream() throws IOException {
            ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body);
            return new ServletInputStream() {
                @Override
                public int read() throws IOException {
                    return byteArrayInputStream.read();
                }
    
                @Override
                public boolean isFinished() {
                    return true;
                }
    
                @Override
                public boolean isReady() {
                    return true;
                }
    
                @Override
                public void setReadListener(ReadListener readListener) {
    
                }
            };
        }
    
        @Override
        public BufferedReader getReader() throws IOException {
            return new BufferedReader(new InputStreamReader(getInputStream()));
        }
    }
    

    这里主要在构造时读了流,然后存在变量body里,每次返回流的时候从body构造回去即可。

    在Filter中使用这个Wrapper如下:

    PkslowRequestWrapper request = new PkslowRequestWrapper(req);
    ServletInputStream servletInputStream = request.getInputStream();
    String body = StreamUtils.copyToString(servletInputStream, Charset.defaultCharset());
    log.info("Request Body(PkslowRequestWrapper): {}", body);
    

    2.3 内置Filter

    其实,针对Request,Spring Boot提供了内置的Filter可以直接记录请求,使用如下:

    package com.pkslow.springboot.common.web.config;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.filter.CommonsRequestLoggingFilter;
    
    @Configuration
    public class PkslowConfig {
        @Bean
        public CommonsRequestLoggingFilter loggingFilter() {
            CommonsRequestLoggingFilter filter = new CommonsRequestLoggingFilter();
            filter.setIncludeHeaders(true);
            filter.setIncludeClientInfo(true);
            filter.setIncludePayload(true);
            filter.setIncludeQueryString(true);
    
            filter.setAfterMessagePrefix("CommonsRequestLoggingFilter Request: ");
    
            return filter;
        }
    }
    

    但要开debug级别的日志才会打出来。

    logging:
      level:
        root: debug
    

    日志如下:

    DEBUG 20356 --- [nio-8080-exec-1] o.s.w.f.CommonsRequestLoggingFilter      : Before request [POST /hello/pkslow, client=127.0.0.1, headers=[authorization:"Basic xxxxxx", content-length:"37", host:"localhost:8080", connection:"Keep-Alive", user-agent:"Apache-HttpClient/4.5.13 (Java/17.0.5)", accept-encoding:"gzip,deflate", Content-Type:"application/json;charset=UTF-8"]]
    

    3 记录返回

    返回也是一样,有流不可重复读的问题,使用Spring自带的ContentCachingResponseWrapper即可。

    ContentCachingResponseWrapper response = new ContentCachingResponseWrapper(res);
    log.info("Response Code: {}", response.getStatus());
    String responseBody = new String(response.getContentAsByteArray(), response.getCharacterEncoding());
    log.info("Response Body: {}", responseBody);
    response.copyBodyToResponse();
    

    特别注意一定要调用copyBodyToResponse()这个方法,不然无法返回body给请求端了。

    4 记录时间

    记录整个请求的处理时间请参考: Java如何测量方法执行时间

    5 测试

    测试一下:

    POST http://localhost:8080/hello/pkslow
    Content-Type: application/json
    Authorization: Basic xxxxxx
    
    {
      "id": 999,
      "value": "content"
    }
    

    执行日志结果如下:

    6 总结

    也可使用ContentCachingRequestWrapper来解决请求流不可重复读的问题,但这个Wrapper是有限制的,具体可以看它源码。也有人提了Issue

    代码请看GitHub: https://github.com/LarryDpk/pkslow-samples

    相关文章

      网友评论

          本文标题:Spring在Filter中记录Web请求Request和返回R

          本文链接:https://www.haomeiwen.com/subject/hfjzhdtx.html