springboot项目,构建可重复读取inputStream的request, 创建RepeatedlyRequestWrapper类

2022-10-07 08:13:55 浏览数 (1)

目录

  • 1 问题
  • 2 解决
    • 2.1 解决方法一
    • 2.2 解决方法二

1 问题

代码语言:javascript复制
如果使用原生的 HttpServletRequest ,只能读取一次,
如果想要二次读取就会报错。 因此需要能够重复读取 
InputStream 的方法。

springboot项目 HttpServletRequest 
getRequest().getInputStream()或getReader()
只能读取一次read closed

我在项目中使用controller中方法进行接收数据的时候需要加上
@RequestBody注解接收这种数据,使用POJO对象进行接收,
但是我的项目需要为这个方法增加LogAspect的功能,
这就要求我需要获取到RequestBody中的数据,
但是一个request数据只能读取一次,在方法内部已经读取过了,不能在LogAspect中再次读取

(试过Filter的方法,但是会对原项目中的Filter有冲突)

2 解决

2.1 解决方法一

在Controller的方法中增加参数HttpServletRequest request,使用request.setAttribute()将对象重新放入到request中,在Aspect中使用getAttribute()进行获取就可以避免getInputStream()这个方法出bug了

这种方法很简单,就是使用了之后再赋值回去; 就是从request拿出东西使用之后,再将拿出来的东西放到request里面

2.2 解决方法二

使用过滤器,重写HttpServletRequest ,里面增加缓冲,记录已读取的内容。

具体实现方法是

1 写一个过滤器类

里面的代码是

代码语言:javascript复制
package com.ruoyi.common.filter;

import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import org.springframework.http.MediaType;
import com.ruoyi.common.utils.StringUtils;

/**
 * Repeatable 过滤器
 * 构建可重复读取inputStream的request
 * @author jing
 */
public class RepeatableFilter implements Filter
{
    @Override
    public void init(FilterConfig filterConfig) throws ServletException
    {

    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException
    {
        ServletRequest requestWrapper = null;
//        APPLICATION_JSON_VALUE = "application/json";
        if (request instanceof HttpServletRequest
                && StringUtils.startsWithIgnoreCase(request.getContentType(), MediaType.APPLICATION_JSON_VALUE))
        {
            requestWrapper = new RepeatedlyRequestWrapper((HttpServletRequest) request, response);
        }
        if (null == requestWrapper)
        {
            chain.doFilter(request, response);
        }
        else
        {
            chain.doFilter(requestWrapper, response);
        }
    }

    @Override
    public void destroy()
    {

    }
}

在这个过滤器里面就会判断,看request请求是不是json,如果是,就将里面的东西进行缓存,不是就直接放行

缓存的逻辑是重新写在另一个文件里面

代码语言:javascript复制
package com.ruoyi.common.filter;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import com.ruoyi.common.utils.http.HttpHelper;

/**
 * 构建可重复读取inputStream的request
 * 如果使用原生的 HttpServletRequest ,
 * 只能读取一次,如果想要二次读取就会报错。 因此需要能够重复读取 InputStream 的方法。
 * request的inputStream只能被读取一次,
 * 多次读取将报错,那么如何才能重复读取呢?答案之一是:增加缓冲,记录已读取的内容。
 * @author ruoyi
 */
public class RepeatedlyRequestWrapper extends HttpServletRequestWrapper
{
//    将request 里面的东西 缓存到这个数组里面
    private final byte[] body;

    public RepeatedlyRequestWrapper(HttpServletRequest request, ServletResponse response) throws IOException
    {
        super(request);
        request.setCharacterEncoding("UTF-8");
        response.setCharacterEncoding("UTF-8");

        body = HttpHelper.getBodyString(request).getBytes("UTF-8");
    }

    @Override
    public BufferedReader getReader() throws IOException
    {
        return new BufferedReader(new InputStreamReader(getInputStream()));
    }

