SpringBoot请求体中的流只能读取一次的问题HttpServletRequest的流只能读取一次的原因

2023-05-16

问题场景:在项目开发过程中需要记录用户的操作行为,即用户请求的url和相关url中带有的请求体参数,在springboot中只能在拦截器中读取了一次,在controller获取不到参数。

经过代码排查,发现ServletInputStream的流只能读取一次,从而影响到controller层接受request内容。

解决方案:

1、声明BodyReaderHttpServletRequestWrapper类继承HttpServletRequestWrapp将请求体中的流copy一份,重写getInputStream()和getReader()方法供外部使用

package com.hffss.config;

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.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;

import org.springframework.util.StreamUtils;

/**
 * 从请求体中获取参数请求包装类
 * @author: Mr.FangLei
 * @Date: 2022/4/14 15:31
 */
public class BodyReaderHttpServletRequestWrapper extends HttpServletRequestWrapper {

    private byte[] requestBody = null;//用于将流保存下来

    public BodyReaderHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        requestBody = StreamUtils.copyToByteArray(request.getInputStream());
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {

        final ByteArrayInputStream bais = new ByteArrayInputStream(requestBody);

        return new ServletInputStream() {

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

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

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

            @Override
            public void setReadListener(ReadListener readListener) {

            }
        };
    }

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

2、自定义MyFilter继承Filter,在Filter中对request对象用包装后的request替换(可重复读取流的请求对象构造完成,但是需要在拦截器中获取,需要将包装后的请求对象放在拦截器中,由于Filter在interceptor之前执行,可以通过Filter进行实现)

package com.hffss.config;

import lombok.extern.slf4j.Slf4j;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;

/**
 * @author: Mr.FangLei
 * @Date: 2022/4/14 15:31
 */

@Slf4j
@WebFilter(urlPatterns = "/*",filterName = "MyFilter")
public class MyFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("开始");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        ServletRequest requestWrapper = null;
        if (request instanceof HttpServletRequest) {
            requestWrapper = new BodyReaderHttpServletRequestWrapper((HttpServletRequest) request);
        }
        if (requestWrapper == null) {
            chain.doFilter(request, response);
        } else {
            System.out.println("------------------------------请求报文----------------------------------");
            System.out.println(getParamsFromRequestBody((HttpServletRequest) requestWrapper));
            System.out.println("------------------------------请求报文----------------------------------");
            chain.doFilter(requestWrapper, response);
        }
    }

    /* *
     * 获取请求体内容
     * @return
     * @throws IOException
     */
    private String getParamsFromRequestBody(HttpServletRequest request) throws IOException {
        BufferedReader br = null;
        String listString = "";
        try {
            br = request.getReader();
            String str = "";
            while ((str = br.readLine()) != null) {
                listString += str;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return listString;
    }

    @Override
    public void destroy() {
        System.out.println("destroy");
    }
}

3、在拦截器中获取请求体

package com.hffss.interceptor;

import com.hffss.entity.RequestLog;
import com.hffss.thread.SaveRequestLogTask;
import com.hffss.utils.IpUtil;
import com.hffss.utils.RequestUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.util.Date;
import java.util.concurrent.ExecutorService;

@Slf4j
public class LogInterceptor implements HandlerInterceptor {

    // 请求日志实体标识
    private static final String LOGGER_ENTITY = "_logger_entity";

    @Resource(name = "taskExecutor")
    private ExecutorService taskExecutor;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String param = "";
        if (isJson(request)) {
            // param = new RequestUtils().getRequestJsonString(request);
            param = getParamsFromRequestBody(request);
        }
        RequestLog requestLog = new RequestLog();
        Integer userId = RequestUtils.getUserId(request);
        String uri = request.getRequestURI();
        requestLog.setUserId(userId);
        requestLog.setReqIp(IpUtil.getIpAddress(request));
        requestLog.setReqParam(param);
        requestLog.setReqUri(uri);
        requestLog.setReqTime(new Date());
        request.setAttribute(LOGGER_ENTITY, requestLog);
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        RequestLog requestLog = (RequestLog) request.getAttribute(LOGGER_ENTITY);
        requestLog.setRespTime(new Date());
        requestLog.setTimeUsed(requestLog.getRespTime().getTime() - requestLog.getReqTime().getTime());
        taskExecutor.execute(new SaveRequestLogTask(requestLog));
    }

    private boolean isJson(HttpServletRequest request) {
        if (request.getContentType() != null) {
            return request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE) || request.getContentType().equals(MediaType.APPLICATION_JSON_UTF8_VALUE);
        }
        return false;
    }

    /**
     * 获取请求体内容
     * @return
     * @throws IOException
     */
    private String getParamsFromRequestBody(HttpServletRequest request) throws IOException {
        BufferedReader reader = request.getReader();
        StringBuilder builder = new StringBuilder();
        try {
            String line = null;
            while((line = reader.readLine()) != null) {
                builder.append(line);
            }
            return builder.toString();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                reader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

}

4、(重点)在Application启动类中需要注入Bean

package com.hffss;

import com.hffss.config.MyFilter;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.MultipartConfigFactory;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.util.unit.DataSize;
import org.springframework.web.client.RestTemplate;

import javax.servlet.MultipartConfigElement;
import java.time.Duration;

@SpringBootApplication
@EnableFeignClients
@EnableAsync
@EnableScheduling
@EnableJpaAuditing
@EnableDiscoveryClient
public class RiskCommonApplication {

    public static void main(String[] args) {
        SpringApplication.run(RiskCommonApplication.class, args);
    }

    /**
     * 文件上传配置
     *
     * @return
     */
    @Bean
    public MultipartConfigElement multipartConfigElement() {
        MultipartConfigFactory factory = new MultipartConfigFactory();
        // 文件最大
        factory.setMaxFileSize(DataSize.ofKilobytes(1024000)); // KB,MB
        // 设置总上传数据总大小
        factory.setMaxRequestSize(DataSize.ofKilobytes(10240000));
        return factory.createMultipartConfig();
    }

    @Bean
    public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder) {
        return restTemplateBuilder.setConnectTimeout(Duration.ofMillis(600000)).setReadTimeout(Duration.ofMillis(600000)).build();
    }

    @Bean
    public FilterRegistrationBean<MyFilter> Filters() {
        FilterRegistrationBean<MyFilter> registrationBean = new FilterRegistrationBean<MyFilter>();
        registrationBean.setFilter(new MyFilter());
        registrationBean.addUrlPatterns("/*");
        registrationBean.setName("MyFilter");
        return registrationBean;
    }
}

