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

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

Java代码
  1. /**
  2.  * Retrieves the body of the request as binary data using
  3.  * a {@link ServletInputStream}.  Either this method or 
  4.  * {@link #getReader} may be called to read the body, not both.
  5.  *
  6.  * @return          a {@link ServletInputStream} object containing
  7.  *              the body of the request
  8.  *
  9.  * @exception IllegalStateException  if the {@link #getReader} method
  10.  *                   has already been called for this request
  11.  *
  12.  * @exception IOException       if an input or output exception occurred
  13.  *
  14.  */
  15. public ServletInputStream getInputStream() throws IOException;
  16. /**
  17.  * Retrieves the body of the request as character data using
  18.  * a <code>BufferedReader</code>.  The reader translates the character
  19.  * data according to the character encoding used on the body.
  20.  * Either this method or {@link #getInputStream} may be called to read the
  21.  * body, not both.
  22.  * 
  23.  *
  24.  * @return                  a <code>BufferedReader</code>
  25.  *                      containing the body of the request  
  26.  *
  27.  * @exception UnsupportedEncodingException  if the character set encoding
  28.  *                      used is not supported and the 
  29.  *                      text cannot be decoded
  30.  *
  31.  * @exception IllegalStateException     if {@link #getInputStream} method
  32.  *                      has been called on this request
  33.  *
  34.  * @exception IOException           if an input or output exception occurred
  35.  *
  36.  * @see                     #getInputStream
  37.  *
  38.  */
  39. public BufferedReader getReader() throws IOException;

两个方法都注明方法只能被调用一次,由于RequestBody是流的形式读取,那么流读了一次就没有了,所以只能被调用一次。既然是因为流只能读一次的原因,那么只要将流的内容保存下来,就可以实现反复读取了。

实现方法:先将RequestBody保存为一个byte数组,然后通过Servlet自带的HttpServletRequestWrapper类覆盖getReader()和getInputStream()方法,使流从保存的byte数组读取。然后再Filter中将ServletRequest替换为ServletRequestWrapper。代码如下:

BodyReaderHttpServletRequestWrapper类包装ServletRequest,将流保存为byte[],然后将getReader()和getInputStream()方法的流的读取指向byte[]

Java代码
  1. import java.io.BufferedReader;
  2. import java.io.ByteArrayInputStream;
  3. import java.io.IOException;
  4. import java.io.InputStreamReader;
  5. import javax.servlet.ServletInputStream;
  6. import javax.servlet.http.HttpServletRequest;
  7. import javax.servlet.http.HttpServletRequestWrapper;
  8. import jodd.JoddDefault;
  9. import jodd.io.StreamUtil;
  10. public class BodyReaderHttpServletRequestWrapper extends HttpServletRequestWrapper {
  11.     private final byte[] body;
  12.     public BodyReaderHttpServletRequestWrapper(HttpServletRequest request)
  13. throws IOException {
  14.         super(request);
  15.         body = StreamUtil.readBytes(request.getReader(), JoddDefault.encoding);
  16.     }
  17.     @Override
  18.     public BufferedReader getReader() throws IOException {
  19.         return new BufferedReader(new InputStreamReader(getInputStream()));
  20.     }
  21.     @Override
  22.     public ServletInputStream getInputStream() throws IOException {
  23.         final ByteArrayInputStream bais = new ByteArrayInputStream(body);
  24.         return new ServletInputStream() {
  25.             @Override
  26.             public int read() throws IOException {
  27.                 return bais.read();
  28.             }
  29.         };
  30.     }
  31. }

在Filter中将ServletRequest替换为ServletRequestWrapper

Java代码
  1. public class HttpServletRequestReplacedFilter implements Filter {
  2.     @Override
  3.     public void init(FilterConfig filterConfig) throws ServletException {
  4.         //Do nothing
  5.     }
  6.     @Override
  7.     public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
  8.         ServletRequest requestWrapper = null;
  9.         if(request instanceof HttpServletRequest) {
  10.             requestWrapper = new BodyReaderHttpServletRequestWrapper((HttpServletRequest) request);
  11.         }
  12.         if(null == requestWrapper) {
  13.             chain.doFilter(request, response);
  14.         } else {
  15.             chain.doFilter(requestWrapper, response);
  16.         }
  17.     }
  18.     @Override
  19.     public void destroy() {
  20.         //Do nothing
  21.     }
  22. }

(文/liwx2000)

本文来源:http://liwx2000.iteye.com/blog/1542431


如果给你带来帮助,欢迎微信或支付宝扫一扫,赞一下。
 

关于 “ServletRequest中getReader()和getInputStream()只能调用一次的解决办法” 的 1 个意见

  1. 从流中获取消息体的方法:
    public static String getBodyString(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();
        }
     

评论关闭。