【SpringBoot】获取request请求参数,多次读取报错问题 (has already been called for this request)

2023-10-30

应用场景 :

        因项目中接口请求时, 需要对请求参数进行签名验证。 当请求参数的body中有基本类型时(例: int, long, boolean等),因为基本类型如果没传值,序列化的时候会有默认值的问题, 最后导致实际接口调用生成的签名和项目中进行校验的签名不匹配。如果直接从request中获取请求参数body, 会出现request请求流重复读取异常,因此需要实现HttpServletRequestWrapper 重写getInputStream()和getReader()方法,将请求参数body复制到自己requestWrapper中, 后续只操作自己的requestWrapper

代码实现

启动类加上@ServletComponentScan 注解,开启Servlet、Filter、Listener可以直接通过@WebServlet、@WebFilter、@WebListener注解自动注册

//开启Servlet、Filter、Listener可以直接通过@WebServlet、@WebFilter、@WebListener注解自动注册
@ServletComponentScan 
@SpringBootApplication(scanBasePackages = {"xxx.xxx.xx"})
public class StartApplication {
    public static void main(String[] args) {
        SpringApplication.run(StartApplication.class, args);
    }
}

自定义filter, 必须将自定义的wrapper通过过滤器传下去, 不传不会调用重写后的getInputStream()和getReader()方法

@Component
@WebFilter(filterName = "RewriteRequestFilter", urlPatterns = "/*")
@Order(1)
public class RewriteRequestFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        //文件上传类型 不需要处理,否则会报java.nio.charset.MalformedInputException: Input length = 1异常
        if (Objects.isNull(request) || Optional.ofNullable(request.getContentType()).orElse(StringUtils.EMPTY).startsWith("multipart/")) {
            chain.doFilter(request, response);
            return;
        }
        //自定义wrapper 处理流,必须在过滤器中处理,然后通过FilterChain传下去, 否则重写后的getInputStream()方法不会被调用
        MyHttpServletRequestWrapper requestWrapper = new MyHttpServletRequestWrapper((HttpServletRequest)request);
        chain.doFilter(requestWrapper,response);
    }
}

自定义wrapper复制请求流, 如果不重写会报 java.lang.IllegalStateException: getInputStream() has already been called for this request 异常, 原因是request请求流不能重复读取。

@Slf4j
@Getter
public class MyHttpServletRequestWrapper extends HttpServletRequestWrapper {
    /** 复制请求body */
    private final String body;

    public MyHttpServletRequestWrapper (HttpServletRequest request) {
        super(request);
        try {
            //设置编码格式, 防止中文乱码
            request.setCharacterEncoding("UTF-8");
            //将请求中的流取出来放到body里,后面都只操作body就行
            this.body = RequestReadUtils.read(request);
        } catch (Exception e) {
            log.error("MyHttpServletRequestWrapper exception", e);
            throw new RuntimeException("MyHttpServletRequestWrapper 拦截器异常");
        }
    }

    @Override
    public ServletInputStream getInputStream()  {
        //返回body的流信息即可
        try(final ByteArrayInputStream bais = new ByteArrayInputStream(body.getBytes())){
            return getServletInputStream(bais);
        }catch(IOException e){
            log.error("MyHttpServletRequestWrapper.getInputStream() exception", e);
            throw new RuntimeException("MyHttpServletRequestWrapper 获取input流异常");
        }
    }

    @Override
    public BufferedReader getReader(){
        return new BufferedReader(new InputStreamReader(this.getInputStream()));
    }

    /**
     * 重写getInputStream流
     * @param bais
     * @return
     */
    private static ServletInputStream getServletInputStream(ByteArrayInputStream bais) {
        return new ServletInputStream() {
            @Override
            public boolean isFinished() {
                return false;
            }

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

            @Override
            public void setReadListener(ReadListener readListener) {
            }
            @Override
            public int read() {
                return bais.read();
            }
        };
    }
}

读取请求流工具类

@Slf4j
public class RequestReadUtils {
    /**
     * 读取请求流
     * @param request
     * @return
     * @throws UnsupportedEncodingException
     */
    public static String read(HttpServletRequest request){
        try(BufferedReader reader = request.getReader()){
            StringBuilder sb = new StringBuilder();
            String line;
            while ((line = reader.readLine()) != null) {
                sb.append(line);
            }
            return sb.toString();
        }catch (Exception e){
            log.error("MyHttpServletRequestWrapper.RequestReadUtils.readexception", e);
            throw new RuntimeException("MyHttpServletRequestWrapper.RequestReadUtils.read 获取请求流异常");
        }
    }
}

