如何构建可重复读取inputStream的request

目录
  • 构建可重复读取inputStream的request
  • request中inputStream多次读取
    • 原因
    • 解决方法(缓存读取到的数据)
    • 代码

构建可重复读取inputStream的request

我们知道,request的inputStream只能被读取一次,多次读取将报错,那么如何才能重复读取呢?答案之一是:增加缓冲,记录已读取的内容。

代码如下所示:

import lombok.extern.log4j.Log4j2;
import org.springframework.mock.web.DelegatingServletInputStream;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;
/**
 * request wrapper: 可重复读取request.getInputStream
 */
@Log4j2
public class RepeatedlyReadRequestWrapper extends HttpServletRequestWrapper {
    private static final int BUFFER_START_POSITION = 0;
    private static final int CHAR_BUFFER_LENGTH = 1024;
    /**
     * input stream 的buffer
     */
    private final String body;
    /**
     * @param request {@link javax.servlet.http.HttpServletRequest} object.
     */
    public RepeatedlyReadRequestWrapper(HttpServletRequest request) {
        super(request);
        StringBuilder stringBuilder = new StringBuilder();
        InputStream inputStream = null;
        try {
            inputStream = request.getInputStream();
        } catch (IOException e) {
            log.error("Error reading the request body…", e);
        }
        if (inputStream != null) {
            try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream))) {
                char[] charBuffer = new char[CHAR_BUFFER_LENGTH];
                int bytesRead;
                while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
                    stringBuilder.append(charBuffer, BUFFER_START_POSITION, bytesRead);
                }
            } catch (IOException e) {
                log.error("Fail to read input stream",e);
            }
        } else {
            stringBuilder.append("");
        }
        body = stringBuilder.toString();
    }
    @Override
    public ServletInputStream getInputStream() throws IOException {
        final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes());
        return new DelegatingServletInputStream(byteArrayInputStream);
    }
}

接下来,需要一个对应的Filter.

代码如下所示:

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
public class RepeatlyReadFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        //Do nothing
    }
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        if (request instanceof HttpServletRequest) {
            request = new RepeatedlyReadRequestWrapper((HttpServletRequest) request);
        }
        chain.doFilter(request, response);
    }
    @Override
    public void destroy() {
        //Do nothing
    }
}

最后,需要在web.xml中,增加该Filter的配置(略)。

request中inputStream多次读取

在使用HTTP协议实现应用间接口通信时,服务端读取客户端请求过来的数据,会用到request.getInputStream(),第一次读取的时候可以读取到数据,但是接下来的读取操作都读取不到数据。

原因

一个InputStream对象在被读取完成后,将无法被再次读取,始终返回-1;

InputStream并没有实现reset方法(可以重置首次读取的位置),无法实现重置操作;

解决方法(缓存读取到的数据)

使用request、session等来缓存读取到的数据,这种方式很容易实现,只要setAttribute和getAttribute就行;

使用HttpServletRequestWrapper来包装HttpServletRequest,在中初始化读取request的InputStream数据,以byte[]形式缓存在其中,然后在Filter中将request转换为包装过的request;

代码

编写rHttpServletRequestWrapper子类,用来处理请求数据

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;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class BodyReaderHttpServletRequestWrapper extends HttpServletRequestWrapper
{
	private final byte[] body;
	public BodyReaderHttpServletRequestWrapper(HttpServletRequest request) throws IOException
	{
		super(request);
		Enumeration<String> e = request.getHeaderNames();
		while (e.hasMoreElements())
		{
			String name = (String) e.nextElement();
			String value = request.getHeader(name);
			log.debug("HttpServletRequest头信息:{}-{}", 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 listener)
			{

			}
			@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);
	}
}

调用

