Filter 记录日志(Get Post 参数)

Posted 北冥鱼_

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Filter 记录日志(Get Post 参数)相关的知识,希望对你有一定的参考价值。

一、拦截器介绍
拦截器(Interceptor)介绍

拦截器(Interceptor)同 Filter 过滤器一样,它俩都是面向切面编程——AOP 的具体实现(AOP切面编程只是一种编程思想而已)。
你可以使用 Interceptor 来执行某些任务,例如在 Controller 处理请求之前编写日志,添加或更新配置……
在 Spring 中,当请求发送到 Controller 时,在被 Controller 处理之前,它必须经过 Interceptors(0或多个)。
Spring Interceptor 是一个非常类似于Servlet Filter 的概念 。

Interceptor 作用
  1. 日志记录:记录请求信息的日志,以便进行信息监控、信息统计、计算 PV(Page View)等;
  2. 权限检查:如登录检测,进入处理器检测是否登录;
  3. 性能监控:通过拦截器在进入处理器之前记录开始时间,在处理完后记录结束时间,从而得到该请求的处理时间。(反向代理,如 Apache 也可以自动记录)
  4. 通用行为:读取 Cookie 得到用户信息并将用户对象放入请求,从而方便后续流程使用,还有如提取 Locale、Theme 信息等,只要是多个处理器都需要的即可使用拦截器实现。
自定义 Interceptor

自定义 Interceptor ,需要实现 org.springframework.web.servlet.HandlerInterceptor接口或继承 org.springframework.web.servlet.handler.HandlerInterceptorAdapter类,并且重写以下 3 个方法:

  1. preHandler(HttpServletRequest request, HttpServletResponse response, Object handler) 方法在请求处理之前被调用。该方法在 Interceptor 类中最先执行,用来进行一些前置初始化操作或是对当前请求做预处理,也可以进行一些判断来决定请求是否要继续进行下去。该方法的返回至是 Boolean 类型,当它返回 false 时,表示请求结束,后续的 Interceptor 和 Controller 都不会再执行;当它返回为 true 时会继续调用下一个 Interceptor 的 preHandle 方法,如果已经是最后一个 Interceptor 的时候就会调用当前请求的 Controller 方法。
  2. postHandler(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) 方法在当前请求处理完成之后,也就是 Controller 方法调用之后执行,但是它会在 DispatcherServlet 进行视图返回渲染之前被调用,所以我们可以在这个方法中对 Controller 处理之后的 ModelAndView 对象进行操作。
  3. afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handle, Exception ex) 方法需要在当前对应的 Interceptor 类的 preHandle 方法返回值为 true 时才会执行。顾名思义,该方法将在整个请求结束之后,也就是在 DispatcherServlet 渲染了对应的视图之后执行。此方法主要用来进行资源清理。
过滤器 Filter 介绍
Filter 介绍

Filter 是一个可以复用的代码片段,可以用来转换HTTP请求、响应和头信息。Java中的 Filter 主要用于对HttpServletRequest 进行预处理,也可以对HttpServletResponse 进行后处理,是个典型的处理链。

过滤链的好处是,执行过程中任何时候都可以被打断,只要不执行chain.doFilter()就不会再执行后面的请求内容。实际使用时,需要特别注意过滤器的执行顺序:

Filter 作用
  1. 在 HttpServletRequest 到达Servlet 之前,拦截客户的HttpServletRequest
  2. 根据需要检查 HttpServletRequest ,可以修改HttpServletRequest 头和数据
  3. 在 HttpServletResponse 到达客户端之前,拦截HttpServletResponse
  4. 根据需要检查 HttpServletResponse ,可以修改HttpServletResponse 头和数据
自定义 Filter

所有的 Servlet 过滤器类都必须实现 javax.servlet.Filter 接口