自定义interceptor拦截器, 判断如果是自己的wrapper, 从wrapper中获取请求参数。只能在拦截器中获取请求参数,

1.如果在自定义的Filter中获取请求参数, restful风格的请求参数无法获取。

2.如果在AOP中获取请求参数, 获取的是序列化后的请求参数(基本类型默认值也会被获取) 

@Slf4j
public class CommonInterceptor extends BaseInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        //如果是自定义wrapper, 从自定义wrapper中获取请求参数, 必须在interceptor拦截器中处理, 否则restful风格的请求参数获取不到。
        if(request instanceof MyHttpServletRequestWrapper){
            Map<String, Object> requestParam = getRequestParam((MyHttpServletRequestWrapper)request);
            //放到ThreadLocal中, 这里可以根据自己的项目业务处理
            CommonData.set("param", requestParam);
        }
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
        Object o, Exception e) throws Exception {
        //清空ThreadLocal, 防止内存泄漏
        clearAllData();
    }

    private void clearAllData() {
        CommonData.clearAll();
    }


    /**
     * 从request获取参数
     * @param request
     * @return
     * @throws IOException
     */
    private Map<String, Object> getRequestParam(CallHttpServletRequestWrapper request){
        Map<String, Object> paramMap = Maps.newHashMap();

        //获取使用@RequestParam注解的参数
        Map<String, String[]> parameterMap = request.getParameterMap();
        if(!CollectionUtils.isEmpty(parameterMap)){
            parameterMap.forEach((k,v)->{
                if(Objects.nonNull(v) && v.length > 0){
                    paramMap.put(k, v[0]);
                }
            });
        }

        //获取restful请求参数,必须在interceptor拦截其中才能这样获取到restful参数
        Object attribute = request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
        if(Objects.nonNull(attribute)){
            Map<String, Object> attributeMap = (Map<String, Object>)attribute;
            if(!CollectionUtils.isEmpty(attributeMap)){
                paramMap.putAll(attributeMap);
            }
        }
        //从自定义wrapper中, 获取body体参数
        String bodyString = request.getBody();
        if(StringUtils.isBlank(bodyString)){
            return paramMap;
        }

        //解析body参数
        Map<String, Object> bodyMap = parseRequestMap(bodyString);
        if(CollectionUtils.isEmpty(bodyMap)){
            return paramMap;
        }
        paramMap.putAll(bodyMap);
        return paramMap;
    }

    /**
     * 解析body请求参数
     * @param bodyString
     * @return
     */
    private Map<String, Object> parseRequestMap(String bodyString) {
        Map<String, Object> paramMap = Maps.newHashMap();
        boolean validObject = JSONObject.isValidObject(bodyString);
        //解析@ReqeustBody注解参数
        if(validObject){
            JSONObject jsonObject = JSONObject.parseObject(bodyString);
            paramMap.putAll(jsonObject);
        }else{
            //解析url拼接参数 例 a=123&b=456, 没有加@RequestBoyd注解的post请求
            String[] param = bodyString.split(SpecialConstant.AND);
            if(param.length == 0){
                return paramMap;
            }
            Stream.of(param).forEach(e->{
                String[] split = e.split(SpecialConstant.EQ);
                if(split.length == 0){
                    return;
                }
                paramMap.put(split[0], split[1]);
            });
        }
        return paramMap;
    }
}

自定义ThreadLocal工具类

@Slf4j
@Getter
@Setter
public class CommonData {
    private static ThreadLocal<Map<String, Object>> threadLocal = new ThreadLocal<>();

     /**
     * 添加数据
     * @param key
     * @param value
     */
    public static void set(String key, Object value) {
        if (threadLocal.get() == null) {
            Map<String, Object> map = new HashMap<>();
            threadLocal.set(map);
        }
        threadLocal.get().put(key, value);
    }

   
     /**
     * 清除数据
     */
    public static void clearAll() {
        threadLocal.set(null);
    }
 

    public static Map<String, Object> getSignParam() {
        Object o = threadLocal.get().get("param");
        if (Objects.isNull(o)) {
            log.info("CommonData.getSignParam is null");
            return null;
        }
        return (Map<String, Object>) o;
    }
}

