`
liwx2000
  • 浏览: 132324 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

ServletRequest中getReader()和getInputStream()只能调用一次的解决办法

阅读更多
最近使用spring mvc做项目,数据格式是json,有一个功能是实现记录请求的参数,而请求的参数是整个RequestBody,Controller里是用过@RequestBody获取的。实现方法是通过一个Filter读取整个RequestBody并记录。但是这时就遇到一个问题,ServletRequest的getReader()和getInputStream()两个方法只能被调用一次,而且不能两个都调用。那么如果Filter中调用了一次,在Controller里面就不能再调用了。查看了下ServletRequest的说明,如下:

    /**
     * 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
	}

}
分享到:
评论
3 楼 maoweiwer 2017-09-01  
这代码编译能过?
new ServletInputStream() 是一个抽象类,还有几个方法也需要实现吧
2 楼 jacktao219 2016-11-02  
赞一赞~~
1 楼 cuitzyj 2012-08-15  
good,好文章

相关推荐

    完美解决request请求流只能读取一次的问题

    解决request请求流只能读取一次的问题,我们可以使用自定义的HttpServletRequestWrapper,覆写getInputStream()和getReader()方法,从而实现流的重复读取。这可以在SpringBoot项目中使用Filter拦截器对所有请求流中...

    Java Web笔记.docx

    - 读取请求体(POST数据):`getInputStream()` 或 `getReader()` 四、案例:用户登录 1. 用户登录案例需求 用户提交用户名和密码,服务器验证信息,成功则允许登录,失败则显示错误信息。 2. 开发步骤 - 创建...

    Head_First_Servlet_and_JSP_筆記.pdf

    - **Idempotent**: 描述了对服务进行多次操作,结果与操作一次相同的行为特性。例如,GET和HEAD方法是幂等的,而POST通常不是。 ##### ServletRequest接口 - **getAttribute(String)**: 根据名称获取请求属性的值...

    java 获取request中的请求参数代码详解

    需要注意的是,使用 `getInputStream()` 方法获取 Request Body 中的数据只能使用一次,如果多次调用将会导致流的数据无法到达 Controller 层,报 400 错误。 Java 提供了多种方式来获取 Request 中的请求参数,...

    Servlet学习笔记2

    可以通过`getInputStream()`或`getReader()`方法读取请求体数据。 #### 五、扩展Servlet - **实现Servlet接口**:要创建自己的Servlet,需要实现`javax.servlet.Servlet`接口,至少重写`service()`方法。 - 示例...

    servlet电子书籍 pdf格式

    - **初始化**: 当Servlet第一次被加载到内存时,容器会调用`init()`方法来初始化Servlet。 - **服务**: 对于每个客户端请求,容器都会调用`service()`方法来处理请求。 - **销毁**: 当Servlet不再需要时,容器会...

    JAVA servlet API说明文档

    2. service(ServletRequest req, ServletResponse res):处理客户端请求,对每个请求都会调用一次。 3. destroy():当Servlet从服务中移除或容器关闭时被调用,用于释放资源。 4. getServletConfig():返回...

    servlet笔记

    - **接口**:`SingleThreadModel` 是一个标记接口,用于指示 Servlet 实例在同一时刻只能处理一个请求。 #### 第三章:Servlet的资源访问 - **资源访问**:Servlet 可以访问 Web 应用的资源,如数据库连接等。 - *...

Global site tag (gtag.js) - Google Analytics