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

相关推荐
qq_12498707534 小时前
基于SSM的动物保护系统的设计与实现(源码+论文+部署+安装)
java·数据库·spring boot·毕业设计·ssm·计算机毕业设计
Coder_Boy_4 小时前
基于SpringAI的在线考试系统-考试系统开发流程案例
java·数据库·人工智能·spring boot·后端
Mr_sun.4 小时前
Day06——权限认证-项目集成
java
瑶山4 小时前
Spring Cloud微服务搭建四、集成RocketMQ消息队列
java·spring cloud·微服务·rocketmq·dashboard
abluckyboy4 小时前
Java 实现求 n 的 n^n 次方的最后一位数字
java·python·算法
2301_818732064 小时前
前端调用控制层接口,进不去,报错415,类型不匹配
java·spring boot·spring·tomcat·intellij-idea
2501_941982054 小时前
深度对比:Java、Go、Python 实现企微外部群推送,哪个效率更高?
java·golang·企业微信
马猴烧酒.5 小时前
【面试八股|JAVA多线程】JAVA多线程常考面试题详解
java·服务器·数据库
sino爱学习5 小时前
高性能线程池实践:Dubbo EagerThreadPool 设计与应用
java·后端
风生u6 小时前
activiti7 详解
java