FilterRegistrationBean方法中,registrationBean.setName("MyFilter");中的MyFilter要与自定义的MyFilter,注解@WebFilter(urlPatterns = "/*",filterName = "MyFilter")中的filterName保持一致否则不生效
  
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

SpringBoot请求体中的流只能读取一次的问题HttpServletRequest的流只能读取一次的原因 的相关文章

随机推荐

  • 百度地图上根据经纬度集合绘制行车轨迹

    以下是素材 最近项目中用到了根据一段线路的经纬度集合来在地图上播放该车辆的行驶轨迹的需求 下面我就讲一下我实现步骤 效果图如下 因为制作gif图为了控制大小去掉了很多帧 不必在意这些细节 嘿嘿 1 首先在界面上展示百度地图 这不是废话么 如
  • skip-GANomaly复现总结

    文章目录 skip GANomaly复现总结附MvTec数据集介绍实验结果总结谈谈我对于skip GANomaly的看法最后的感想 代码 skip GANomaly复现总结 附MvTec数据集 链接 xff1a https pan baid
  • YOLOv3 从入门到部署:(五)YOLOv3模型的部署(基于C++ opencv)

    文章目录 YOLOv3 从入门到部署 xff1a xff08 五 xff09 YOLOv3模型的部署 xff08 基于C 43 43 opencv xff09 目录关于opencv的DNN介绍代码讲解效果展示 YOLOv3 从入门到部署 x
  • 基于YOLO-fastest-xl的OCR

    文章目录 基于YOLO fastest xl的OCR项目介绍对于yolo fastest xl的结构的更改运行方法效果总结 基于YOLO fastest xl的OCR github链接https github com qqsuhao yol
  • Pytorch多GPU训练时使用hook提取模型中间层输出时与模型输入张量不在同一个GPU上的解决办法

    Pytorch多GPU训练时使用hook提取模型中间层输出时与模型输入张量不在同一个GPU上的解决办法 通常对于单卡训练的模型 xff0c 使用hook可以较为方便地提取出模型中间层输出 例如我们想要获取自定义模型DBL中的conv2d的输
  • 发布自己的Python包

    文章目录 发布自己的Python包第一步 xff1a 注册Pypi账号第二步 xff1a 准备本地文件第三部 xff1a 构建包并上传 发布自己的Python包 参考https packaging python org en latest
  • Python音频信号处理库函数librosa介绍

    文章目录 Python音频信号处理库函数librosa介绍 部分内容将陆续添加 介绍安装综述 xff08 库函数结构 xff09 Core IO and DSP xff08 核心输入输出功能和数字信号处理 xff09 Audio proce
  • python库函数之scipy.signal——滤波器设计

    文章目录 python库函数之scipy signal butter 函数参数设计模拟滤波器设计数字滤波器 2021 06 03 有位博主评论了这篇博客 xff08 评论已被删除 xff09 xff0c 特此说明 python库函数之sci
  • 使用python绘制短时傅里叶变换(STFT)频谱图(时频像)

    文章目录 使用python绘制短时傅里叶变换 xff08 STFT xff09 频谱图 xff08 时频像 xff09 使用python绘制短时傅里叶变换 xff08 STFT xff09 频谱图 xff08 时频像 xff09 使用sci
  • python csv文件数据写入和读取(适用于超大数据量)

    文章目录 python csv文件数据写入和读取 xff08 适用于超大数据量 xff09 python csv文件数据写入和读取 xff08 适用于超大数据量 xff09 一般情况下由于我们使用的数据量比较小 xff0c 因此可以将数据一
  • CentOS 开启端口方法

    查看已经开放的端口 xff1a firewall cmd list ports 查看防火墙状态 xff1a firewall cmd state 开启防火墙 xff1a systemctl start firewalld service 开
  • opencv3颜色识别(C++)

    文章目录 opencv3颜色识别 C 43 43 目标思路1 读取图像2 对比度调整 xff08 直方图均衡 xff09 3 RGB颜色分类4 形态学去噪声 代码结果参考 opencv3颜色识别 C 43 43 目标 给定一幅图像 xff0
  • 小区物业管理系统

    技术 xff1a 小区物业管理 ASP技术 B S 模式 SQL SERVER 2008 摘要 xff1a 随着市场经济的发展和人们生活水平的提高 xff0c 住宅小区已经成为人们安家置业的首选 xff0c 小区业主不但对住宅的本身的美观
  • java判断字符串是否为空的方法总结

    以下是java 判断字符串是否为空的四种方法 方法一 最多人使用的一个方法 直观 方便 但效率很低 if s 61 61 null 34 34 equals s 方法二 比较字符串长度 效率高 是我知道的最好一个方法 if s 61 61
  • 用Python的networkx绘制精美网络图

    最近因为数学建模3天速成Python 然后做了一道网络的题 xff0c 要画网络图 在网上找了一些 xff0c 发现都是一些很基础的丑陋红点图 xff0c 并且关于网络的一些算法也没有讲 xff0c 于是自己进http networkx g
  • Tomcat虚拟路径设置

    前几天写了一个关于登录页面banner图的展示 需求 xff1a banner图的存放地址在项目包的外部 xff0c 不能占用项目资源 这种通过外部存储位置渲染图片的实现方式有两种 xff0c 1 xff1a 目录映射 xff08 虚拟路径
  • Jenkins自动部署,mvn不同的环境打包配置

    今天看了个问题 xff0c 就是在Jenkins里如何根据不同的环境发布代码 我本地的代码环境有 xff0c 开发环境 测试环境 预发布环境和线上环境 基于项目的风险控制 xff0c 安全控制 xff0c 我只有开发环境和测试环境的权限 x
  • IOException while sending message; nested exception is:java.io.FileNotFoundException

    异步发送邮件出现的异常情况 1 问题描述 近期做了一个发送邮件的功能 xff0c 因为在处理发送邮件联系人上出现过失效的邮箱地址 xff0c 为了快速定位到问题 现将批量发送的方式改为单独发送 Failed messages javax m
  • 自定义分页

    给大家介绍一个简单分页的方法 xff08 有兴趣的可以自己试一下 xff09 1 实体 package com hffss entity ext import lombok Builder import lombok Data import
  • SpringBoot请求体中的流只能读取一次的问题HttpServletRequest的流只能读取一次的原因

    问题场景 xff1a 在项目开发过程中需要记录用户的操作行为 xff0c 即用户请求的url和相关url中带有的请求体参数 xff0c 在springboot中只能在拦截器中读取了一次 xff0c 在controller获取不到参数 经过代