ServletRequest中getReader()和getInputStream()只能调用一次的解决办法
程序员文章站
2022-03-10 12:09:48
...
最近使用spring mvc做项目,数据格式是json,有一个功能是实现记录请求的参数,而请求的参数是整个RequestBody,Controller里是用过@RequestBody获取的。实现方法是通过一个Filter读取整个RequestBody并记录。但是这时就遇到一个问题,ServletRequest的getReader()和getInputStream()两个方法只能被调用一次,而且不能两个都调用。那么如果Filter中调用了一次,在Controller里面就不能再调用了。查看了下ServletRequest的说明,如下:
两个方法都注明方法只能被调用一次,由于RequestBody是流的形式读取,那么流读了一次就没有了,所以只能被调用一次。既然是因为流只能读一次的原因,那么只要将流的内容保存下来,就可以实现反复读取了。
实现方法:先将RequestBody保存为一个byte数组,然后通过Servlet自带的HttpServletRequestWrapper类覆盖getReader()和getInputStream()方法,使流从保存的byte数组读取。然后再Filter中将ServletRequest替换为ServletRequestWrapper。代码如下:
BodyReaderHttpServletRequestWrapper类包装ServletRequest,将流保存为byte[],然后将getReader()和getInputStream()方法的流的读取指向byte[]
在Filter中将ServletRequest替换为ServletRequestWrapper
/**
* Retrieves the body of the request as binary data using
* a {@link ServletInputStream}. Either this method or
* {@link #getReader} may be called to read the body, not both.
*
* @return a {@link ServletInputStream} object containing
* the body of the request
*
* @exception IllegalStateException if the {@link #getReader} method
* has already been called for this request
*
* @exception IOException if an input or output exception occurred
*
*/
public ServletInputStream getInputStream() throws IOException;
/**
* Retrieves the body of the request as character data using
* a <code>BufferedReader</code>. The reader translates the character
* data according to the character encoding used on the body.
* Either this method or {@link #getInputStream} may be called to read the
* body, not both.
*
*
* @return a <code>BufferedReader</code>
* containing the body of the request
*
* @exception UnsupportedEncodingException if the character set encoding
* used is not supported and the
* text cannot be decoded
*
* @exception IllegalStateException if {@link #getInputStream} method
* has been called on this request
*
* @exception IOException if an input or output exception occurred
*
* @see #getInputStream
*
*/
public BufferedReader getReader() throws IOException;
两个方法都注明方法只能被调用一次,由于RequestBody是流的形式读取,那么流读了一次就没有了,所以只能被调用一次。既然是因为流只能读一次的原因,那么只要将流的内容保存下来,就可以实现反复读取了。
实现方法:先将RequestBody保存为一个byte数组,然后通过Servlet自带的HttpServletRequestWrapper类覆盖getReader()和getInputStream()方法,使流从保存的byte数组读取。然后再Filter中将ServletRequest替换为ServletRequestWrapper。代码如下:
BodyReaderHttpServletRequestWrapper类包装ServletRequest,将流保存为byte[],然后将getReader()和getInputStream()方法的流的读取指向byte[]
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import jodd.JoddDefault;
import jodd.io.StreamUtil;
public class BodyReaderHttpServletRequestWrapper extends HttpServletRequestWrapper {
private final byte[] body;
public BodyReaderHttpServletRequestWrapper(HttpServletRequest request)
throws IOException {
super(request);
body = StreamUtil.readBytes(request.getReader(), JoddDefault.encoding);
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream bais = new ByteArrayInputStream(body);
return new ServletInputStream() {
@Override
public int read() throws IOException {
return bais.read();
}
};
}
}
在Filter中将ServletRequest替换为ServletRequestWrapper
public class HttpServletRequestReplacedFilter 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(null == requestWrapper) {
chain.doFilter(request, response);
} else {
chain.doFilter(requestWrapper, response);
}
}
@Override
public void destroy() {
//Do nothing
}
}
推荐阅读
-
ServletRequest中getReader()和getInputStream()只能调用一次的解决办法
-
拦截器中,request中getReader()和getInputStream()只能调用一次,构建可重复读取inputStream的request.
-
解决request.getInputStream()与request.getReader()只能调用一次的问题
-
ServletRequest中getReader()和getInputStream()只能调用一次的解决办法
-
拦截器中,request中getReader()和getInputStream()只能调用一次,构建可重复读取inputStream的request.
-
关于web请求中的getInputStream()和getReader()只能读取一次的原因