背景

通常对安全性有要求的接口都会对请求参数做一些签名验证,而我们一般会把验签的逻辑统一放到过滤器或拦截器里,这样就不用每个接口都去重复编写验签的逻辑。

在一个项目中会有很多的接口,而不同的接口可能接收不同类型的数据,例如表单数据和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的输入流只能读取一次的问题的更多相关文章

  1. 解决spring http输入流和输出流只能读取一次

    1.需求:给某些请求接口记录日志,记录请求的数据和响应的数据和请求所花费的时间.这里采用非侵入式编程,也业务代码进行解耦.按照spring AOP 的编程思想. 2.编程设计:在spring 拦截器中 ...

  2. 大白话讲解如何解决HttpServletRequest的请求参数只能读取一次的问题

    大家在开发过程中,可能会遇到对请求参数做下处理的场景,比如读取上送的参数中看调用方上送的系统编号是否是白名单里面的(更多的会用request中获取IP地址判断).需要对请求方上送的参数进行大小写转换或 ...

  3. SpringBoot 解决HttpServletRequest只能读取一次

    业务逻辑,通过filter读取请求的request,获取token,并将token传递后面流程使用 BodyReaderHttpServletRequestWrapper: public class ...

  4. 解决SpringMVC拦截器中Request数据只能读取一次的问题

    解决SpringMVC拦截器中Request数据只能读取一次的问题 开发项目中,经常会直接在request中取数据,如Json数据,也经常用到@RequestBody注解,也可以直接通过request ...

  5. httpServletRequest中的流只能读取一次的原因

    首先,我们复习一下InputStream read方法的基础知识, java InputStream read方法内部有一个,postion,标志当前流读取到的位置,每读取一次,位置就会移动一次,如果 ...

  6. request.getInputStream() 流只能读取一次问题

    问题: 一次开发过程中同事在 sptring interceptor 中获取 request body 中值,以对数据的校验和预处理等操作 .导致之后spring 在读取request body 值做 ...

  7. springboot请求体中的流只能读取一次的问题

    场景交代 在springboot中添加拦截器进行权限拦截时,需要获取请求参数进行验证.当参数在url后面时(queryString)获取参数进行验证之后程序正常运行.但是,当请求参数在请求体中的时候, ...

  8. Request.getInputStrema只能读取一次的分析过程

    1. 我们先来看一下继承关系HttpServletRequest 接口继承ServletRequest接口 public abstract interface  ServletRequest{ pub ...

  9. HttpServletRequest.getInputStream()多次读取问题

    转自:https://www.jianshu.com/p/85feeb30c1ed HttpServletRequest.getInputStream()多次读取问题   背景 使用POST方法发送数 ...

随机推荐

  1. Java中的BufferedImage类、Image类、Graphics类

    https://www.cnblogs.com/jpfss/p/11731812.html

  2. Python 周刊第 418 期

    新闻 PyCon US 2020 开始接受财务赞助! https://pycon.blogspot.com/2019/10/financial-aid-launches-for-pycon-us-20 ...

  3. PHP 管理树莓派

    同学给过我一块树莓派,那会儿觉得挺新鲜的.但是每次使用都需要远程桌面或者 ssh 进行登录,比较麻烦.后来为了方便管理,在树莓派上安装部署了 LAMP 环境,然后写了一个简单的 PHP 页面,代码如下 ...

  4. 在IntelliJ IDEA中,Lombok注解@Slf4j找不到log解决方案

    在IntelliJ IDEA中,注解@Slf4j找不到log时,可以安装Lombok插件 File → settings → Plugins, 然后点击"Browse repositorie ...

  5. [Java 开发利器Lombok] 常用注解演示

    在以往的对象模型编码时,我们需要写一大堆的get/set以及不同的构造函数等.Lombok为我们提供了一个非常好的插件形式. 在大多数的项目中,只需要使用到以下集中Annotation就足够了,如果需 ...

  6. 简约工作汇报总结演讲辩论创业投资PPT模板

    不管是什么风格的PPT模板,排版布局条例清新,画面干净,也会跟文字内容相辅相成,完成一个好的学生答辩PPT.  模版来源:http://ppt.dede58.com/gongzuohuibao/262 ...

  7. Java工程师学习指南

    java学习指南-四个部分:分别是入门篇,初级篇,中级篇,高级篇 第一步是打好Java基础,掌握Java核心技术,                                            ...

  8. hadoop免登录

    参考:http://wenku.baidu.com/link?url=n4PT7AhGnV7N8KevSEAMcCVGEaYqTuKmNodCQsUnR7qtAnWM0WDs8pFYLOpCUu9R9 ...

  9. PHP清除数组中为0的元素

    array_diff($arr, [0]): // 清除数组中指定元素 $arr = [1,2,3,0,1]; $arr = array_diff($arr, [0]);//输出[1,2,3,1] v ...

  10. RPM常用命令总结

    安装 rpm -ivh package_name (package_name指的是RPM包的文件名) 查询 1.查询是否安装,及安装版本 rpm -q 已安装的软件名(ex:rpm -q docker ...