转自:https://www.jianshu.com/p/85feeb30c1ed

HttpServletRequest.getInputStream()多次读取问题

 

背景

使用POST方法发送数据时,我们习惯于把数据包装成json格式。

 
image.png

有些情况下,我们会在Filter中读取body数据进行数据校验,
GET方法获取参数比较简单。对于POST方法,可使用如下方法从request中获取body参数:

 private String getBody(HttpServletRequest request) throws IOException {
InputStream in = request.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(in, Charset.forName("UTF-8")));
StringBuffer sb = new StringBuffer("");
String temp;
while ((temp = br.readLine()) != null) {
sb.append(temp);
}
if (in != null) {
in.close();
}
if (br != null) {
br.close();
}
return sb.toString();
}

注意,这里有了一次request.getInputStream()调用。

但是在测试时,一直报JSON格式不正确的错误。经调查发现,项目中使用了公司基础组件中的Filter,而该Filter中也解析了body。同时,不出所料,也是通过调用getInputStream()方法获取的。

原来:

  • 一个InputStream对象在被读取完成后,将无法被再次读取,始终返回-1;
  • InputStream并没有实现reset方法(可以重置首次读取的位置),无法实现重置操作;

因此,当自己写的Filter中调用了一次getInputStream()后,后面再调用getInputStream()读取的数据都为空,所以才报JSON格式不正确的错误。

解决方法

  1. 缓存数据
  2. 使用HttpServletRequestWrapper进行包装

缓存数据
所谓缓存数据,其实就是调用ServletRequestsetAttribute(String s, Object o)来存储数据。

  1. 获取到body后,直接缓存
String body = getBody(request);
request.setAttribute("body", body);

优点:
方便
缺点:
不能控制第三方Filter

  1. 其他地方需要使用body时,只需调用getAttribute方法就能获取数据了:
request.getAttribute("body");

HttpServletRequestWrapper包装

public class RequestWrapper extends HttpServletRequestWrapper {
private final byte[] body;
public RequestWrapper(HttpServletRequest request) throws IOException {
super(request);
body = getBodyStringFromReq(request).getBytes(Charset.forName("UTF-8"));
} public String getBodyString() {
try {
return new String(body, "UTF-8");
} catch (UnsupportedEncodingException ex) {
return new String(body);
}
} private String getBodyStringFromReq(ServletRequest request) {
StringBuilder sb = new StringBuilder();
InputStream inputStream = null;
BufferedReader reader = null;
try {
inputStream = request.getInputStream();
reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8")));
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return sb.toString();
}
}

在Filter中使用时,FilterChain.doFilter()传入Wrapper对象:

public class TestFilter implements Filter {

    @Override
public void destroy() { } @Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
RequestWrapper requestWrapper = new RequestWrapper((HttpServletRequest)request);
String body = requestWrapper.getBodyString();
chain.doFilter(requestWrapper, response); //传入Wrapper对象
} @Override
public void init(FilterConfig arg0) throws ServletException { }
}

这样,位于后面的Filter就可以拥有唯一一次调用HttpServletRequest.getInputStream()的机会了。

优点:
不影响第三方Filter

缺点:
多写了这么多代码,麻烦了一些

参考

HttpServletRequest.getInputStream()多次读取问题的更多相关文章

  1. HttpServletRequest.getInputStream() 只能读取一次

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

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

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

  3. 解决HttpServletRequest的输入流只能读取一次的问题

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

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

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

  5. springboot使用百度富文本UEditor遇到的问题一览(springboot controller中request.getInputStream无法读取)

    先吐槽一下UEditor作为一个前端的js类库,非要把4种后端的代码给出来,而实际生产中用的框架不同,其代码并不具有适应性.(通常类似其它项目仅仅是给出数据交互的规范.格式,后端实现就可以自由定制) ...

  6. 多次读取HttpServletRequest的inputstream方法 问题解决

    原因:我要收集所有来自前台请求的参数信息,无论在任何地方的.当前请求参数都是json格式,都写在httpservlet的body中.这个只能通过流进行获取.然后问题来了,HttpServletRequ ...

  7. 解决 request.getInputStream() 只能获取一次body的问题

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

  8. 解决在Filter中读取Request中的流后, 然后再Control中读取不到的做法

    摘要: 大家知道, StringMVC中@RequestBody是读取的流的方式, 如果在之前有读取过流后, 发现就没有了. 我们来看一下核心代码: filter中主要做的事情, 就是来校验请求是否合 ...

  9. springbootboot-HttpServletRequest.getInputStream() 获取post内容

    问题描述: 在php端用curl post一段json到java springboot.在java端用request.getInputStream()获取到的数据为空. 问题确认: 询问度娘后, 她告 ...

随机推荐

  1. Linux性能优化实战CPU篇之软中断(三)

    一.软中断 1,中断的定义 a>定义 举例:你点了一份外卖,在无法获知外卖进度的情况下,配送员送外卖是不等人的,到了发现没人取会直接走,所以你只能苦苦等着,时不时去门口看送到没有,无法干别的事情 ...

  2. 关于设备控制器和 I/O 通道的理解

    设备控制器 设备控制器是CPU 与 I/O 设备之间的接口,它接收 CPU 发来的命令,去控制 I/O 设备工作,使 CPU 从繁杂的设备控制事务中解放出来. 当 CPU 要操作 I/O 设备时,首先 ...

  3. C#早期绑定&后期绑定

    早期绑定(early binding),又可以称为静态绑定(static binding).在使用某个程序集前,已知该程序集里封装的方法.属性等.则创建该类的实例后可以直接调用类中封装的方法. 后期绑 ...

  4. Java -- int与String相互转换

    int转换为String 使用Integer的静态方法 Integer.toString(int num); 空值会抛出NullPointerException异常 使用String的静态方法 Str ...

  5. maven通用镜像设置

    <mirrors> <mirror> <id>nexus-aliyun</id> <mirrorOf>central</mirrorO ...

  6. 洛谷 简单字符串 'P1055ISBN号码' 问题

    题目描述如下: 知识点①:char数组与int型数字进行运算时,需要将 char[i]-'0' .比如 char c[5]; int i; for(i=0;i<5;i++) scanf(&quo ...

  7. 设置vim永久显示编号

    永久显示行号,需要我们设置配置文件,两种配置方式: 1.  /etc/vimrc   是系统范围的初始化配置 2.  -/.vimrc     个人的vim初始化配置 编辑配置文件,以个人为例: vi ...

  8. kube-scheduler源码分析(3)-抢占调度分析

    kube-scheduler源码分析(3)-抢占调度分析 kube-scheduler简介 kube-scheduler组件是kubernetes中的核心组件之一,主要负责pod资源对象的调度工作,具 ...

  9. 使用http-server 模拟接口数据 mock data

    首先创建文件夹 mkdir mockData&&cd mockData 安装http-server npm i http-server 创建数据文件 touch index.json ...

  10. 防止入侵 两步修改XP远程管理默认端口

    自Windows 2000开始,微软就提供一项终端服务(Terminal Server)这项服务可以将远程的桌面传递到本地.通过该服务,可视化的远程管理可以非常方便的实现.继Windows 2000之 ...