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

相关推荐
我命由我1234512 小时前
Android多进程开发 - AIDL 最简单的实现、传递数据大小限制
android·java·java-ee·kotlin·android studio·android jetpack·android-studio
青云计划19 小时前
知光项目知文发布模块
java·后端·spring·mybatis
赶路人儿19 小时前
Jsoniter(java版本)使用介绍
java·开发语言
探路者继续奋斗19 小时前
IDD意图驱动开发之意图规格说明书
java·规格说明书·开发规范·意图驱动开发·idd
消失的旧时光-194320 小时前
第十九课:为什么要引入消息队列?——异步系统设计思想
java·开发语言
A懿轩A20 小时前
【Java 基础编程】Java 面向对象入门:类与对象、构造器、this 关键字,小白也能写 OOP
java·开发语言
乐观勇敢坚强的老彭21 小时前
c++寒假营day03
java·开发语言·c++
biubiubiu070621 小时前
谷歌浏览器无法访问localhost:8080
java
大黄说说21 小时前
新手选语言不再纠结:Java、Python、Go、JavaScript 四大热门语言全景对比与学习路线建议
java·python·golang
烟沙九洲21 小时前
Java 中的 封装、继承、多态
java