Interceptor 、Filter 区别
  1. Filter 流程是线性的, url 传来之后,检查之后,可保持原来的流程继续向下执行,被下一个 Filter, servle t接收等,而servlet 处理之后,不会继续向下传递。Filter 功能可用来保持流程继续按照原来的方式进行下去,或者主导流程,而servlet的功能主要用来主导流程。
    Filter 可用来进行字符编码的过滤,检测用户是否登陆的过滤,禁止页面缓存等

  2. interceptor 拦截器,类似于filter,不过在struts.xml中配置,不是在 web.xml,并且不是针对 URL 的,而是针对 action,当页面提交 action 时,进行过滤操作,相当于 struts1.x 提供的 plug-in 机制,可以看作,前者是 struts1.x 自带的filter,而 interceptor 是 struts2 提供的 filter.
    与 filter 不同点:
    1). 不在 web.xml中 配置,而是在 struts.xml 中完成配置,与action 在一起
    2).可由 action 自己指定用哪个interceptor 来在接收之前做事

为什么使用 Filter 而不是拦截器

这是因为在 Filter 中获取网络请求的入参、出参更方便:

通过重写 HttpServletRequestWrapper 类 获 getInputStream中的流数据,然后在将body数据进行重新写入传递下去。

  1. GET 传递
    参数可以直接通过request.getParameter获取。

  2. Post 传递
    因为(request.getInputStream()只能够读取一次),post参数不能直接从request.getInputStream() 读取,必须要进行「response包装」重新写。

一个实际例子

1、request 参数包装:

MyRequestWrapper.java:

package com.ruoyi.business.Log;


import lombok.extern.slf4j.Slf4j;


import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;


/**
 * @description: MyRequestWrapper
 * @author: chang
 * @create: 2019-07-24 11:15
 **/
@Slf4j
public class MyRequestWrapper extends HttpServletRequestWrapper 

    private Map<String, String[]> params = new HashMap<>();
    private String body;
    private byte[] buffer;

    /**
     * Constructs a request object wrapping the given request.
     *
     * @param request
     * @throws IllegalArgumentException if the request is null
     */
    public MyRequestWrapper(HttpServletRequest request) throws IOException 
        super(request);

        StringBuilder stringBuilder = new StringBuilder();
        BufferedReader bufferedReader = null;
        try 
            InputStream inputStream = request.getInputStream();
            if (inputStream != null) 
                bufferedReader = new BufferedReader(new InputStreamReader(inputStream,"UTF-8"));
                char[] charBuffer = new char[128];
                int bytesRead = -1;
                while ((bytesRead = bufferedReader.read(charBuffer)) > 0) 
                    stringBuilder.append(charBuffer, 0, bytesRead);
                
             else 
                stringBuilder.append("");
            
         catch (IOException ex) 
            throw ex;
         finally 
            if (bufferedReader != null) 
                try 
                    bufferedReader.close();
                 catch (IOException ex) 
                    throw ex;
                
            
        
        body = stringBuilder.toString();
    

    public String getBody() 
        return this.body;
    

    public ServletInputStream getInputStream() 
        return new BufferedServletInputStream(this.buffer);
    

    /**
     * 重载构造方法
     */
    public MyRequestWrapper(HttpServletRequest request, Map<String, Object> extendParams) throws IOException 
        this (request);
        //这里将扩展参数写入参数表
        addAllParameters(extendParams);
    

    /**
     * 重写getParameter方法
     *
     * @param name 参数名
     * @return 返回参数值
     */
    @Override
    public String getParameter(String name) 
        String[] values = params.get(name);
        if (values == null || values.length == 0) 
            return null;
        
        return values[0];
    

    @Override
    public String[] getParameterValues(String name) 
        String[] values = params.get(name);
        if (values == null || values.length == 0) 
            return null;
        
        return values;
    

    /**
     * 增加多个参数
     *
     * @param otherParams 增加的多个参数
     */
    public void addAllParameters(Map<String, Object> otherParams) 
        for (Map.Entry<String, Object> entry : otherParams.entrySet()) 
            addParameter(entry.getKey(), entry.getValue());
        
    

    /**
     * 将parameter的值去除空格后重写回去
     */
    public void modifyParameterValues() 
        Set<String> set = params.keySet();
        Iterator<String> it = set.iterator();
        while (it.hasNext()) 
            String key = (String) it.next();
            String[] values = params.get(key);
            values[0] = values[0].trim();
            params.put(key, values);
        
    

    /**
     * 增加参数
     *
     * @param name  参数名
     * @param value 参数值
     */
    public void addParameter(String name, Object value) 
        if (value != null) 
            if (value instanceof String[]) 
                params.put(name, (String[]) value);
             else if (value instanceof String) 
                params.put(name, new String[](String) value);
             else 
                params.put(name, new String[]String.valueOf(value));
            
        
    

