Spring boot请求/响应参数打印(修訂版)

原文鏈接:https://blog.csdn.net/jiahao1186/article/details/91870776

关键词:

请求参数,响应参数,过滤器,拦截器,日志过滤器,配置过滤器

一、目的:

对系统请求参数和响应参数,进行输出打印。支持表单提交json提交

二、过滤器与拦截器

  首先,了解一下过滤器和拦截器。两者功能很类似,但具体技术实现,相去甚远。对比两者的区别前,先理解一下AOP,AOP不是一种具体的技术,而是一种编程思想。在面向对象编程的过程中,我们很容易通过继承、多态来解决纵向扩展。 但是对于横向的功能,比如,在所有的service方法中开启事务,或者统一记录日志等功能,面向对象的是无法解决的。所以AOP——面向切面编程其实是面向对象编程思想的一个补充。而我们今天讲的过滤器和拦截器都属于面向切面编程的具体实现。而两者的主要区别包括以下几个方面:

  1、Filter是依赖于Servlet容器,属于Servlet规范的一部分;而拦截器则是独立存在的,可以在任何情况下使用。

  2、Filter的执行由Servlet容器回调完成;而拦截器通常通过动态代理的方式来执行。

  3、Filter的生命周期由Servlet容器管理;而拦截器则可以通过IoC容器来管理;因此,可以通过注入等方式来获取其他Bean的实例,因此使用会更方便。

三、日志过滤器_代码

这里使用了集成OncePerRequestFilter,具体原因参考链接文章。

SpringBoot基础篇(五)过滤器OncePerRequestFilter

package com.foo.web.filter;

import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.output.TeeOutputStream;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.jetty.server.Request;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.multipart.MultipartResolver;
import org.springframework.web.multipart.support.StandardServletMultipartResolver;

import javax.servlet.FilterChain;
import javax.servlet.ReadListener;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletOutputStream;
import javax.servlet.WriteListener;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import javax.validation.constraints.NotNull;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.Map;

/**
 * 系统请求/响应参数打印
 * 输出对应url 请求体,响应体,耗时
 *
 */
@Slf4j
public class LogFilter extends OncePerRequestFilter {

    private static final String DOT = ".";

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        long requestTime = System.currentTimeMillis();
        String url = ((Request) request).getOriginalURI();
        //静态资源 跳过
        if (request.getRequestURI().contains(DOT)) {
            filterChain.doFilter(request, response);
            return;
        }
        //打印请求头
        if(logger.isDebugEnabled()){
            Enumeration<String> headerNames = request.getHeaderNames();
            while (headerNames.hasMoreElements()) {
                String name = headerNames.nextElement();
                //根据名称获取请求头的值
                String value = request.getHeader(name);
                log.debug("请求URL:{},请求头:{}---{}" , url, name , value);
            }
        }
        //打印cookie值
        if(logger.isDebugEnabled()){
            Cookie[] cookies = request.getCookies();
            if(cookies != null && cookies.length > 0){
                for (Cookie cookie : cookies){
                    log.debug("请求URL:{},请求cookie: {}----{}", url, cookie.getName() , cookie.getValue());
                }
            }
        }

        //输出请求体
        String requestBody = StringUtils.EMPTY;
        String requestContentType = request.getHeader(HttpHeaders.CONTENT_TYPE);

        if (requestContentType != null) {
            //xml json
            if (requestContentType.startsWith(MediaType.APPLICATION_JSON_VALUE) || requestContentType.startsWith(MediaType.APPLICATION_XML_VALUE)) {
                requestBody = getRequestBody(request);
                final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(requestBody.getBytes(StandardCharsets.UTF_8));
                request = new HttpServletRequestWrapper(request) {
                    @Override
                    public ServletInputStream getInputStream() {
                        return new ByteArrayServletInputStream(byteArrayInputStream);
                    }
                };
                //普通表单提交
            } else if (requestContentType.startsWith(MediaType.APPLICATION_FORM_URLENCODED_VALUE)) {
                requestBody = getFormParam(request);
                //文件表单提交
            } else if (requestContentType.startsWith(MediaType.MULTIPART_FORM_DATA_VALUE)) {
                requestBody = getFileFormParam(request);
            }
        }

        final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        response = new HttpServletResponseWrapper(response) {
            @Override
            public ServletOutputStream getOutputStream() throws IOException {
                return new TeeServletOutputStream(super.getOutputStream(), byteArrayOutputStream);
            }
        };

        filterChain.doFilter(request, response);

        long costTime = System.currentTimeMillis() - requestTime;
        String responseBody = StringUtils.EMPTY;
        //暂定只有json 输出响应体
        String contentType = response.getHeader(HttpHeaders.CONTENT_TYPE);
        if (contentType != null && contentType.startsWith(MediaType.APPLICATION_JSON_VALUE)) {
            responseBody = byteArrayOutputStream.toString();
        }