    @Override
    public ServletInputStream getInputStream() throws IOException
    {
        final ByteArrayInputStream bais = new ByteArrayInputStream(body);
        return new ServletInputStream()
        {
            @Override
            public int read() throws IOException
            {
                return bais.read();
            }

            @Override
            public int available() throws IOException
            {
                return body.length;
            }

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

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

            @Override
            public void setReadListener(ReadListener readListener)
            {

            }
        };
    }
}

第二步,写一个过滤器的配置类就可以了

代码语言:javascript复制
package com.ruoyi.framework.config;

import java.util.HashMap;
import java.util.Map;
import javax.servlet.DispatcherType;
import javax.servlet.annotation.WebFilter;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.ruoyi.common.filter.RepeatableFilter;
import com.ruoyi.common.filter.XssFilter;
import com.ruoyi.common.utils.StringUtils;

/**
 * Filter配置   过滤器配置
 *
 * @author jing
 */
@Configuration
public class FilterConfig
{
//    # 排除链接(多个用逗号分隔)
    @Value("${xss.excludes}")
    private String excludes;
//  # 匹配链接   也就是这个里面的,要防止xss攻击
    @Value("${xss.urlPatterns}")
    private String urlPatterns;


//    xss的过滤器的  注册
    @SuppressWarnings({ "rawtypes", "unchecked" })
    @Bean
//    通过其两个属性name以及havingValue来实现的,
//    其中name用来从application.properties中读取某个属性值。
//    如果该值为空,则返回false;
//    如果值不为空,则将该值与havingValue指定的值进行比较,如果一样则返回true;否则返回false。
//    如果返回值为false,则该configuration不生效;为true则生效。
    @ConditionalOnProperty(value = "xss.enabled", havingValue = "true")
    public FilterRegistrationBean xssFilterRegistration()
    {
//        创建过滤器的 注册器
        FilterRegistrationBean registration = new FilterRegistrationBean();

        // 设置过滤器的URL模式  REQUEST:默认值。浏览器直接请求资源
//        浏览器直接请求 资源时会执行过滤
//         * 注解配置:
//        * 设置dispatcherTypes属性,取值如下:
//        1. REQUEST:默认值。浏览器直接请求资源
//        @WebFilter(value="/index.jsp",dispatcherTypes = DispatcherType.REQUEST)//浏览器直接请求index.jsp资源时会执行过滤
//        2. FORWARD:转发访问资源
//        @WebFilter(value = "/index.jsp",dispatcherTypes = DispatcherType.FORWARD)//在发生转发请求时,如果转发请求的资源时index.jsp则执行过滤
//        3. INCLUDE:包含访问资源
//        4. ERROR:错误跳转资源
//        5. ASYNC:异步访问资源
//        注意:上述的取值可以同时存在多个,比如:
//        @WebFilter(value = "/index.jsp",dispatcherTypes = {DispatcherType.REQUEST,DispatcherType.FORWARD})
//
//            * web.xml配置
//            * 设置<dispatcher></dispatcher>标签即可

        registration.setDispatcherTypes(DispatcherType.REQUEST);//浏览器直接请求 资源时会执行过滤
        registration.setFilter(new XssFilter());// 设置具体的过滤器
//         匹配链接   也就是这个里面的,要防止xss攻击
        registration.addUrlPatterns(StringUtils.split(urlPatterns, ","));
        registration.setName("xssFilter");
//     FilterRegistrationBean.HIGHEST_PRECEDENCE   表这个过滤器在众多过滤器中级别最高,也就是过滤的时候最先执行
//        设置优先过滤的级别,越小越优先。
        registration.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE);

        Map<String, String> initParameters = new HashMap<String, String>();
//         # 排除链接(多个用逗号分隔)
//        这个key 是随便写的,但是具体的过滤器里面 拿的时候也要对应的MC
        initParameters.put("excludes", excludes); //设置初始化的一些参数
        registration.setInitParameters(initParameters);
        return registration;
    }

    @SuppressWarnings({ "rawtypes", "unchecked" })
    @Bean
    public FilterRegistrationBean someFilterRegistration()
    {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(new RepeatableFilter());
        registration.addUrlPatterns("/*");
        registration.setName("repeatableFilter");
        registration.setOrder(FilterRegistrationBean.LOWEST_PRECEDENCE);
        return registration;
    }

}

只需要上面的3个文件,你原封不动的放到你的项目里面,就可以实现构建可重复读取inputStream的request了;

0 人点赞