解决HttpServletRequest输入流只能读一次的问题

简介: 在日常的开发中 出于对接口的安全性考虑会要求对接口的请求参数等一起进行签名 加签验签的处理。不同的接口中可能接受不同的数据类型。例如表单数据和json数据,表单数据还好说,调用request的getParameterMap就能全部取出来。而json数据就有些麻烦了,因为json数据放在body中,我们需要通过request的输入流去读取。但问题在于request的输入流只能读取一次不能重复读取,所以我们在过滤器或拦截器里读取了request的输入流之后,请求走到controller层时就会报错。而本文的目的就是介绍如何解决在这种场景下遇到HttpServlet

为什么HttpServletRequest的输入流只能读一次呢?

当我们调用getInputStream()方法获取输入流时得到的是一个InputStream对象,而实际类型是ServletInputStream,它继承于InputStream。

InputStream的read()方法内部有一个postion,标志当前流被读取到的位置,每读取一次,该标志就会移动一次,如果读到最后,read()会返回-1,表示已经读取完了。如果想要重新读取则需要调用reset()方法,position就会移动到上次调用mark的位置,mark默认是0,所以就能从头再读了。调用reset()方法的前提是已经重写了reset()方法,当然能否reset也是有条件的,它取决于markSupported()方法是否返回true。

InputStream默认不实现reset的相关方法,而ServletInputStream也没有重写reset的相关方法,这样就无法重复读取流,这就是我们从request对象中获取的输入流就只能读取一次的原因。


解决办法:重写HttpServletRequestWrapper方法

通过重写HttpServletRequestWrapper把request的保存下来,然后通过过滤器保存下来的request在填充进去,这样就可以多次读取request了。

重写HttpServletRequestWrapper

package com.aliyun.icc.core.config.httpHelp;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.util.Enumeration;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
public class BodyReaderHttpServletRequestWrapper extends
        HttpServletRequestWrapper {
    private final byte[] body;
    public BodyReaderHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        Enumeration e = request.getHeaderNames()   ;
        while(e.hasMoreElements()){
            String name = (String) e.nextElement();
            String value = request.getHeader(name);
            System.out.println(name+" = "+value);
        }
        body = HttpHelper.getBodyString(request).getBytes(Charset.forName("UTF-8"));
    }
    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(getInputStream()));
    }
    @Override
    public ServletInputStream getInputStream() throws IOException {
        final ByteArrayInputStream bais = new ByteArrayInputStream(body);
        return new ServletInputStream() {
            @Override
            public boolean isFinished() {
                return false;
            }
            @Override
            public boolean isReady() {
                return false;
            }
            @Override
            public void setReadListener(ReadListener readListener) {
            }
            @Override
            public int read() throws IOException {
                return bais.read();
            }
        };
    }
    @Override
    public String getHeader(String name) {
        return super.getHeader(name);
    }
    @Override
    public Enumeration<String> getHeaderNames() {
        return super.getHeaderNames();
    }
    @Override
    public Enumeration<String> getHeaders(String name) {
        return super.getHeaders(name);
    }
}

再编写过滤器

package com.aliyun.core.config.filter; 
import com.aliyun.core.config.httpHelp.BodyReaderHttpServletRequestWrapper; 
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
public class HttpServletRequestReplacedFilter implements Filter {
    @Override
    public void destroy() {
    }
    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
                         FilterChain chain) throws IOException, ServletException {
        ServletRequest requestWrapper = null;
        if(request instanceof HttpServletRequest &&
                (null == request.getParameterMap() || request.getParameterMap().isEmpty())) {
            requestWrapper = new RequestReaderHttpServletRequestWrapper((HttpServletRequest) request);
        }
        //获取请求中的流如何,将取出来的字符串,再次转换成流,然后把它放入到新request对象中。
        // 在chain.doFiler方法中传递新的request对象
        if(requestWrapper == null) {
            chain.doFilter(request, response);
        } else {
            chain.doFilter(requestWrapper, response);
        }
    }
    @Override
    public void init(FilterConfig arg0) throws ServletException {
    }
}
相关文章
|
Java Maven 数据安全/隐私保护
弄懂maven仓库 & 仓库优先级 & settings & pom配置关系及差异
弄懂maven仓库 & 仓库优先级 & settings & pom配置关系及差异
3304 1
|
Oracle Java 关系型数据库
Oracle jdk 的国内下载镜像
Oracle jdk 的国内下载镜像
51467 0
解决springboot添加拦截器之后只能获取一次流,并且@requestbody注解和表单方式都可以接到参
解决springboot添加拦截器之后只能获取一次流,并且@requestbody注解和表单方式都可以接到参
|
消息中间件 JSON Java
Spring Boot、Spring Cloud与Spring Cloud Alibaba版本对应关系
Spring Boot、Spring Cloud与Spring Cloud Alibaba版本对应关系
23168 0
|
Java 数据库连接 数据库
MyBatis TypeHandler详解:原理与自定义实践
MyBatis TypeHandler详解:原理与自定义实践
|
SQL Java 数据库连接
深入解析@MapperScan注解:简化MyBatis接口与映射器的关联
在Java持久化领域,MyBatis是一个广泛使用的ORM(对象关系映射)框架,用于将数据库中的数据映射到Java对象中。MyBatis的核心概念是SQL映射器(Mapper),它定义了数据库操作的方法。为了简化Mapper接口与映射器的关联,MyBatis提供了`@MapperScan`注解。本文将深入探讨`@MapperScan`注解的作用、用法,以及在MyBatis应用中的应用场景。
2953 0
|
缓存 安全 前端开发
来聊聊Java项目分层规范
本文讨论了Java项目的分层规范,强调了分层的重要性以避免代码不易扩展和职责边界模糊。作者分享了阿里提出的六层分层模型(开放接口层、终端显示层、Web层、Service层、Manager层、Mapper层)以及对应的领域模型(DO、DTO、VO、query)。同时,提出了简化版的分层规约,以提高开发效率。作者是CSDN Java博客专家,维护者之一的Java Guide项目,并提供了个人项目结构示例。文章鼓励读者关注其公众号以获取更多交流机会。
2782 4
|
Java 关系型数据库 MySQL
【已解决】SpringBoot 启动报错:Failed to configure a DataSource: ‘url‘ attribute is not specified and no emb
【已解决】SpringBoot 启动报错:Failed to configure a DataSource: ‘url‘ attribute is not specified and no emb
6934 1
|
存储 缓存 JSON
详解HTTP四种请求:POST、GET、DELETE、PUT
【4月更文挑战第3天】
65041 3
详解HTTP四种请求:POST、GET、DELETE、PUT
|
JSON 安全 Dubbo
实践指南:WebSocket 鉴权的最佳实践
WebSocket 作为实时通信的利器,越来越受到开发者的青睐。然而,为了确保通信的安全性和合法性,鉴权成为不可或缺的一环。本文将深入探讨 WebSocket 的鉴权机制,为你呈现一揽子的解决方案,确保你的 WebSocket 通信得心应手。
实践指南:WebSocket 鉴权的最佳实践