javaspringspring-bootspring-mvc

How am I able to read the request body after caching the HttpServletRequest object using ContentCachingRequestWrapper?


I am logging the request body if some custom exception occurs. For this, I have added a filter which caches the HttpServletRequest object using the ContentCachingRequestWrapper. Here is the code sample for that:

@Component
public class RequestWrapperFilter extends OncePerRequestFilter {

  @Override
  protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
    filterChain.doFilter(new ContentCachingRequestWrapper(httpServletRequest), httpServletResponse);
  }

}

And, my controller class looks something like this:
@PostMapping(Paths.INSPECTION_IMAGE_SUBMIT_REQUEST_V1)
  public ResponseEntity<ResponseObject> processThisRequest(
    @RequestBody RequestObject requestObject //how am I able to access this requestObject?
  ) throws Exception {
    return someService.process(requestBody);
  }

So, how am I able to use this requestObject? Since, in the filter class, I have cached this, therefore, the getReader() method must have already been called on this, right? So I should not be able to read the requestObject, right?


Solution

  • ======EDIT======

    Look at how ContentCachingRequestWrapper works

    javax.servlet.http.HttpServletRequest wrapper that caches all content read from the input stream and reader, and allows this content to be retrieved via a byte array.

    Cached does not mean it read the input stream and save in memory, but whenever bytes is read from the wrapped stream, the cacher will write same bytes to its internal buffer.

    So your requestObject is only read once in the entire filter chain, when you create the ContentCachingRequestWrapper in your filter, it does not cache anything yet.

    Other part of framework will read the cached content by invoking the ContentCachingRequestWrapper#getContentAsByteArray method (not read from the stream again, as it already read). (Note that the content only available after the body reader finish reading the input stream, if you invoke before, you will receive incomplete data)

    ======END EDIT======

    Can use RequestBodyAdviceAdapter

    
    @ControllerAdvice
    public class RequestAdvice extends RequestBodyAdviceAdapter {
        
        @Override
        @Nonnull
        public Object afterBodyRead(@Nonnull Object body, @Nonnull HttpInputMessage inputMessage,
            @Nonnull MethodParameter parameter, @Nonnull Type targetType,
            @Nonnull Class<? extends HttpMessageConverter<?>> converterType) {
            
            // Do something with the payload
            return super.afterBodyRead(body, inputMessage, parameter, targetType, converterType);
        }
    }