至此通过自定义wrapper重复读取request请求流的方式完成, 也不会再报 java.lang.IllegalStateException: getInputStream() has already been called for this request异常

最后感谢大家的阅读, 如问题请随时指出!!!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【SpringBoot】获取request请求参数,多次读取报错问题 (has already been called for this request) 的相关文章

  • “java.net.MalformedURLException:未找到协议”读取到 html 文件

    我收到一个错误 java net MalformedURLException Protocol not found 我想读取网络上的 HTML 文件 mainfest uses permission android name android
  • Java:在 eclipse 中导出到 .jar 文件

    我正在尝试将 Eclipse 中的程序导出到 jar 文件 在我的项目中 我添加了一些图片和 PDF s 当我导出到 jar 文件时 似乎只有main已编译并导出 我的意愿是如果可能的话将所有内容导出到 jar 文件 因为这样我想将其转换为
  • Spring RestTemplate 使用 cookie 遵循重定向

    最近我遇到了一个问题 我需要做一个GET请求远程服务 我假设使用一个简单的 servlet 并且 RestTemplate 返回Too many redirects 经过一番调查 似乎对指定远程服务发出的第一个请求实际上只是一个 302 重
  • 身份验证在 Spring Boot 1.5.2 和 Oauth2 中不起作用

    我正在使用带有 spring boot 1 5 2 RELEASE 的 Oauth2 当我尝试重写 ResourceServerConfigurerAdapter 类的配置方法时 它给了我一个编译错误 但这在 Spring boot 1 2
  • org.hibernate.QueryException:无法解析属性:文件名

    我正在使用休眠Criteria从列中获取值filename在我的桌子上contaque recording log 但是当我得到结果时 它抛出异常 org hibernate QueryException 无法解析属性 文件名 com co
  • 如何根据运行的 jar 的结果让我的 ant 任务通过或失败?

    我正在运行 CrossCheck 无浏览器 js 单元测试 作为 ant 脚本的一部分 如果 CrossCheck 测试失败 我希望 ant 报告失败 这是 build xml 中的相关部分
  • 什么时候可以在 Java 中使用 Thead.stop() ?

    Thread stop 的 Java 文档听起来好像如果您调用 Thread stop 世界就会终结 已弃用 这种方法本质上是不安全的 停止线程 Thread stop 导致它解锁所有已锁定的监视器 作为未经检查的 ThreadDeath
  • 使用 JUnit 时,有没有办法验证测试方法中是否调用了 try/catch 指令的 Catch 部分?

    例如 如果我想测试以下课程 public class SomeClass public void someMethod try Some code where comething could go wrong catch Exception
  • 自动生成Flyway的迁移SQL

    当通过 Java 代码添加新模型 字段等时 JPA Hibernate 的自动模式生成是否可以生成新的 Flyway 迁移 捕获自动生成的 SQL 并将其直接保存到新的 Flyway 迁移中 以供审查 编辑 提交到项目存储库 这将很有用 预
  • 如何检测 Java 字符串中的 unicode 字符?

    假设我有一个包含 的字符串 我如何找到所有这些 un icode 字符 我应该测试他们的代码吗 我该怎么做呢 例如 给定字符串 A X 我想将其转换为 AYXY 我想对其他 unicode 字符做同样的事情 并且我不想将它们存储在某种翻译映
  • 套接字的读写如何同步?

    我们创建一个套接字 在套接字的一侧有一个 服务器 在另一侧有一个 客户端 服务器和客户端都可以向套接字写入和读取 这是我的理解 我不明白以下事情 如果服务器从套接字读取数据 它在套接字中是否只看到客户端写入套接字的内容 我的意思是 如果服务
  • 内部存储的安全性如何?

    我需要的 对于 Android 我需要永久保存数据 但也能够编辑 并且显然是读取 它 用户不应访问此数据 它可以包含诸如高分之类的内容 用户不得对其进行编辑 我的问题 我会 并且已经 使用过Internal Storage 但我不确定它实际
  • GWT 2.3 开发模式 - 托管模式 JSP 编译似乎不使用 java 1.5 兼容性

    无法编译 JSP 类 生成的 servlet 错误 DefaultMessage 上次更新 0 日期 中 0 时间 HH mm ss z 语法 错误 注释仅在源级别为 1 5 时可用 在尝试以开发模式在 Web 浏览器中打开我的 gwt 模
  • 是否可以使用 Java Guava 将函数应用于集合?

    我想使用 Guava 将函数应用于集合 地图等 基本上 我需要调整 a 的行和列的大小Table分别使所有行和列的大小相同 执行如下操作 Table
  • “无法实例化活动”错误

    我的一个 Android 应用程序拥有大约 100 000 个用户 每周大约 10 次 我会通过 Google 的市场工具向我报告以下异常情况 java lang RuntimeException Unable to instantiate
  • Hamcrest Matchers - 断言列表类型

    问题 我目前正在尝试使用 Hamcrest Matchers 来断言返回的列表类型是特定类型 例如 假设我的服务调用返回以下列表 List
  • OpenCSV:将嵌套 Bean 映射到 CSV 文件

    我正在尝试将 bean 映射到 CSV 文件 但问题是我的 bean 具有其他嵌套 bean 作为属性 所发生的情况是 OpenCSV 遍历属性找到一个 bean 然后进入其中并映射该 bean 内的所有数据 如果找到另一个 bean 它就
  • Java中HashMap和ArrayList的区别?

    在爪哇 ArrayList and HashMap被用作集合 但我不明白我们应该在哪些情况下使用ArrayList以及使用时间HashMap 他们两者之间的主要区别是什么 您具体询问的是 ArrayList 和 HashMap 但我认为要完
  • Java EE 目录结构

    我对以下教程有疑问 http www mkyong com jsf2 jsf 2 internationalization example http www mkyong com jsf2 jsf 2 internationalizatio
  • 在java中使用多个bufferedImage

    我正在 java 小程序中制作游戏 并且正在尝试优化我的代码以减少闪烁 我已经实现了双缓冲 因此我尝试使用另一个 BufferedImage 来存储不改变的游戏背景元素的图片 这是我的代码的相关部分 public class QuizApp

