Spring 多次读取请求体

起因

在某次项目中,我需要在 Filter 中获取 request 的 body 部分的内容以检验数据是否符合要求。 但当我在 Filter 添加通过 request.getReader() 获取 body 的代码后,进行测试时,抛出了异常,异常 信息显式 getReader() has already been called for this request stack。

问题分析

对于一个流而言,其通常只能被读取一次,当我们在 Filter 中进行第一次读取后,后续到 Controller 部分 时,由于有 @RequestBody 注释的对象,此时会再次读取 body 部分,这时就会抛出异常。

解决方案

解决方案也很简单,我们只需要自定义一个 CachedBodyHttpServletReqeust 继承 HttpServletRequestWrapper, 在其中缓存 body 部分的内容,然后在 Filter 中获取 body 时,直接从缓存中获取即可:

private class CachedBodyHttpServletRequest extends HttpServletRequestWrapper {
    private class CachedBodyServletInputStream extends ServletInputStream {
        private final InputStream cacheBodyInputStream;

        public CachedBodyServletInputStream(byte[] cacheBody) {
            this.cacheBodyInputStream = new ByteArrayInputStream(cacheBody);
        }

        @Override
        public boolean isFinished() {
            try {
                return cacheBodyInputStream.available() == 0;
            } catch (IOException e) {
                return true;
            }
        }

        @Override
        public boolean isReady() {
            return true;
        }

        @Override
        public void setReadListener(ReadListener listener) {
            throw new UnsupportedOperationException();
        }

        @Override
        public int read() throws IOException {
            return cacheBodyInputStream.read();
        }
    }

    private final byte[] cacheBody;

    public CachedBodyHttpServletRequest(HttpServletRequest request) throws IOException {
        super(request);
        InputStream requestInputStream = request.getInputStream();
        this.cacheBody = StreamUtils.copyToByteArray(requestInputStream);
    }

    @Override
    public ServletInputStream getInputStream() {
        return new CachedBodyServletInputStream(this.cacheBody);
    }

    @Override
    public BufferedReader getReader() {
        return new BufferedReader(
                new InputStreamReader(new ByteArrayInputStream(this.cacheBody)));
    }
}

这样我们在 Filter 中进行处理的时候需要创建一个 CachedBodyHttpServletRequest 对象,然后将其传递给 FilterChain 的 doFilter 方法即可:

@Override
protected void doFilterInternal(
        HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
        throws ServletException, IOException {
    CachedBodyHttpServletRequest cachedRequest = new CachedBodyHttpServletRequest(request);
    // do something
    filterChain.doFilter(cachedRequest, response);
}

巨人的肩膀




    Enjoy Reading This Article?

    Here are some more articles you might like to read next:

  • 奇怪的知识增加了
  • nvim 学习
  • 文学摘抄
  • Go 学习笔记
  • 鞋带公式