前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >springboot项目,构建可重复读取inputStream的request, 创建RepeatedlyRequestWrapper类

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

作者头像
一写代码就开心
发布2022-10-07 08:13:55
1.4K0
发布2022-10-07 08:13:55
举报
文章被收录于专栏:java和pythonjava和python

目录

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了;

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-10-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 目录
  • 1 问题
  • 2 解决
    • 2.1 解决方法一
      • 2.2 解决方法二
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档