解决HttpServletRequest中的流无法重复读取的问题

简介: 解决HttpServletRequest中的流无法重复读取的问题

问题场景



有一个传递json参数的post接口,笔者增加了一个拦截器。在拦截器中需要获取参数然后进行一些操作。这里的获取是通过流来获取的参数。测试都ok,发上去以后就报了这个错误:Cannot callgetInputStream(), getReader() already called。看报错信息其实很明了,就是不能重复获取流。什么?没有重复获取?其实rest接口接收json参数(使用@RequestBody)也是通过获取流来实现的,其实获取json就这一个方法,就是通过流来获取。无论穿上什么外衣,在哪里你终究还是你。


问题思考



1.既然是我在拦截器中提前将流获取了,导致了接口中无法获取流,那能不能将流获取后再塞回去呢?

如果可以塞回去,那接口也就可以正常获取了。

经测试,无法正常塞回去,这个方向告吹(测试了requst的所有方法,没有能将流再塞回去的)。


(网上有位道友使用了类似的方法:request.setAttribute(data,json);,不过他的例子不是纯json数据,这个json外层还有一个data,其实和这个场景略有不同)


2.上网一通查,发现大家用的都是包装类,也就是将现有的http请求进行包装。怎么个包装法呢?既然流无法重复获取,那我就把流中的数据取出来自己存,自己定义的存储结构,那还不想怎么获取就怎么获取,也不会有获取次数限制了?是不是?


问题解决



所以笔者就新增了一个过滤器,然后在过滤器中使用包装类进行传递请求,这样就解决了问题,代码如下所示:


1.包装类代码


package cn.com.sunacwy.mdm.filter;
import lombok.extern.slf4j.Slf4j;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;
@Slf4j
public class MdmHttpServletRequestWrapper extends HttpServletRequestWrapper {
    private String body;
  //方便再拦截器中直接使用body,无需再从流中获取,当然从流中获取也是ok的
    public String getBody(){
        return this.body;
    }
    public MdmHttpServletRequestWrapper(HttpServletRequest request) {
        super(request);
        //将请求中的流取出来放到body里,后面都只操作body就行
        this.body = RequestReadUtils.read(request);
    }
    @Override
    public ServletInputStream getInputStream()  {
      //返回body的流信息即可
        try(final ByteArrayInputStream bais = new ByteArrayInputStream(body.getBytes());ServletInputStream servletInputStream = 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 bais.read();
            }
        }){
            return servletInputStream;
        }catch(IOException e){
            log.info("#### MdmHttpServletRequestWrapper,发生异常,异常信息:{} ####",e.getMessage());
            return null;
        }
    }
    @Override
    public BufferedReader getReader(){
        return new BufferedReader(new InputStreamReader(this.getInputStream()));
    }
    static class RequestReadUtils {
        private static final int BUFFER_SIZE = 1024 * 8;
        public static String read(HttpServletRequest request) {
            try(BufferedReader bufferedReader = request.getReader();StringWriter writer = new StringWriter()){
                int read;
                char[] buf = new char[BUFFER_SIZE];
                while( ( read = bufferedReader.read(buf) ) != -1 ) {
                    writer.write(buf, 0, read);
                }
                return writer.getBuffer().toString();
            }catch (Exception e){
                log.info("#### MdmHttpServletRequestWrapper,发生异常,异常信息:{} ####",e.getMessage());
                return "";
            }
        }
    }
}


2.过滤器代码


@WebFilter(filterName = "RewriteQequestFilter", urlPatterns = "/mdmcustomer/commit")
@Order(1)
public class RewriteQequestFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        MdmHttpServletRequestWrapper requestWrapper = new MdmHttpServletRequestWrapper((HttpServletRequest)request);
        chain.doFilter(requestWrapper,response);
    }
}


总结



问题解决其实不难,难点在于知识面能不能覆盖到包装类这块,当然了技术都是在解决问题中进度的,在此记录,加深印象。

相关文章
getReader() has already been called for this request
getReader() has already been called for this request
2034 0
getReader() has already been called for this request
|
前端开发 Java 编译器
Spring5新宠:PathPattern,AntPathMatcher:那我走?(下)
Spring5新宠:PathPattern,AntPathMatcher:那我走?(下)
Spring5新宠:PathPattern,AntPathMatcher:那我走?(下)
|
监控 Java Spring
Spring Boot 拦截器(Interceptor)详解
本文介绍Spring Boot拦截器的原理与使用,涵盖自定义拦截器创建、注册配置、执行顺序及典型应用场景,助力提升系统安全性与可维护性。(238字)
995 0
启动报错:java.nio.charset.MalformedInputException: Input length = 1
启动报错:java.nio.charset.MalformedInputException: Input length = 1
1322 0
|
XML 安全 Java
【日志框架整合】Slf4j、Log4j、Log4j2、Logback配置模板
本文介绍了Java日志框架的基本概念和使用方法,重点讨论了SLF4J、Log4j、Logback和Log4j2之间的关系及其性能对比。SLF4J作为一个日志抽象层,允许开发者使用统一的日志接口,而Log4j、Logback和Log4j2则是具体的日志实现框架。Log4j2在性能上优于Logback,推荐在新项目中使用。文章还详细说明了如何在Spring Boot项目中配置Log4j2和Logback,以及如何使用Lombok简化日志记录。最后,提供了一些日志配置的最佳实践,包括滚动日志、统一日志格式和提高日志性能的方法。
3951 31
【日志框架整合】Slf4j、Log4j、Log4j2、Logback配置模板
|
12月前
|
SQL Java 数据库连接
如何在 Java 代码中使用 JSqlParser 解析复杂的 SQL 语句?
大家好,我是 V 哥。JSqlParser 是一个用于解析 SQL 语句的 Java 库,可将 SQL 解析为 Java 对象树,支持多种 SQL 类型(如 `SELECT`、`INSERT` 等)。它适用于 SQL 分析、修改、生成和验证等场景。通过 Maven 或 Gradle 安装后,可以方便地在 Java 代码中使用。
3687 11
|
前端开发 应用服务中间件 定位技术
Nginx 如何代理转发传递真实 ip 地址?
【10月更文挑战第32天】
2986 5
Nginx 如何代理转发传递真实 ip 地址?
|
监控 Java easyexcel
面试官:POI大量数据读取内存溢出?如何解决?
【10月更文挑战第14天】 在处理大量数据时,使用Apache POI库读取Excel文件可能会导致内存溢出的问题。这是因为POI在读取Excel文件时,会将整个文档加载到内存中,如果文件过大,就会消耗大量内存。以下是一些解决这一问题的策略:
1816 1
|
缓存 Java
java: 警告: 源发行版 17 需要目标发行版 17,java17 无效的目标发行
java: 警告: 源发行版 17 需要目标发行版 17,java17 无效的目标发行
11879 59
|
SQL druid Java
解决 ‘The last packet successfully received from the server was xxx milliseconds ago‘ 问题
解决 ‘The last packet successfully received from the server was xxx milliseconds ago‘ 问题
7764 0