        if (response.getStatus() >= HttpStatus.OK.value() && response.getStatus() < HttpStatus.MULTIPLE_CHOICES.value()) {
            log.info("请求URL:{}, total time:{} ms, responseCode:{}, requestBody:{}, responseBody:{}", url, costTime, response.getStatus(), requestBody, responseBody);
        } else {
            log.error("请求URL:{}, total time:{} ms, responseCode:{}, requestBody:{}, responseBody:{}", url, costTime, response.getStatus(), requestBody, responseBody);
        }
    }

    private String getRequestBody(HttpServletRequest request) {
        int contentLength = request.getContentLength();
        if (contentLength <= 0) {
            return "";
        }
        try {
            return IOUtils.toString(request.getReader());
        } catch (IOException e) {
            log.error("获取请求体失败", e);
            return "";
        }
    }

    private String getFormParam(HttpServletRequest request) {
        Map<String, Object> param = Maps.newHashMapWithExpectedSize(16);
        Map<String, String[]> parameterMap = request.getParameterMap();
        if (!parameterMap.isEmpty()) {
            parameterMap.forEach((k, v) -> param.put(k, Lists.newArrayList(v)));
        }
        return toForm(param);
    }

    private String getFileFormParam(HttpServletRequest request) {
        MultipartResolver resolver = new StandardServletMultipartResolver();
        MultipartHttpServletRequest mRequest = resolver.resolveMultipart(request);

        Map<String, Object> param = Maps.newHashMapWithExpectedSize(8);
        Map<String, String[]> parameterMap = mRequest.getParameterMap();
        if (!parameterMap.isEmpty()) {
            parameterMap.forEach((k, v) -> param.put(k, Lists.newArrayList(v)));
        }
        MultiValueMap<String, MultipartFile> multiFileMap = mRequest.getMultiFileMap();
        if (!multiFileMap.isEmpty()) {
            multiFileMap.forEach((k, v) -> param.put(k,
                v.stream().map(file -> file.getOriginalFilename() + "(" + file.getSize() + " byte)")
                    .collect(Collectors.toList())));
        }
        return toForm(param);
    }

    private static String toForm(Map<String, Object> params) {
        return Joiner.on("&").withKeyValueSeparator("=").join(params);
    }

    /**
     * @author foo
     */
    static class ByteArrayServletInputStream extends ServletInputStream {

        private ByteArrayInputStream byteArrayInputStream;

        public ByteArrayServletInputStream(ByteArrayInputStream byteArrayInputStream) {
            this.byteArrayInputStream = byteArrayInputStream;
        }

        @Override
        public int read() {
            return byteArrayInputStream.read();
        }

        @Override
        public boolean isFinished() {
            return false;
        }

        @Override
        public boolean isReady() {
            return false;
        }

        @Override
        public void setReadListener(ReadListener readListener) {
            // default implementation ignored
        }
    }

    /**
     * @author foo
     */
    static class TeeServletOutputStream extends ServletOutputStream {

        private final TeeOutputStream teeOutputStream;

        public TeeServletOutputStream(OutputStream one, OutputStream two) {
            this.teeOutputStream = new TeeOutputStream(one, two);
        }

        @Override
        public void write(@NotNull byte[] b) throws IOException {
            this.teeOutputStream.write(b);
        }

        @Override
        public void write(@NotNull byte[] b, int off, int len) throws IOException {
            this.teeOutputStream.write(b, off, len);
        }

        @Override
        public void write(int b) throws IOException {
            this.teeOutputStream.write(b);
        }

        @Override
        public void flush() throws IOException {
            super.flush();
            this.teeOutputStream.flush();
        }

        @Override
        public void close() throws IOException {
            super.close();
            this.teeOutputStream.close();
        }

        @Override
        public boolean isReady() {
            return false;
        }

        @Override
        public void setWriteListener(WriteListener writeListener) {
            // default implementation ignored
        }
    }
}

请留意代码:filterChain.doFilter(request, response);

即在方法执行前先记录时间戳,以及请求参数;然后,通过过滤器链完成请求的执行;在此次之后,收集响应参数,在返回结果之间计算执行打印。

四、配置过滤器_代码

package com.foo.bar.config;

import com.foo.bar.filter.LogFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * @author foo
 */
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

  @Bean
  public FilterRegistrationBean<LogFilter> logFilter() {
    final FilterRegistrationBean<LogFilter> filterRegistrationBean = new FilterRegistrationBean<>();
    final LogFilter logFilter = new LogFilter();
    filterRegistrationBean.setFilter(logFilter);
    filterRegistrationBean.setName("logFilter");
    filterRegistrationBean.addUrlPatterns("/*");
    filterRegistrationBean.setOrder(1);
    return filterRegistrationBean;
  }

}

参考:

https://www.cnblogs.com/paddix/p/8365558.html

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值