ContentCachingRequestWrapper#getContentAsByteArray is empty before javax.servlet.FilterChain#doFilter

public class LogFilter implements Filter {

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)
            throws IOException, ServletException {
        ContentCachingRequestWrapper contentCachingRequestWrapper = new ContentCachingRequestWrapper((HttpServletRequest) servletRequest);
        ContentCachingResponseWrapper contentCachingResponseWrapper = new ContentCachingResponseWrapper((HttpServletResponse) servletResponse);

        byte[] contentAsByteArray = contentCachingRequestWrapper.getContentAsByteArray();
        // but contentAsByteArray is empty
        String s = new String(contentAsByteArray);
        System.out.println("request body:" + s);

        chain.doFilter(contentCachingRequestWrapper, contentCachingResponseWrapper);

        byte[] contentAsBytes = contentCachingRequestWrapper.getContentAsByteArray();
        // contentAsBytes is not empty
        String str = new String(contentAsBytes);
        System.out.println("request body:" + str);

        byte[] contentAsByteArray1 = contentCachingResponseWrapper.getContentAsByteArray();
        String s1 = new String(contentAsByteArray1);
        System.out.println("response body:" + s1);

        contentCachingResponseWrapper.copyBodyToResponse();
    }

}

Comment From: mdeinum

This class acts as an interceptor that only caches content as it is being read but otherwise does not cause content to be read. That means if the request content is not consumed, then the content is not cached, and cannot be retrieved via getContentAsByteArray().

Which is how it is supposed to work according to the documentation.

Before the doFilter the request hasn't been consumed/read thus the cached content is empty, afterwards (if consumed) it might be filled.

Comment From: brucelwl

This class acts as an interceptor that only caches content as it is being read but otherwise does not cause content to be read. That means if the request content is not consumed, then the content is not cached, and cannot be retrieved via getContentAsByteArray().

Which is how it is supposed to work according to the documentation.

Before the doFilter the request hasn't been consumed/read thus the cached content is empty, afterwards (if consumed) it might be filled.

Can spring make some adjustments to make it more reasonable to get data before doFilter ??

Comment From: HCF1998

This class acts as an interceptor that only caches content as it is being read but otherwise does not cause content to be read. That means if the request content is not consumed, then the content is not cached, and cannot be retrieved via getContentAsByteArray().

Which is how it is supposed to work according to the documentation.

Before the doFilter the request hasn't been consumed/read thus the cached content is empty, afterwards (if consumed) it might be filled.

Then what should i do if want want to add a filter to log out the request body ? if i comsume the request (request.getInputStream())to take it out . The second filter will always throw out the "HttpMessageNotReadableException: Required request body is missing" Exception.Thanks.

Comment From: bclozel

Can spring make some adjustments to make it more reasonable to get data before doFilter ??

@brucelwl before doFilter, the web framework doesn't know what will handle the request so no, there isn't anything we can do here. This limitation should not prevent you from using ContentCachingRequestWrapper as it's meant to be.

@HCF1998 You can use this wrapper in a custom Servlet filter and log the request body once the filter chain has been called. You can see a similar approach in ShallowEtagHeaderFilter. For more questions, please ask on StackOverflow.

Comment From: LabFerAdm

Good morning, the best solution I found at this link ContentCachingRequestWrapper, works perfectly.

Comment From: HCF1998

@LabFerAdm @bclozel Hi All I had solved it by using custom class which is extend from ContentCachingRequestWrapper, we can override the getContentAsByteArray() and also define a variable cachedContent which we can copy the content in this class. Then using it instead of the ContentCachingRequestWrapper and convert the type in the filter. Below are some sample code:

public class CustomRequestWrapper extends ContentCachingRequestWrapper {

  private final byte[] cachedContent;

  public CustomRequestWrapper(HttpServletRequest request) throws IOException {
    super(request);
    this.cachedContent = StreamUtils.copyToByteArray(request.getInputStream());
  }

  @Override
  public byte[] getContentAsByteArray() {
    return cachedContent;
  }

  @Override
  public ServletInputStream getInputStream() throws IOException {
    return new ContentCachingInputStream(cachedContent);
  }

  private static class ContentCachingInputStream extends ServletInputStream {

    private final ByteArrayInputStream is;

    public ContentCachingInputStream(byte[] bytes) {
      this.is = new ByteArrayInputStream(Objects.isNull(bytes) ? new byte[0] : bytes);
    }

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

    @Override
    public int read(byte[] b) throws IOException {
      return this.is.read(b);
    }

    @Override
    public int read(final byte[] b, final int off, final int len) throws IOException {
      return this.is.read(b, off, len);
    }

    @Override
    public boolean isFinished() {
      return this.is.available() > 0;
    }

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

    @Override
    public void setReadListener(ReadListener readListener) {
    }

    @Override
    public void close() throws IOException {
      this.is.close();
    }
  }
}

Covert the type in each filter firstly:

default ContentCachingRequestWrapper wrapContentCachingRequestWrapper(HttpServletRequest request) throws IOException {
    if (request instanceof ContentCachingRequestWrapper) {
      return (ContentCachingRequestWrapper) request;
    }
    return new CustomRequestWrapper(request);
  }

  default ContentCachingResponseWrapper wrapContentCachingResponseWrapper(HttpServletResponse response) {
    if (response instanceof ContentCachingResponseWrapper) {
      return (ContentCachingResponseWrapper) response;
    }
    return new ContentCachingResponseWrapper(response);
  }

For Example:

@Override
  protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
      throws ServletException, IOException {
    if (isAsyncDispatch(request)) {
      filterChain.doFilter(request, response);
    } else {
      doFilterWrapper(wrapContentCachingRequestWrapper(request), wrapContentCachingResponseWrapper(response), filterChain);
    }
  }

  public void doFilterWrapper(ContentCachingRequestWrapper request, ContentCachingResponseWrapper response, FilterChain filterChain)
      throws ServletException, IOException {
    try {
// Do some precheck job like log request body or check the access token
      beforeHandler(request, response);
      filterChain.doFilter(request, response);
    } finally {
// Can log some response body or duration in here
      afterHandler(request, response);
      response.copyBodyToResponse();
    }
  }
  }