重复读取 HttpServletRequest 中 InputStream 的方法
开篇第一句,大家是否遇到过这样的问题:
- 通过
httpServletRequest.getInputStream()
获取InputStream
之后,遇到Required request body is missing
错误?
如果你回答“是”的话,那你就来对了。在本文中,我们就来讨论一下,
-
问题 1:为什么
InputStream
无法重复读取? -
问题 2:如何重复读取
HttpServletRequest
中的InputStream
?
回答第一个问题
对于第一个问题,“为什么InputStream
无法重复读取?”,最直接粗暴的回答:InputStream
就是被设计为无法被重复读取的。
我们可以看一下InputStream
中read()
方法的注释:
/**
* Reads the next byte of data from the input stream. The value byte is
* returned as an <code>int</code> in the range <code>0</code> to
* <code>255</code>. If no byte is available because the end of the stream
* has been reached, the value <code>-1</code> is returned. This method
* blocks until input data is available, the end of the stream is detected,
* or an exception is thrown.
*
* <p> A subclass must provide an implementation of this method.
*
* @return the next byte of data, or <code>-1</code> if the end of the
* stream is reached.
* @exception IOException if an I/O error occurs.
*/
public abstract int read() throws IOException;
翻译过来,其大意为:
- 从输入流中读取下一个字节的数据。返回的字节值为从
0
到255
之间的int
型数据。如果由于流到达结尾而没有可用的字节,则返回-1
。除非有可用的输入数据、或者探测到已经到达流的末尾、或者抛出异常,否则将一直阻塞。
根据上面的注释中,我们可以很容易的得出结论:流中的数据,并不是一直存储的,而是会随着读取的行为,被消费掉。
也许上面的解释很抽象,因此我们可以简单的将InputStream
想象为装水的管子,随着水的流出,管子中的水早晚会有流尽的一天。
这么一想,InputStream
到和 NIO 中的Buffer
有些类似了,但无论是InputStream
还是OutputStream
都是单向的,要么只能进、要么只能出,而 NIO 中的Buffer
则是双向的。
回答第二个问题
既然我们已经知道了InputStream
无法被重复读取的原因,那么对于第二个问题,“如何重复读取HttpServletRequest
中的InputStream
?”,其解决方法就简单了。我们可以在获取HttpServletRequest
中的InputStream
的时候,同时做一个备份。
例如,先将HttpServletRequest
中的InputStream
取出来,转为String
对象,然后再把String
对象转为byte[]
数组存回去,这就保证了HttpServletRequest
中InputStream
的值不变,但是我们却获得了可以重复使用的String
对象。
下面就给出一段可用的代码示例,能够保证我们安全的获取HttpServletRequest
中的InputStream
对象:
public class SafeHttpServletRequestWrapper extends HttpServletRequestWrapper {
private static final String CHARSET_UTF8 = "UTF-8";
private final byte[] body;
private String bodyString;
public SafeHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
this.bodyString = StreamUtils.copyToString(request.getInputStream(), Charset.forName(CHARSET_UTF8));
body = bodyString.getBytes(CHARSET_UTF8);
}
public String getBodyString() {
return this.bodyString;
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream innerBAIS = new ByteArrayInputStream(body);
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 innerBAIS.read();
}
};
}
}
如上述代码所示,SafeHttpServletRequestWrapper
是一个安全的HttpServletRequest
包装类。使用方式为:
@Slf4j
public abstract class AbstractFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
try {
SafeHttpServletRequestWrapper requestWrapper = new SafeHttpServletRequestWrapper((HttpServletRequest) servletRequest);
log.info("AbstractFilter servletRequest body is {}", requestWrapper.getBodyString());
filterChain.doFilter(requestWrapper, servletResponse);
} catch (IOException e) {
e.printStackTrace();
}
}
}
如上述代码所示,我们先将servletRequest
强转成了HttpServletRequest
,然后又包装成了SafeHttpServletRequestWrapper
对象。在SafeHttpServletRequestWrapper
对象中,就含有我们备份的InputStream
对象(实际上,被包装成了ByteArrayInputStream
对象)以及可用的bodyString
字符串。
在这里,如果我们想要获取原HttpServletRequest
中InputStream
对象的内容,我们直接调用getBodyString()
即可;如果我们想要将HttpServletRequest
继续传递下去,我们直接传递包装后的SafeHttpServletRequestWrapper
即可,因为其已经包含了原HttpServletRequest
中的全部信息,并且备份了InputStream
对象的内容。
参考文献:
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- C语言——for循环和while循环的效率区别——类似哨兵思想
- 机器学习(八)—Apriori算法
- 机器学习(九)—FP-growth算法
- LeetCode — (1)
- Django初体验——搭建简易blog
- Python开发简单记事本
- 在stm32开发可以调用c标准库的排序和查找 qsort bsearch
- Python解析excel文件并存入sqlite数据库
- python学习总结
- C语言calloc()函数:分配内存空间并初始化——stm32中的应用
- 提升代码的运算速度——代码优化的方法总结
- 自己实现sizeof+大小端测试
- 写一个程序检查一个整数是2的幂
- 持续部署入门:基于 Kubernetes 实现滚动发布
- Python源码分析(一)