解决HttpServletRequest的输入流只能读取一次的问题
背景
通常对安全性有要求的接口都会对请求参数做一些签名验证,而我们一般会把验签的逻辑统一放到过滤器或拦截器里,这样就不用每个接口都去重复编写验签的逻辑。
在一个项目中会有很多的接口,而不同的接口可能接收不同类型的数据,例如表单数据和json数据,表单数据还好说,调用request的getParameterMap就能全部取出来。而json数据就有些麻烦了,因为json数据放在body中,我们需要通过request的输入流去读取。
但问题在于request的输入流只能读取一次不能重复读取,所以我们在过滤器或拦截器里读取了request的输入流之后,请求走到controller层时就会报错。而本文的目的就是介绍如何解决在这种场景下遇到HttpServletRequest的输入流只能读取一次的问题。
注:本文代码基于SpringBoot框架
HttpServletRequest的输入流只能读取一次的原因
我们先来看看为什么HttpServletRequest的输入流只能读一次,当我们调用getInputStream()方法获取输入流时得到的是一个InputStream对象,而实际类型是ServletInputStream,它继承于InputStream。
InputStream的read()方法内部有一个postion,标志当前流被读取到的位置,每读取一次,该标志就会移动一次,如果读到最后,read()会返回-1,表示已经读取完了。如果想要重新读取则需要调用reset()方法,position就会移动到上次调用mark的位置,mark默认是0,所以就能从头再读了。调用reset()方法的前提是已经重写了reset()方法,当然能否reset也是有条件的,它取决于markSupported()方法是否返回true。
InputStream默认不实现reset(),并且markSupported()默认也是返回false,这一点查看其源码便知:
我们再来看看ServletInputStream,可以看到该类没有重写mark(),reset()以及markSupported()方法:
综上,InputStream默认不实现reset的相关方法,而ServletInputStream也没有重写reset的相关方法,这样就无法重复读取流,这就是我们从request对象中获取的输入流就只能读取一次的原因。
使用HttpServletRequestWrapper + Filter解决输入流不能重复读取问题
既然ServletInputStream不支持重新读写,那么为什么不把流读出来后用容器存储起来,后面就可以多次利用了。那么问题就来了,要如何存储这个流呢?
所幸JavaEE提供了一个 HttpServletRequestWrapper类,从类名也可以知道它是一个http请求包装器,其基于装饰者模式实现了HttpServletRequest界面,部分源码如下:
从上图中的部分源码可以看到,该类并没有真正去实现HttpServletRequest的方法,而只是在方法内又去调用HttpServletRequest的方法,所以我们可以通过继承该类并实现想要重新定义的方法以达到包装原生HttpServletRequest对象的目的。
首先我们要定义一个容器,将输入流里面的数据存储到这个容器里,这个容器可以是数组或集合。然后我们重写getInputStream方法,每次都从这个容器里读数据,这样我们的输入流就可以读取任意次了。
实现代码:
package com.eshore.ismp.filter; import org.springframework.util.StreamUtils; import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets; /**
* Created by lihaodi on 2016/4/7.
*/
public class RequestWrapper extends HttpServletRequestWrapper { private byte[] requestBody; public RequestWrapper(HttpServletRequest request) throws IOException {
super(request);
requestBody = StreamUtils.copyToByteArray(request.getInputStream());
} @Override
public BufferedReader getReader() {
return new BufferedReader(new InputStreamReader(getInputStream(), StandardCharsets.UTF_8));
} @Override
public ServletInputStream getInputStream() {
if (requestBody == null) {
requestBody = new byte[0];
}
final ByteArrayInputStream bais = new ByteArrayInputStream(requestBody);
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();
}
};
}
}
解决HttpServletRequest的输入流只能读取一次的问题的更多相关文章
- 解决spring http输入流和输出流只能读取一次
1.需求:给某些请求接口记录日志,记录请求的数据和响应的数据和请求所花费的时间.这里采用非侵入式编程,也业务代码进行解耦.按照spring AOP 的编程思想. 2.编程设计:在spring 拦截器中 ...
- 大白话讲解如何解决HttpServletRequest的请求参数只能读取一次的问题
大家在开发过程中,可能会遇到对请求参数做下处理的场景,比如读取上送的参数中看调用方上送的系统编号是否是白名单里面的(更多的会用request中获取IP地址判断).需要对请求方上送的参数进行大小写转换或 ...
- SpringBoot 解决HttpServletRequest只能读取一次
业务逻辑,通过filter读取请求的request,获取token,并将token传递后面流程使用 BodyReaderHttpServletRequestWrapper: public class ...
- 解决SpringMVC拦截器中Request数据只能读取一次的问题
解决SpringMVC拦截器中Request数据只能读取一次的问题 开发项目中,经常会直接在request中取数据,如Json数据,也经常用到@RequestBody注解,也可以直接通过request ...
- httpServletRequest中的流只能读取一次的原因
首先,我们复习一下InputStream read方法的基础知识, java InputStream read方法内部有一个,postion,标志当前流读取到的位置,每读取一次,位置就会移动一次,如果 ...
- request.getInputStream() 流只能读取一次问题
问题: 一次开发过程中同事在 sptring interceptor 中获取 request body 中值,以对数据的校验和预处理等操作 .导致之后spring 在读取request body 值做 ...
- springboot请求体中的流只能读取一次的问题
场景交代 在springboot中添加拦截器进行权限拦截时,需要获取请求参数进行验证.当参数在url后面时(queryString)获取参数进行验证之后程序正常运行.但是,当请求参数在请求体中的时候, ...
- Request.getInputStrema只能读取一次的分析过程
1. 我们先来看一下继承关系HttpServletRequest 接口继承ServletRequest接口 public abstract interface ServletRequest{ pub ...
- HttpServletRequest.getInputStream()多次读取问题
转自:https://www.jianshu.com/p/85feeb30c1ed HttpServletRequest.getInputStream()多次读取问题 背景 使用POST方法发送数 ...
随机推荐
- [洛谷P1279][题解]字串距离
题目戳我 很明显的这题是一道dp,主要讲一下几个细节 1.初始化 我们需要初始化边界情况也就是一个字符串为空的情况 #----------# #----------# A:aaaaaa A:□□□□□ ...
- 25.md5 collision(NUPT_CTF)
抓住两点提示: 1.md5碰撞 2.please input a 利用0 == 字符串是成立的,从而可以绕过MD5检查. 所以找一个md5是0e开头的值,因为 php 在处理 == 的时候当碰到的字符 ...
- P3747 [六省联考2017]相逢是问候
题意 如果对一个数操作\(k\)次,那么这个数会变成\(c^{c^{...^{a_i}}}\),其中\(c\)有\(k\)个. 根据P4139 上帝与集合的正确用法这道题,我们可以知道一个数不断变为自 ...
- Jmeter文件目录介绍
当我们解压安装包后,在主目录下有以下文件目录,在这里就不一一介绍,主要介绍一些会经常使用到的文件或目录 1.bin目录——存放启动脚本.配置文件.模板等文件 examples:该目录下存放Jmeter ...
- ROS基础-基本概念和简单工具(1)
1.什么是ROS? Robot operating System ,简单说机器人操作系统,弱耦合的分布式进程框架,通过进程间的消息传递和管理.实现硬件抽象和设备控制. 2.节点(node) node ...
- [C++]Game模板-正面视角
前言 本来是想打一个小游戏的-- 可是打到一半思路断了-- 只打出了模板--先把模板拿出来放着 Code //head #include <iostream> #include <c ...
- Cypress 之 URL访问
visit 作用: 访问一个远程URL. (建议:使用前设置 baseUrl) 语法: cy.visit(url) cy.visit(url, options) cy.visit(options) 使 ...
- Linux中哪些工具堪称神器?
作者:int32bit www.zhihu.com/question/59227720 ag:比grep.ack更快的递归搜索文件内容. Github地址: https://github.com/gg ...
- 通过 RxSwift 优雅使用 NotificationCenter
原文 纯粹的官方代码使用NotificationCenter真的很难用,但是有了RxSwift,就变得方便了很多. 修改 Podfile,通过pod引入RxSwift pod 'RxSwift' po ...
- jeesite3环境部署时初始化数据库注意问题
---恢复内容开始--- 首先要修改jeesite.properties下数据库连接方式,注意选择自己的数据库 其次在pom.xml文件中修改对应的数据库连接方式 最后运行db文件夹下的init-db ...