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

【问题记录】SpringBoot 解决 getReader() has already been called for this request 错误

1,错误日志

getReader() has already been called for this request

2,错误原因

根据Servlet规范,Servlet规范不允许直接多次读取请求体。由于我在拦截器中读取了请求体的信息,又在controller层中使用@RequestBody来获取请求体的信息,导致多次读取请求体的信息,抛出IllegalStateException异常。

3,解决办法

解决方案:

将请求体缓存起来

具体实现:

自定义一个 CacheRequestBodyWrapper 类继承 HttpServletRequestWrapper 类,把请求体信息保存在 CacheRequestBodyWrapper 类中,并重写getReader()getInputStream()方法,返回新的流对象。这样就可以多次读取请求体信息。

编码实现:可直接复制使用

1,自定义CacheRequestBodyWrapper类保存请求体信息

import com.cms.utils.servlet.ServletUtils;
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 Hva
 * Request Body 缓存 Wrapper
 */
public class CacheRequestBodyWrapper extends HttpServletRequestWrapper {

    /**
     * 缓存请求体
     */
    private final byte[] body;

    /**
     * 构造函数,初始化时缓存请求体
     */
    public CacheRequestBodyWrapper(HttpServletRequest request) {
        super(request);
        body = ServletUtils.getBodyBytes(request);
    }

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

    @Override
    public ServletInputStream getInputStream() throws IOException {
        final ByteArrayInputStream inputStream = new ByteArrayInputStream(body);
        // 返回 ServletInputStream
        return new ServletInputStream() {

            @Override
            public int read() {
                return inputStream.read();
            }

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

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

            @Override
            public void setReadListener(ReadListener readListener) {}

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

ServletUtils工具类如下:

我使用的springboot3.x版本,所以使用的是 javax.servlet-api 依赖,使用的工具类为:JakartaServletUtil

如果你的版本是springboot2.x,你应该使用 javax.servlet-api 依赖,工具类使用:ServletUtil

public class ServletUtils {

    /**
     * 判断是否是 json 请求
     */
    public static boolean isJsonRequest(ServletRequest request) {
        return StrUtil.startWithIgnoreCase(request.getContentType(), MediaType.APPLICATION_JSON_VALUE);
    }

    /**
     * 获取json请求的内容
     */
    public static byte[] getBodyBytes(HttpServletRequest request) {
        // 只有 json 请求在读取,因为只有 CacheRequestBodyFilter 才会进行缓存,支持重复读取
        if (isJsonRequest(request)) {
            return JakartaServletUtil.getBodyBytes(request);
        }
        return null;
    }

}

2,自定义过滤器

import com.cms.utils.servlet.ServletUtils;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.jetbrains.annotations.NotNull;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;

/**
 * @author Hva
 * @Description: Request Body 缓存 Filter,实现它的可重复读取
 */
public class CacheRequestBodyFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response, FilterChain filterChain)
            throws IOException, ServletException {
        // 使用自定义的可重复读的请求体包装类
        filterChain.doFilter(new CacheRequestBodyWrapper(request), response);
    }

    @Override
    protected boolean shouldNotFilter(@NotNull HttpServletRequest request) {
        // 只处理 json 请求内容
        return !ServletUtils.isJsonRequest(request);
    }
}

3,Web配置类

在配置类中注册过滤器

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    /**
     * 创建 RequestBodyCacheFilter Bean,可重复读取请求内容
     */
    @Bean
    public FilterRegistrationBean<CacheRequestBodyFilter> requestBodyCacheFilter() {
        return createFilterBean(new CacheRequestBodyFilter(), 1);
    }

    public static <T extends Filter> FilterRegistrationBean<T> createFilterBean(T filter, Integer order) {
        FilterRegistrationBean<T> bean = new FilterRegistrationBean<>(filter);
        bean.setOrder(order);
        // 拦截所有资源
        bean.addUrlPatterns("/*");
        return bean;
    }
}

完成这些配置后再访问接口就不会报错了!!!


http://www.kler.cn/a/470416.html

相关文章:

  • php.ini配置中有10处设置不当,会使网站存在安全问题哦
  • 《Mcal》--MCU模块
  • Harmony开发【笔记1】报错解决(字段名写错了。。)
  • 安装MySQL的五种方法(Linux系统和Windows系统)
  • 查询Mysql中被锁住的表以及如何解锁
  • 如何查看PostgreSQL的版本
  • F#语言的计算机基础
  • HTML - <link>
  • 03、MySQL安全管理和特性解析(DBA运维专用)
  • Python:类方法、实例方法与静态方法深度解析(补)
  • (安卓无线调试)ADB 无法连接及 Scrcpy 问题排查指南
  • 机器学习算法---贝叶斯学习
  • 城市安全风险综合监测预警平台
  • 阿里云 人工智能与机器学习
  • 动漫推荐系统django+vue前台后台完整源码
  • 这是什么操作?强制迁移?GitLab 停止中国区用户访问
  • 专业学习|BFS算法介绍以及实现框架
  • RK3588平台开发系列讲解(系统篇)Linux Kconfig的语法
  • AI赋能运维:实现运维任务的智能化自动分配
  • 2025.1.2
  • CE中注册的符号地址如何通过编程获取
  • [开源]自动化定位建图系统
  • ETL处理工具Kettle入门
  • 如何开通阿里云DDoS保护服务:全面防护您的网站安全
  • 让Qt 具有多选文件夹和记忆上一次打开位置的文件对话框
  • 前端基础函数算法整理应用(sort+reduce+date+双重for循环)