2、response参数包装:

HttpServletResponseWrapper.java:

package com.ruoyi.business.Log;

import javax.servlet.ServletOutputStream;
import javax.servlet.WriteListener;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import java.io.ByteArrayOutputStream;
import java.io.IOException;

/**
 * @description: 返回值输出代理类
 * @author: chang
 * @create: 2019-06-12 09:28
 **/
public class EcaResponseWrapper extends HttpServletResponseWrapper 

    private ByteArrayOutputStream buffer;
    private ServletOutputStream out;

    public EcaResponseWrapper(HttpServletResponse response) 
        super(response);
        buffer = new ByteArrayOutputStream();
        out = new WrapperOutputStream(buffer);
    

    @Override
    public ServletOutputStream getOutputStream()
            throws IOException
    
        return out;
    

    @Override
    public void flushBuffer()
            throws IOException
    
        if (out != null)
        
            out.flush();
        
    

    public byte[] getContent()
            throws IOException
    
        flushBuffer();
        return buffer.toByteArray();
    

    class WrapperOutputStream extends ServletOutputStream
    
        private ByteArrayOutputStream bos;

        public WrapperOutputStream(ByteArrayOutputStream bos)
        
            this.bos = bos;
        

        @Override
        public void write(int b)
                throws IOException
        
            bos.write(b);
        

        @Override
        public boolean isReady()
        
            // TODO Auto-generated method stub
            return false;
        

        @Override
        public void setWriteListener(WriteListener arg0)
        
            // TODO Auto-generated method stub
        
    


3、辅助参数类:

BufferedServletInputStream.java

package com.ruoyi.business.Log;

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;

/**
 * @description: 自定义输入流
 * @author: chang
 * @create: 2021-10-27 10:27
 **/
public class BufferedServletInputStream extends ServletInputStream 

    private ByteArrayInputStream sourceStream;
    private boolean finished = false;

    public BufferedServletInputStream(byte[] buffer)
        this.sourceStream = new ByteArrayInputStream(buffer);
    

    public final InputStream getSourceStream() 
        return this.sourceStream;
    

    @Override
    public int read() throws IOException 
        int data = this.sourceStream.read();
        if (data==-1)
            this.finished = true;
        
        return data;
    

    @Override
    public int available() 
        return this.sourceStream.available();
    

    @Override
    public void close() throws IOException 
        super.close();
        this.sourceStream.close();
    

    @Override
    public boolean isFinished() 
        return this.finished;
    

    @Override
    public boolean isReady() 
        return true;
    

    @Override
    public void setReadListener(ReadListener readListener) 
        throw new UnsupportedOperationException();
    

4、过滤器Filter:

EcaResponseFilter.java

package com.ruoyi.business.Log;

import com.alibaba.fastjson.JSONObject;
import com.google.gson.Gson;
import com.ruoyi.common.utils.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Enumeration;
import java.util.TreeMap;

/**
 * @description: 返回值输出过滤器,修改返回值加密方式
 * @author: chang
 * @create: 2019-06-12 09:38
 **/
