springboot请求体中的流只能读取一次的问题
场景交代
在springboot中添加拦截器进行权限拦截时,需要获取请求参数进行验证。当参数在url后面时(queryString)获取参数进行验证之后程序正常运行。但是,当请求参数在请求体中的时候,通过流的方式将请求体取出参数进行验证之后,发现后续流程抛出错误:
Required request body is missing ...
经过排查,发现ServletInputStream的流只能读取一次(参考:httpServletRequest中的流只能读取一次的原因)。
这就是为什么在拦截器中读取消息体之后,controller的@RequestBody注解无法获取参数的原因。
解决思路
既然知道了原因,那就可以想到一个大概思路了:可不可以把请求的body流换成可重复读的流?
答案是可以的。可以通过继承HttpServletRequestWrapper类进行。
解决方案
1. 继承HttpServletRequestWrapper
继承HttpServletRequestWrapper类,将请求体中的流copy一份出来,覆写getInputStream()和getReader()方法供外部使用。如下,每次调用覆写后的getInputStream()方法都是从复制出来的二进制数组中进行获取,这个二进制数组在对象存在期间一直存在,这样就实现了流的重复读取。
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import org.springframework.util.StreamUtils;
/**
*
* 从请求体中获取参数请求包装类:<br>
* @author nick
* @version 5.0 since 2018年9月5日
*/
public class BodyReaderHttpServletRequestWrapper extends HttpServletRequestWrapper{
private byte[] requestBody = null;//用于将流保存下来
public BodyReaderHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
requestBody = StreamUtils.copyToByteArray(request.getInputStream());
}
@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream bais = new ByteArrayInputStream(requestBody);
return new ServletInputStream() {
@Override
public int read() throws IOException {
return bais.read();
}
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) {
}
};
}
@Override
public BufferedReader getReader() throws IOException{
return new BufferedReader(new InputStreamReader(getInputStream()));
}
}
2. 替换原始request对象
现在可重复读取流的请求对象构造好了,但是需要在拦截器中获取,就需要将包装后的请求对象放在拦截器中。由于filter在interceptor之前执行,因此可以通过filter进行实现。
创建filer,在filter中对request对象用包装后的request替换。
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import com.znz.dns.controller.interceptor.auth.BodyReaderHttpServletRequestWrapper;
@WebFilter(filterName="bodyReaderFilter",urlPatterns="/*")
public class BodyReaderFilter 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 {
ServletRequest requestWrapper=null;
if(request instanceof HttpServletRequest) {
requestWrapper=new BodyReaderHttpServletRequestWrapper((HttpServletRequest)request);
}
if(requestWrapper==null) {
chain.doFilter(request, response);
}else {
chain.doFilter(requestWrapper, response);
}
}
@Override
public void destroy() {
// do nothing
}
}
配置filter
@Bean
public FilterRegistrationBean<BodyReaderFilter> Filters() {
FilterRegistrationBean<BodyReaderFilter> registrationBean = new FilterRegistrationBean<BodyReaderFilter>();
registrationBean.setFilter(new BodyReaderFilter());
registrationBean.addUrlPatterns("/*");
registrationBean.setName("koalaSignFilter");
return registrationBean;
}
3. 获取请求体
既然request对象流已经换成了wrapper reqest,那么流就可以重复读取了。接下来就是获取。
在拦截器中,直接从request中获取流,并进行读取:
/**
* 获取请求体内容
* @return
* @throws IOException
*/
private Map<String, Object> getParamsFromRequestBody(HttpServletRequest request) throws IOException {
BufferedReader reader = request.getReader();
StringBuilder builder = new StringBuilder();
try {
String line = null;
while((line = reader.readLine()) != null) {
builder.append(line);
}
String bodyString = builder.toString();
return objectMapper.readValue(bodyString, Map.class);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return new HashMap<>();
}
补充
在网上找了一些关于“springboot请求体中流不可重复读取问题”的相关文章,解决了自己的问题,但是觉得整个逻辑不怎么清晰(可能是自己没理解?) 因此,根据自己的思路重新整理了一遍。
参考文章
springboot请求体中的流只能读取一次的问题的更多相关文章
- httpServletRequest中的流只能读取一次的原因
首先,我们复习一下InputStream read方法的基础知识, java InputStream read方法内部有一个,postion,标志当前流读取到的位置,每读取一次,位置就会移动一次,如果 ...
- 解决SpringMVC拦截器中Request数据只能读取一次的问题
解决SpringMVC拦截器中Request数据只能读取一次的问题 开发项目中,经常会直接在request中取数据,如Json数据,也经常用到@RequestBody注解,也可以直接通过request ...
- 如何对POST请求但是URL中也有参数/GET请求但是请求体中也有参数的情况进行安全扫描
通常情况下,GET的参数都在URL中,POST的参数都在请求体中,但是如题的情况也有,像使用方法PUT.DELETE的情况也有,这些情况该如何进行安全扫描呢?
- request.getInputStream() 流只能读取一次问题
问题: 一次开发过程中同事在 sptring interceptor 中获取 request body 中值,以对数据的校验和预处理等操作 .导致之后spring 在读取request body 值做 ...
- 微信小程序:post请求参数放在请求体中还是拼接到URL中需要看后台是如何接收的
前端发送post请求时,请求参数可以放在请求中,代码如下: function post(url, data, callback) { wx.request({ method: 'POST', url: ...
- 利用Filter和HttpServletRequestWrapper实现请求体中token校验
先说一下项目的背景,系统传参为json格式,token为其中一个必传参数,此时如果在过滤器中直接读取request,则后续controller中通过RequestBody注解封装请求参数是会报stre ...
- Java中IO流文件读取、写入和复制
//构造文件File类 File f=new File(fileName); //判断是否为目录 f.isDirectory(); //获取目录下的文件名 String[] fileName=f.li ...
- 请求体中需要的true和requests包put请求冲突了
python put请求,添加请求头 不知道怎么解决
- 使用restTemplate发送post请求,传入参数是在requestBody请求体中,以json形式传输
@PostMapping public ResponseResult add(User user){ HttpHeaders httpHeaders = new HttpHeaders(); Medi ...
随机推荐
- 《剑指offer》Q13-18 (牛客10.13)
目录 Q13 调整数组顺序使奇数位于偶数前 Q14 链表中倒数第k个结点 Q15 反转链表 Q16 合并两个有序链表 Q17 树的子结构 Q18 二叉树的镜像 Q13 调整数组顺序使奇数位于偶数前 输 ...
- 【计算机视觉】双目测距(六)--三维重建及UI显示
原文: http://blog.csdn.NET/chenyusiyuan/article/details/5970799 在获取到视差数据后,利用 OpenCV 的 reProjectImageTo ...
- orcale数据库授权码
Product Code:4t46t6vydkvsxekkvf3fjnpzy5wbuhphqzserial Number:601769password:xs374ca
- Ubuntu18突然卡死解决方法
emmmm 1.Ctrl+Alt+F2/F3/F4/F5/F6 F2-6随便选一个都可以 2.进入tty终端后先输入用户名和密码(记得小键盘会自动
- DP单调队列--斜率优化P3195
题意:https://www.luogu.com.cn/problem/P3195 思路:https://www.luogu.com.cn/problemnew/solution/P3195 #def ...
- PHP和js判断访问设备是否是微信浏览器实例
PHP和js判断访问设备是否是微信浏览器实例,代码非常精简,适合新手学习. js判断是否是微信浏览器: 1 function is_weixin() { 2 var ua = window.navig ...
- 基于TCP 协议的socket 简单通信
DNS 服务器:域名解析 socket 套接字 : socket 是处于应用层与传输层之间的抽象层,也是一组操作起来非常简单的接口(接受数据),此接口接受数据之后,交由操作系统 为什么存在 soc ...
- 8.perf top系统性能分析工具
perf 是一个调查 Linux 中各种性能问题的有力工具. # perf --help usage: perf [--version] [--help] COMMAND [ARGS] The m ...
- DevExpress WPF控件记录
以下是博主用到DevExpress WPF控件时的一些记录笔记: 1.Canvas控件:Canvas控件的背景色一定要设置(background="Transparent"),不然 ...
- Linux:删除一个目录下的所有文件,但保留一个指定文件
面试题:删除一个目录下的所有文件,但保留一个指定文件 解答: 假设这个目录是/xx/,里面有file1,file2,file3..file10 十个文件 [root@oldboy xx]# touc ...