Request IO流只能读取一次的原因和解决方法
欢迎关注驿外残香 | HC的博客
探索路线
只能读一次的原因
首先要知道为什么httpServletRequest
的流只能读取一次。
调用httpServletRequest.getInputStream()
可以看到获取的流类型为ServletInputStream
,继承InputStream
。
下面复习下InputStream
,InputStream
的read
方法内部有一个postion,标志当前流读取到的位置,每读取一次,位置就会移动一次,如果读到最后,read()
会返回-1,标志已经读取完了。如果想要重新读取则需要重写reset()
方法,当然能否reset是有条件的,它取决于markSupported()
是否返回true。
在InputStream
源码中默认不实现reset()
,并且markSupported()
默认返回false:
public synchronized void reset() throws IOException {
// 调用重新读取则抛出异常
throw new IOException("mark/reset not supported");
}
public boolean markSupported() {
// 不支持重新读取
return false;
}
而查看ServletInputStream
源码可以发现,该类没有重写mark()
,reset()
以及markSupported()
,因此Request IO流无法重复读取
public abstract class ServletInputStream extends InputStream {
protected ServletInputStream() {
}
public int readLine(byte[] b, int off, int len) throws IOException {
if (len <= 0) {
return 0;
} else {
int count = 0;
int c;
while((c = this.read()) != -1) {
b[off++] = (byte)c;
++count;
if (c == 10 || count == len) {
break;
}
}
return count > 0 ? count : -1;
}
}
public abstract boolean isFinished();
public abstract boolean isReady();
public abstract void setReadListener(ReadListener var1);
}
使用HttpServletRequestWrapper
既然ServletInputStream
不支持重新读写,那么为什么不把流读出来后用容器存储起来,后面就可以多次利用了。
所幸Java提供了一个请求包装器 :HttpServletRequestWrapper
基于装饰者模式实现了HttpServletRequest
介面,只需要继承该类并实现你想要重新定义的方法即可。
/**
* 代码来自网络,侵删
*/
public class BodyReaderHttpServletRequestWrapper extends HttpServletRequestWrapper {
private final byte[] body;
BodyReaderHttpServletRequestWrapper(HttpServletRequest request) {
super(request);
String sessionStream = getBodyString(request);
body = sessionStream.getBytes(Charset.forName("UTF-8"));
}
/**
* 获取请求Body
*
* @param request 请求
* @return Body字符串
*/
private String getBodyString(final ServletRequest request) {
StringBuilder sb = new StringBuilder();
InputStream inputStream = null;
BufferedReader reader = null;
try {
inputStream = cloneInputStream(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();
}
/**
* 复制输入流
* @param inputStream 请求输入流
* @return 复制出来的输入流
*/
private InputStream cloneInputStream(ServletInputStream inputStream) {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len;
try {
while ((len = inputStream.read(buffer)) > -1) {
byteArrayOutputStream.write(buffer, 0, len);
}
byteArrayOutputStream.flush();
}
catch (IOException e) {
e.printStackTrace();
}
return new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
}
@Override
public BufferedReader getReader() {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
@Override
public ServletInputStream getInputStream() {
final ByteArrayInputStream bais = new ByteArrayInputStream(body);
return new ServletInputStream() {
@Override
public int read() {
return bais.read();
}
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) {
}
};
}
}
并且在读取请求流的过滤器中使用该包装器:
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
BodyReaderHttpServletRequestWrapper myRequestWrapper;
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
// 封装请求参数
myRequestWrapper = new BodyReaderHttpServletRequestWrapper(httpServletRequest);
// 从请求包装器中取出json值
int index = new Gson().fromJson(servletRequest.getReader(), InteractionData.class).getIndex();
// 从请求包装器拿出Session属性user
User user = (User) myRequestWrapper.getSession().getAttribute("user");
// 将请求包装器传递下
filterChain.doFilter(myRequestWrapper, servletResponse);
}
这时候就在后面的代码中再次调用流了