public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException
	{
		HttpServletRequest httpRequest = (HttpServletRequest) request;
		HttpServletResponse httpResponse = (HttpServletResponse) response;

		ServletRequest requestWrapper = null;
		requestWrapper = new BodyReaderHttpServletRequestWrapper(httpRequest);

		//数据读取处理
		//...
		//将requestWrapper专递给后面的过滤器
		filterChain.doFilter(requestWrapper, httpResponse);
	}

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • 浅谈request.getinputstream只能读取一次的问题

    首先,我们复习一下InputStream read方法的基础知识, java InputStream read方法内部有一个,postion,标志当前流读取到的位置,每读取一次,位置就会移动一次,如果读到最后,InputStream.read方法会返回-1,标志已经读取完了,如果想再次读取,可以调用inputstream.reset方法,position就会移动到上次调用mark的位置,mark默认是0,所以就能从头再读了. 当然,能否reset是有条件的,它取决于markSupported,m

  • 解决spring 处理request.getInputStream()输入流只能读取一次问题

    一般我们会在InterceptorAdapter拦截器中对请求进行验证 正常普通接口请求,request.getParameter()可以获取,能多次读取 如果我们的接口是用@RequestBody来接受数据,那么我们在拦截器中 需要读取request的输入流 ,因为 ServletRequest中getReader()和getInputStream()只能调用一次 这样就会导致controller 无法拿到数据. 解决方法 : 1.自定义一个类 BodyReaderHttpServletReq

  • 完美解决request请求流只能读取一次的问题

    解决request请求流只能读取一次的问题 实际开发碰到的问题 解决request请求流中的数据二次或多次使用问题 实际开发碰到的问题 springboot项目中,为了防止sql注入,采用Filter拦截器对所有请求流中的json数据进行校验,请求数据没问题则继续向下执行,在后边的代码中应用到请求参数值时,发现request中的json数据为空: 除上边描述的情况,尝试过两次从request中获取json数据,第二次同样是获取不到的. 解决request请求流中的数据二次或多次使用问题 继承Ht

  • 为了多次读取ServletInputStream引发的一系列问题

    目录 多次读取ServletInputStream引发的问题 因为服务器和app之间传输方式是JSON 于是写了下面的filter来让ServletInputStream来多次读取 解决思路:自己解决 ServletInputStream重复读取问题 直接贴代码了,亲测能用 多次读取ServletInputStream引发的问题 因为服务器和app之间传输方式是JSON 格式如下 { head:null body:null token:xxxxxxxxxxxxxxxxxxxxx } 所以想在服务

  • 如何构建可重复读取inputStream的request

    目录 构建可重复读取inputStream的request request中inputStream多次读取 原因 解决方法(缓存读取到的数据) 代码 构建可重复读取inputStream的request 我们知道,request的inputStream只能被读取一次,多次读取将报错,那么如何才能重复读取呢?答案之一是:增加缓冲,记录已读取的内容. 代码如下所示: import lombok.extern.log4j.Log4j2; import org.springframework.mock.

  • SpringBoot v2.2以上重复读取Request Body内容的解决方案

    目录 SpringBoot v2.2以上重复读取Request Body内容 一.需求 二.解决方案 三.遇到问题 四.问题排查 解决方案 SpringBoot 读取Request参数的坑 后端拿参数相关 关于流 SpringBoot v2.2以上重复读取Request Body内容 一.需求 项目有两个场景会用到从Request的Body中读取内容. 打印请求日志 提供Api接口,在api方法执行前,从Request Body中读取参数进行验签,验签通过后在执行api方法 二.解决方案 2.1

  • Springboot如何设置过滤器及重复读取request里的body

    目录 HttpServletRequest的输入流只能读取一次的原因 重复读取body中数据的方法 springboot的过滤器 上面的getBody的代码 需求: request的content-type为applciation/json,进入controller之前需要把body中的参数取出来做一次处理,然后和hearder中的另一个参数做对比. 思路: 加一个过滤器,在过滤器中取出参数做处理,然后比较 注意: body里的数据用流来读取,只能读取一次 HttpServletRequest的

  • Gateway网关自定义拦截器的不可重复读取数据问题

    目录 Gateway网关自定义拦截器的不可重复读取数据 统一网关Gateway 一.为什么需要网关 二.搭建网关服务 三.路由断言工厂 四.路由过滤器 五.跨域问题处理 Gateway网关自定义拦截器的不可重复读取数据 最近在开发gateway网关时,通过自定义拦截器对某些接口的数据进行处理,发现,无法读取到数据.经过查询,发现在Spring5的webflux编程或者普通web编程中,只能从request中获取body一次,后面无法再获取. 参考网上的方法先通过全局过滤器把body先缓存起来.

  • Java使用ByteArrayOutputStream 和 ByteArrayInputStream 避免重复读取配置文件的方法

    ByteArrayOutputStream类是在创建它的实例时,程序内部创建一个byte型别数组的缓冲区,然后利用ByteArrayOutputStream和ByteArrayInputStream的实例向数组中写入或读出byte型数据.在网络传输中我们往往要传输很多变量,我们可以利用ByteArrayOutputStream把所有的变量收集到一起,然后一次性把数据发送出去.具体用法如下: ByteArrayOutputStream:    可以捕获内存缓冲区的数据,转换成字节数组. ByteA

  • Spring Cloud Gateway(读取、修改 Request Body)的操作

    Spring Cloud Gateway(以下简称 SCG)做为网关服务,是其他各服务对外中转站,通过 SCG 进行请求转发. 在请求到达真正的微服务之前,我们可以在这里做一些预处理,比如:来源合法性检测,权限校验,反爬虫之类- 因为业务需要,我们的服务的请求参数都是经过加密的. 之前是在各个微服务的拦截器里对来解密验证的,现在既然有了网关,自然而然想把这一步骤放到网关层来统一解决. 如果是使用普通的 Web 编程中(比如用 Zuul),这本就是一个 pre filter 的事儿,把之前 Int

  • SpringBoot+Redis实现后端接口防重复提交校验的示例

    目录 1 Maven依赖 2 RepeatedlyRequestWrapper 3 RepeatableFilter 4 RepeatSubmit 5 RepeatSubmitInterceptor 6 RepeatSubmitConfig 7 RepeatSubmitController 1 Maven依赖 <!--redis缓存--> <dependency> <groupId>org.springframework.boot</groupId> <

  • SpringBoot基于redis自定义注解实现后端接口防重复提交校验

    目录 一.添加依赖 二.代码实现 三.测试 一.添加依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-redis</artifactId> <version>1.4.4.RELEASE</version> </dependency> <dependency> <

  • Java INPUTSTREAM如何实现重复使用

    引语: 之前做项目的时候遇到一个问题,就是从网络中读取的图片要上传到oss,而且要对图片进行裁剪和压缩,其中上传和裁剪都要使用到图片的inputStream, 又因为inputstream不能重复读,导致裁剪是成功的,而上传是失败的.我们今天就提供两种方法来解决,inputStream不能重复读的问题. 问题分析: inputStream的内部有个pos指针,当读取的时候指针会不断的移动,当移动到末尾的时候,就无法再次读取了. 我们写个简单的例子来看下: String text = "测试inp

随机推荐