解决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 {
    }
}
相关文章
|
JSON 前端开发 Java
前端请求SpringBoot接口出现Required request body is missing
前端请求SpringBoot接口出现Required request body is missing
869 2
|
Java Maven 数据安全/隐私保护
弄懂maven仓库 & 仓库优先级 & settings & pom配置关系及差异
弄懂maven仓库 & 仓库优先级 & settings & pom配置关系及差异
3851 1
|
人工智能 Java Spring
Spring Boot循环依赖的症状和解决方案
Spring Boot循环依赖的症状和解决方案
|
JSON Java 应用服务中间件
HttpServletRequest核心方法以及获取请求参数
HttpServletRequest核心方法以及获取请求参数
3148 0
|
4月前
|
机器学习/深度学习 人工智能 前端开发
终端里的 AI 编程助手:OpenCode 使用指南
OpenCode 是开源的终端 AI 编码助手,支持 Claude、GPT-4 等模型,可在命令行完成代码编写、Bug 修复、项目重构。提供原生终端界面和上下文感知能力,适合全栈开发者和终端用户使用。
42293 11
|
开发工具 git 开发者
|
11月前
|
域名解析 网络协议 安全
DNS服务器地址大全
DNS(域名系统)是互联网的“电话簿”,将域名解析为IP地址。选择优质DNS服务器可提升网络速度、降低延迟。以下是全球及中国各运营商的DNS服务器列表,包括公共DNS(如Google DNS、Cloudflare DNS)、中国电信、联通、移动等。根据地理位置、稳定性、安全性与隐私保护等因素选择适合的DNS服务器,优化上网体验。
40680 6
|
监控 Java API
基于trace_id实现SpringCloudGateway网关的链路追踪
通过上述步骤,我们可以在 Spring Cloud Gateway 中基于 `trace_id` 实现链路追踪。引入必要的依赖,配置 Zipkin,自动生成和传递 `trace_id`,并通过测试验证追踪功能。这种机制能够有效地帮助我们监控分布式系统中的请求流,快速定位问题和瓶颈。
991 13
|
监控 Java Nacos
使用Spring Boot集成Nacos
通过上述步骤,Spring Boot应用可以成功集成Nacos,利用Nacos的服务发现和配置管理功能来提升微服务架构的灵活性和可维护性。通过这种集成,开发者可以更高效地管理和部署微服务。
4570 17