4

I have a Springboot API in Scala with multiple endpoints. All endpoints are async and return DeferredResult. I want to use filters to log response body in some cases. I have created a filter with order 1 to cache requests and response as below:

@Component
@Order(1)
class ContentCachingFilter extends OncePerRequestFilter {
  override def doFilterInternal(request: HttpServletRequest, response: HttpServletResponse, filterChain: FilterChain): Unit = {
    val requestToCache  = new ContentCachingRequestWrapper(request)
    val responseToCache = new ContentCachingResponseWrapper(response)
    filterChain.doFilter(requestToCache, responseToCache)
    responseToCache.copyBodyToResponse()
  }
}

My logging filter is similar to below(removed domain specific logic):

@Component
@Order(2)
class RequestResponseLoggingFilter extends OncePerRequestFilter {

  override def doFilterInternal(request: HttpServletRequest, response: HttpServletResponse, filterChain: FilterChain): Unit = {

    logger.info(
      s"Request -> URL : ${request.getMethod} ${request.getRequestURI} ${request.getParameterMap.asScala.stringRepresentation()}")
    logger.debug(
      s"Request Headers -> Content-type : ${request.getContentType} \\n Headers : ${new ServletServerHttpRequest(request).getHeaders}")

    filterChain.doFilter(request, response)

    logger.info(s"Response -> Status : ${response.getStatus}")

  }
}

I have to use ContentCachingResponseWrapper because I want to log the response body. However, even without trying to log the response body and just by introducing ContentCachingResponseWrapper, I don't get any response for calls to any endpoint. A local running instance returns an output like this on curl:

HTTP/1.1 200
Content-Type: application/json
Content-Length: 197
.
.
.
<
* transfer closed with 197 bytes remaining to read
* stopped the pause stream!
* Closing connection 0
curl: (18) transfer closed with 197 bytes remaining to read

If I remove the ContentCachingFilter, the logging filter above works fine but if I try to log response body, the response stream is consumed and the response is no longer generated. Hence I need to use ContentCachingFilter.

I did find out that all this works if the endpoints are synchronous. Could someone help me find out what is the problem and how to make this work with asynchronous endpoint.

nishantv
  • 643
  • 4
  • 9
  • 27

3 Answers3

8
responseToCache.copyBodyToResponse()

change to :

if (requestToCache.isAsyncStarted()) {
  requestToCache.getAsyncContext().addListener(new AsyncListener() {
      public void onComplete(AsyncEvent asyncEvent) throws IOException {
          responseToCache.copyBodyToResponse()
      }

      public void onTimeout(AsyncEvent asyncEvent) throws IOException {
      }

      public void onError(AsyncEvent asyncEvent) throws IOException {
      }

      public void onStartAsync(AsyncEvent asyncEvent) throws IOException {
      }
  })
} else {
  responseToCache.copyBodyToResponse()
}

linys
  • 81
  • 1
  • 3
  • This worked in my case. – Durin Jan 20 '22 at 08:00
  • Spent all day looking for this answer. Kept seeing a 200 status code in the wrapped response in code, yet a 500 in the actual HTTP response. This async check was the key and now I can log all status codes. Thank you! – carmbrester Jul 06 '22 at 19:55
  • You saved my day, thank you! ( In my case, the execution order: sync-filterChain(in)->Controller of DeferredResult method -> sync-filterChain(out) -> async-filterChain(in) ->async-filterChain(out). It seems that the response returns to the front-end just after sync-filterChain(out), not after async-filterChain(out), so of cause the sync-response-body is empty! ) – boholder Mar 24 '23 at 07:10
0
val requestToCache = new ContentCachingRequestWrapper(request);
val responseToCache = new ContentCachingResponseWrapper(response);

This two line change to:

  if(!(request instanceof ContentCachingRequestWrapper)) {
     requestToCache = new ContentCachingRequestWrapper(request);
  }
  if(!(request instanceof ContentCachingResponseWrapper)){
     responseToCache = new ContentCachingResponseWrapper(request);
  }
Vivek Jain
  • 2,730
  • 6
  • 12
  • 27
  • sorry I didn't see your reply. could you please tell me why I need to do that? Also, what will be the else part of above if statements? – nishantv Jan 08 '21 at 07:49
0

I ran into the same problem, no http response after adding the ContentCachingResponseWrapper. It was an order issue, once I changed the order of the calls, everything work as expected. The next block is the wrong way, causing the symptoms

ContentCachingResponseWrapper wrappedResponse = new ContentCachingResponseWrapper(response);
wrappedResponse.copyBodyToResponse();
filterChain.doFilter(wrappedRequest, wrappedResponse);

This is the order that resolved my issue.

ContentCachingResponseWrapper wrappedResponse = new ContentCachingResponseWrapper(response);
filterChain.doFilter(wrappedRequest, wrappedResponse);
wrappedResponse.copyBodyToResponse();
Dan F
  • 31
  • 4