随机推荐

  • OpenCV使用Shi-Tomasi 算法来实现角点检测

    Shi Tomasi 算法是Harris 算法的改进 Harris 算法最原始的定义是将矩阵 M 的行列式值与 M 的迹相减 再将差值同预先给定的阈值进行比较 后来Shi 和Tomasi 提出改进的方法 若两个特征值中较小的一个大于最小阈值
  • PID参数调节的经验

    一 为什么P的值太小会有稳态误差 举个例子 假如一个温度控制系统 就比如控制烙铁的温度 1 我们在烙铁电源线中间串一个继电器作为自动开关 继电器用单片机控制 2 烙铁头上绑一个热电偶 作为温度反馈元件 首先假设 1 给定值Sv 100度 2
  • 【每日知识】使用git命令更新命令到远程仓库

    一 更新当前分支最新代码 1 如果本地当前分支设置了上游分支通过 git branch vv 查看 后运行 git pull 即可更新最新代码 2 如果没有设置上游分支可先设置上游分支 git branch u origin 分支名 再执行
  • TypeScript ---- 初识基础篇

    TypeScript 初识基础篇 写在前面 本篇文章篇幅有点长 适合正打算学习TS的小白 如果有一定基础的大神可以忽略本篇文章 文章并非完全原创 大部分内容都是从网上其他文章搬运过来的 再在其中添加上自己的一些理解和总结 如果有涉及侵权请联
  • C语言的基本结构(一)

    目录 1 C语言程序框架 1 1 程序编译的过程 1 2 C语言程序结构分析 1 C语言程序框架 C程序一般由头文件 主函数和函数三部分组成 从最简单的程序开始 对于大多数程序语言 第一个入门编程代码便是 Hello World 一步一步的
  • SQA在线聊天记录三:QA的职责要求和基本素质

    SQA在线聊天记录三 QA的职责要求和基本素质 2005 05 20 来自 CSDN管理频道 共有评论 条 发表评论 嘉宾主持Bluesky 这里涉及到一个问题 在招聘QA的时候 怎么看待QA人员具备的素质 CSDN的调查结果显示 47 的
  • 签名获取错误(错误: java.io.IOException: Invalid keystore format)签名中没打印出MD5信息

    安卓生成签名文件获取信息的小坑 首先我们通过AndroidStudio生成的签名文件 生成时使用的jdk是根据Studio配置的jdk版本 也就是说是根据下图 图一 中的jdk版本 假如这个jdk版本和电脑配置的环境变量的jdk 图二 不是
  • win10应用商店打不开,错误代码0x80131500

    我也突然遇到这个问题 一开始找各种方法也解决不了 然后在外网找到方法 很多人只是把代理开了 只要关了就可以了 这点不累述 都会提到 我的win10应用商店有两个错误代码0x80131500和0x80072efd 0x80131500错误会转
  • 粒子群算法优化策略总结

    粒子群算法优化策略总结 前言 1 对于惯性权重w的优化 1 1 引入混沌Sine映射构造非线性随机递增惯性权重 1 2 采用一种指数型的非线性递减惯性权重 1 3 分策略更改惯性权重 2 对于c1 c2的优化 2 1 引入正余弦函数来构造非
  • 永久一键关闭QQ频道,不用重新安装

    Step1 使用WMIC指令排查QQ相关进程 首先 按住Windows键 R键打开 运行 然后输入CMD 开启CMD工具 然后 输入如下指令 查找QQ相关的进程信息 由于我这里已经卸载了QQGuild 所以查找不到 wmic process
  • 解决VsCode 软件上方菜单栏消失问题

    当软件的页面出现这样的情况 菜单栏消失 变成三个横杠 不要慌 有方法解决 将鼠标放在此位置上 右键会出现选项 点击红色框选的项目 即可将工作区解锁出上方 这样菜单栏就会出现 如果还是没有将 菜单栏 弄出来 使用快捷键Ctrl Shift P
  • 做项目必读的vue3基础知识

    1 响应式 1 1 两者实现原理 vue2 利用es5的 Object defineProperty 对数据进行劫持结合发布订阅模式来实现 vue3 利用es6的 proxy 对数据代理 通过 reactive 函数给每一个对象都包一层 p
  • 华为p40android auto怎么用,华为手机无线投屏到车载导航,华为车机互联教程

    越来越多的车机系统可以与手机互联 不同的系统连接方式不一样 我们主要以华为手机与车机互联的教程说明 华为手机无线投屏到车载导航的方法 车型雷克萨斯18款ES200 手机是华为MATE8 安卓7 0版本 不同的品牌车型连接方式不一样 可以根据
  • String.ToCharArray()方法中的内存优化技巧

    原文发表于CSDN我的Blog http blog csdn net happyhippy archive 2006 10 29 1356088 aspx 先看下Reflector exe反汇编 net framework 2 0中Msco
  • DNS根服务器

    从抓包可以看出 DNS在传输层上使用了UDP协议 那它只用UDP吗 DNS的IPV4根域名只有13个 这里面其实有不少都部署在漂亮国 那是不是意味着 只要他们不高兴了 切断我们的访问 我们的网络就得瘫痪了呢 我们来展开今天的话题 DNS是基
  • PrintWriter out= response.getWriter()失效无法在前端弹出提示框以及乱码问题.

    PrintWriter out response getWriter 失效无法在前端弹出提示框 在后端想弹出提示框最简单的办法就是使用PrintWriter getWriter PrintWriter out response getWri
  • 使用ELK(ES+Logstash+Filebeat+Kibana)收集nginx的日志

    文章目录 引入logstash Nginx日志格式修改 配置logstash收集nginx日志 引入Redis 收集日志写入redis 从redis中读取日志 logstash解析自定义日志格式 引入Filebeat Filebeat简介
  • 七种性能测试方法

    根据在实际项目中的实践经验 我把常用的性能测试方法分为七大类 后端性能测试 Back end Performance Test 前端性能测试 Front end Performance Test 代码级性能测试 Code level Per
  • USB的阻抗匹配问题

    USB的阻抗匹配问题 USB特征阻抗90 总结 低速和全速时最好进行阻抗匹配 源端串联或终端并联90ohm 高速时不需要 USB 可以自动选择HS High Speed 高速 480 Mbps FS Full Speed 全速 12Mbps
  • 【SpringBoot】获取request请求参数,多次读取报错问题 (has already been called for this request)

    应用场景 因项目中接口请求时 需要对请求参数进行签名验证 当请求参数的body中有基本类型时 例 int long boolean等 因为基本类型如果没传值 序列化的时候会有默认值的问题 最后导致实际接口调用生成的签名和项目中进行校验的签名