前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >重复读取 HttpServletRequest 中 InputStream 的方法

重复读取 HttpServletRequest 中 InputStream 的方法

原创
作者头像
CG国斌
修改2020-08-07 15:13:36
2K0
修改2020-08-07 15:13:36
举报
文章被收录于专栏:维C果糖维C果糖

开篇第一句,大家是否遇到过这样的问题:

  • 通过httpServletRequest.getInputStream()获取InputStream之后,遇到Required request body is missing错误?

如果你回答“是”的话,那你就来对了。在本文中,我们就来讨论一下,

  • 问题 1:为什么InputStream无法重复读取?
  • 问题 2:如何重复读取HttpServletRequest中的InputStream

回答第一个问题

对于第一个问题,“为什么InputStream无法重复读取?”,最直接粗暴的回答:InputStream就是被设计为无法被重复读取的。

我们可以看一下InputStreamread()方法的注释:

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

翻译过来,其大意为:

  • 从输入流中读取下一个字节的数据。返回的字节值为从0255之间的int型数据。如果由于流到达结尾而没有可用的字节,则返回-1。除非有可用的输入数据、或者探测到已经到达流的末尾、或者抛出异常,否则将一直阻塞。

根据上面的注释中,我们可以很容易的得出结论:流中的数据,并不是一直存储的,而是会随着读取的行为,被消费掉

也许上面的解释很抽象,因此我们可以简单的将InputStream想象为装水的管子,随着水的流出,管子中的水早晚会有流尽的一天。

这么一想,InputStream到和 NIO 中的Buffer有些类似了,但无论是InputStream还是OutputStream都是单向的,要么只能进、要么只能出,而 NIO 中的Buffer则是双向的。

回答第二个问题

既然我们已经知道了InputStream无法被重复读取的原因,那么对于第二个问题,“如何重复读取HttpServletRequest中的InputStream?”,其解决方法就简单了。我们可以在获取HttpServletRequest中的InputStream的时候,同时做一个备份。

例如,先将HttpServletRequest中的InputStream取出来,转为String对象,然后再把String对象转为byte[]数组存回去,这就保证了HttpServletRequestInputStream的值不变,但是我们却获得了可以重复使用的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字符串。

在这里,如果我们想要获取原HttpServletRequestInputStream对象的内容,我们直接调用getBodyString()即可;如果我们想要将HttpServletRequest继续传递下去,我们直接传递包装后的SafeHttpServletRequestWrapper即可,因为其已经包含了原HttpServletRequest中的全部信息,并且备份了InputStream对象的内容。


参考文献

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 回答第一个问题
  • 回答第二个问题
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档