@WebFilter(urlPatterns = "/*", filterName = "EcaResponseFilter")
public class EcaResponseFilter implements Filter

    protected final Logger logger = LoggerFactory.getLogger(this.getClass());
    private static final Gson gson = new Gson();

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
            throws IOException, ServletException 

        HttpServletRequest req = (HttpServletRequest) request;
        String requestUri = req.getRequestURI();
        // 过滤不需记录日志的链接
        if (requestUri.contains(".js")||requestUri.contains(".css")||requestUri.contains(".ico")||requestUri.contains("/fonts/"))
            filterChain.doFilter(request, response);
        else 
            StringBuilder parameterSb = new StringBuilder(2048);
            Enumeration<String> parameterNames = request.getParameterNames();
            while (parameterNames.hasMoreElements()) 
                String key = parameterNames.nextElement();
                String value = request.getParameter(key);
                parameterSb.append(key+"="+value+", ");
            
            String sb = parameterSb.toString();
            if (StringUtils.isNotEmpty(sb))
                logger.info("responseFlow requestUri == "+requestUri+" 在filter中打印的 get 请求参数:"+ "\\n" +""+sb+"");
            
            // 处理请求流程
            String result = requestFlow(request, response, filterChain);
            // 处理返回流程
            responseFlow(request, response, result);
        
    

    @Override
    public void init(FilterConfig arg0)
            throws ServletException 
    

    @Override
    public void destroy() 
    

    public String requestFlow(ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException 

        HttpServletRequest req = (HttpServletRequest) request;
        String requestUri = req.getRequestURI();
        MyRequestWrapper requestWrapper = new MyRequestWrapper(req);

        /**
         * 获取 POST 请求参数
         */
        TreeMap paramsMaps = new TreeMap();
        if ("POST".equals(req.getMethod().toUpperCase())) 
            String body = requestWrapper.getBody();
            if (StringUtils.isNotEmpty(body))
                try 
                    paramsMaps = JSONObject.parseObject(body, TreeMap.class);
                    logger.info("responseFlow requestUri == "+requestUri+" 在filter中打印的 post 请求参数:"+ "\\n" +paramsMaps.toString());
                catch (Exception e)
                    logger.error("JSONObject.parseObject error: requestUri= "+requestUri);
                
            
        

        EcaResponseWrapper wrapper = new EcaResponseWrapper((HttpServletResponse) response);
        // 这句话非常重要,注意看到第二个参数是我们的包装器而不是response
        try 
            long bgn = System.currentTimeMillis();
            filterChain.doFilter(requestWrapper, wrapper);
            long end = System.currentTimeMillis();
            try 
                byte[] content = wrapper.getContent();//获取返回值
                //判断是否有值
                if (content.length > 0)
                
                    String result = new String(content, "UTF-8");
                    logger.info("responseFlow requestUri == "+requestUri+" 在filter中打印的返回参数 result :"+ "\\n" +result);
                    logger.info("filter中记录 "+(requestUri)+" 耗时:"+(end-bgn)+"ms");
                    return result;
                
            catch (Exception e)
                String stackStr = toStackTrace(e);
                logger.info("wrapper.getContent() 获取返回值 error: "+stackStr);
            
         catch (Exception e) 
            String stackStr = toStackTrace(e);
            logger.info("requestFlow Exception:  "+stackStr);
        
        return "";
    

    public void responseFlow(ServletRequest request, ServletResponse response, String result) throws IOException 
        String responseStr = result;
        // 输出最终的结果 --> 做完处理之后再把这个值返回回去
       response.getOutputStream().write(responseStr.getBytes());

    

    /**
     * 获取堆栈信息
     * @param e
     * @return
     */
    public String toStackTrace(Exception e) 
        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw);
        try 
            e.printStackTrace(pw);
            String result = sw.toString().substring(0,1600);
            return result;
         catch (Exception var4) 
            return " var4";
        
    

5、过滤器嵌入项目配置:

MyAdapter.java

package com.ruoyi.business.Log;

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.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class MyAdapter implements WebMvcConfigurer 
    @Bean
    public LogInterceptor customInterceptor() 
        return new LogInterceptor();
    
    /**
     * Description:重写增加自定义拦截器的注册,某一个拦截器需要先注册进来,才能工作
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) 

        // 添加拦截器
        // registry.addInterceptor(customInterceptor()).addPathPatterns("/**");
    

    @Bean
    public FilterRegistrationBean someFilterRegistration() 
        FilterRegistrationBean registration = new FilterRegistrationBean();
        // 配置一个返回值加密过滤器
        registration.setFilter(new EcaResponseFilter());
        registration.addUrlPatterns("/*");
        registration.addInitParameter("paramName", "paramValue");
        registration.setName("responseFilter");
        return registration;
    

参考链接:

  1. Spring Boot拦截器(Interceptor)详解
  2. Servlet之Filter深入讲解及实例研究
  3. servlet/filter/listener/interceptor区别与联系

以上是关于Filter 记录日志(Get Post 参数)的主要内容,如果未能解决你的问题,请参考以下文章

Filter 记录日志(Get Post 参数)

Filter 记录日志(Get Post 参数)

GET和POST的区别?

在 Apache 中记录 POST 数据的最佳方式?

get和post提交方式的差别

统一日志输出打印POST请求参数