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

相关推荐
pnoker21 分钟前
从工业软件到 AI 智能体:工业 AIoT 技术路线的系统梳理
java·人工智能·物联网·microsoft·开源·工业互联网
专注仿真30 分钟前
Vapor框架构建及Swift for TensorFlow图像分析
java·其他
逃逸线LOF30 分钟前
Spring的AOP简介
java·后端·spring
砚底藏山河36 分钟前
【量化纯GET实战 #21】用 pandas 做分析:把接口数据变成 DataFrame
java·python·金融·maven·pandas
Zzzzmo_1 小时前
Spring MVC
java·spring·mvc·cookie/session
cfm_29141 小时前
Spring核心设计模式
java·spring·设计模式
Java小白笔记1 小时前
Java 实现阿里云 OSS 文件上传链路:普通上传、秒传、分片与断点续传
java·开发语言·数据库·spring·阿里云
2333!!!!!1 小时前
rocket新手一些常见问题
java·开发语言
骇客野人1 小时前
Springboot 的 配置文件 application.yml 和 bootrap.yml 的由来和作用
java·spring boot·后端
wuminyu1 小时前
JVM锁膨胀与Futex源码解析
java·linux·c语言·jvm·c++