当前位置: 首页 > news >正文

使用HttpServletRequestWrapper解决web项目request数据流无法重复读取的问题

在做web项目开发时,我们有时候需要做一些前置的拦截判断处理,比如非法参数校验,防攻击拦截,统一日志处理等,而请求参数如果是form表单提交还好处理;对于json这种输入流的数据就会有问题,统一处理如果读取了数据流就会将流进行关闭,这就会导致接下来的业务处理无法读取数据流。为了解决这个问题,需要将request中的输入流包装为可以重复读取的数据流,具体的操作如下:
自定义一个类继承HttpServletRequestWrapper,并实现它里面的相关方法:

import cn.hutool.core.io.IoUtil;
import jakarta.servlet.ReadListener;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;

/**
 * @Author xingo
 * @Date 2024/1/26
 */
public class RepeatableReadRequestWrapper extends HttpServletRequestWrapper {

    private final byte[] body;

    public RepeatableReadRequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        request.setCharacterEncoding("UTF-8");
        body = IoUtil.readBytes(request.getInputStream());
    }

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

    @Override
    public ServletInputStream getInputStream() throws IOException {
        final ByteArrayInputStream bis = new ByteArrayInputStream(body);

        return new ServletInputStream() {

            @Override
            public int read() throws IOException {
                return bis.read();
            }

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

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

            @Override
            public void setReadListener(ReadListener readListener) {

            }
        };
    }

}

封装成这个类就是为了解决需要重复读取输入流的地方就使用这个包装类替换原有的request对象。再定义一个过滤器用于模拟统一处理请求参数,下面就简单模拟在参数中取用户名的过滤器:

import com.fasterxml.jackson.databind.JsonNode;
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.Objects;

/**
 * @Author xingo
 * @Date 2024/1/26
 */
@Order(value = Ordered.LOWEST_PRECEDENCE - 1)
@Component
@WebFilter(filterName = "paramsFilter", urlPatterns = "/*")
public class CheckParamsFilter implements Filter {

    private ServletContext context;
    static final String checkKey = "userName";

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        Filter.super.init(filterConfig);
        context = filterConfig.getServletContext();
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;

        ServletRequest requestWrapper = null;
        String userName = null;
        String contentType = request.getContentType();
        if(contentType != null && contentType.contains(MediaType.APPLICATION_JSON_VALUE)) {
            try {
                // 对于需要读取输入流的先对request进行包装处理,这样后续再次需要读取数据流时就可以正常读到
                requestWrapper = new RepeatableReadRequestWrapper(request);

                JsonNode jsonNode = JacksonUtils.getObjectMapper().readTree(requestWrapper.getInputStream());
                if(jsonNode.get(checkKey) != null) {
                    userName = jsonNode.get(checkKey).asText();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        } else {
            try {
                if(request.getParameter(checkKey) != null) {
                    userName = request.getParameter(checkKey);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        if(userName != null) {
            // 这里判断用户名检查成功就放行、否则就返回失败信息,在放行处理时需要判断是否需要传递包装request
            if(this.check(userName)) {
                chain.doFilter(Objects.requireNonNullElse(requestWrapper, servletRequest), servletResponse);
            }
            servletResponse.setContentType("application/json; charset=utf-8");
            servletResponse.getWriter().print(JacksonUtils.toJSONString(ApiResult.fail(400, "信息验证失败")));

            return;
        }

        chain.doFilter(Objects.requireNonNullElse(requestWrapper, servletRequest), servletResponse);
    }

    @Override
    public void destroy() {
        Filter.super.destroy();
    }

    private boolean check(String userName) {
        return "admin".equals(userName);
    }
}

相关文章:

  • Linux的 .bashrc 有什么作用?
  • 贪吃蛇项目
  • 【报错处理】ModuleNotFoundError: No module named ‘paddle.fluid‘
  • 【Deeplabv3+】Ubutu18.04中使用pytorch复现Deeplabv3+第三步)-----CityscapesScripts生成自己的标签
  • c++谓词
  • 题记(35)--日期累加
  • 2024国际跨境电商展(广州,深圳)两展联动纵深布局新产业
  • 基于Puppeteer实现配置自动化
  • LeetCode——415. 字符串相加
  • 构建高效外卖系统:利用Spring Boot框架实现
  • 三、ElasticSearch集群搭建实战
  • 网络安全知识和华为防火墙
  • Java 面试题之 IO(二)
  • 路由协议解析之静态路由(基于eNSP)(持续更新)
  • Demo: 前端生成条形码并打印
  • 第十四届蓝桥杯C组题目 三国游戏
  • Spring Retry机制详解
  • 鸿鹄工程项目管理系统em Spring Cloud+Spring Boot+前后端分离构建工程项目管理系统
  • vue3封装el-pagination分页组件
  • Python 一些常见的字符串操作
  • 华泰柏瑞基金总经理韩勇因工作调整卸任,董事长贾波代为履职
  • 绿城约13.93亿元竞得西安浐灞国际港港务片区地块,区内土地楼面单价首次冲破万元
  • AMD:预计美国芯片出口管制将对全年营收造成15亿美元损失
  • 李云泽:小微企业融资协调工作机制已发放贷款12.6万亿元
  • 李云泽:将尽快推出支持小微企业民营企业融资一揽子政策
  • 体坛联播|国米淘汰巴萨晋级欧冠决赛,申花击败梅州避免连败