getInputStream has already been called for this request 问题记录

问题背景

HttpServletRequest.getReader()

HttpServletRequest.getInputStream() 不能在过滤器中读取一次二进制流(字符流),又在另外一个Servlet中读取一次,即一个InputSteam(BufferedReader)对象在被读取完成后,将无法再次被读取。二进制流被读取后,字节流/字符流的下标将发生变化,假如程序中重新调用一遍getReader/getInputStream 将会提示异常

解决方案

思路很简单,既然HttpServletRequest的请求体无法通过getReader/getInputStream再次调用,那我们只需要包装一层,通过Wrapper对象去集成HttpServletRequest的所有能力,并将请求体抽离出来,这样每次读取我们定义的请求体,问题就解决了。这个方法非常灵活。

画个流程图给大家解释下

通过上述流程图,可以得到关键的两个信息

1、定义包装类HttpServletRequestWrapper

2、全局过滤器filter,把HttpServletRequest包装成

ps:幸好的是,java语言设计者也考虑到了这种场景,已经帮我们准备好Wrapper类,直接集成使用即可。

代码示例

包装类定义(RepeatedlyReadRequestWrapper.java)

java 复制代码
package com.whale.finance.filter;

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;

/**
 * @author yanyq
 * @desc 重复读取HttpServletRequest reader/inputstream
 * @date 2023/7/27
 */
public class RepeatedlyReadRequestWrapper extends HttpServletRequestWrapper {

    private final String body;

    /**
     *
     * @param request
     */
    public RepeatedlyReadRequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        StringBuilder sb = new StringBuilder();
        InputStream ins = request.getInputStream();
        BufferedReader isr = null;
        try{
            if(ins != null){
                isr = new BufferedReader(new InputStreamReader(ins));
                char[] charBuffer = new char[128];
                int readCount;
                while((readCount = isr.read(charBuffer)) != -1){
                    sb.append(charBuffer,0,readCount);
                }
            }
        }catch (IOException e){
            throw e;
        }finally {
            if(isr != null) {
                isr.close();
            }
        }

        sb.toString();
        body = sb.toString();
    }

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

    @Override
    public ServletInputStream getInputStream() {
        final ByteArrayInputStream byteArrayIns = new ByteArrayInputStream(body.getBytes());
        ServletInputStream servletIns = 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 byteArrayIns.read();
            }
        };
        return  servletIns;
    }
}

过滤器定义(ReadBodyHttpServletFilter.java)

java 复制代码
package com.whale.finance.filter;

import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author yanyq
 * @desc 重复读取HttpServletRequest reader/inputstream
 * @date 2023/7/27
 */
@Component
@WebFilter("/*")
public class ReadBodyHttpServletFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
        RepeatedlyReadRequestWrapper requestWrapper = new RepeatedlyReadRequestWrapper(httpServletRequest);
        filterChain.doFilter(requestWrapper, httpServletResponse);
    }

    @Override
    public void destroy() {
    }
}

进行验证测试

java 复制代码
    /**
     * 测试request.getReader
     */
    @PostMapping("/list")
    public void test(HttpServletRequest request) {
        request.getReader(); // 不报错
        return;
    }

Reference

https://blog.csdn.net/feeltouch/article/details/103275416

相关推荐
I_LPL17 分钟前
day34 代码随想录算法训练营 动态规划专题2
java·算法·动态规划·hot100·求职面试
亓才孓17 分钟前
【MyBatis Exception】Public Key Retrieval is not allowed
java·数据库·spring boot·mybatis
J_liaty1 小时前
Java设计模式全解析:23种模式的理论与实践指南
java·设计模式
Desirediscipline1 小时前
cerr << 是C++中用于输出错误信息的标准用法
java·前端·c++·算法
Demon_Hao2 小时前
JAVA快速对接三方支付通道标准模版
java·开发语言
Renhao-Wan2 小时前
Java 算法实践(八):贪心算法思路
java·算法·贪心算法
w***71102 小时前
常见的 Spring 项目目录结构
java·后端·spring
野犬寒鸦2 小时前
深入解析HashMap核心机制(底层数据结构及扩容机制详解剖析)
java·服务器·开发语言·数据库·后端·面试
##学无止境##3 小时前
从0到1吃透Java负载均衡:原理与算法大揭秘
java·开发语言·负载均衡
梵得儿SHI3 小时前
Spring Cloud 核心组件精讲:负载均衡深度对比 Spring Cloud LoadBalancer vs Ribbon(原理 + 策略配置 + 性能优化)
java·spring cloud·微服务·负载均衡·架构原理·对比单体与微服务架构·springcloud核心组件