10

I am receiving some XML payload in a POST request and want to see the payload as received for debugging purposes.

Below (my custom WebFilter) code logs the URI and request headers as expected but not the request body/payload, must be something wrong with my reactive code -

       final ServerHttpRequest request = exchange.getRequest();
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        LOGGER.info("Request: uri={}", request.getURI());
        LOGGER.info("Request: headers={}", request.getHeaders().entrySet());
        request.getBody().doOnNext(dataBuffer -> {
            try {
                Channels.newChannel(baos).write(dataBuffer.asByteBuffer().asReadOnlyBuffer());
                String body = new String(baos.toByteArray());
                LOGGER.info("Request: payload={}", body);
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    baos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });

I have looked at Brian's response for post below and followed it but since there is no code, I am probably making some silly mistake

How to log request and response bodies in Spring WebFlux

Updated Code

@Configuration
public class RequestFilter implements WebFilter {

    private static final Logger LOGGER = LoggerFactory.getLogger(RequestFilter.class);

    @Override
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public Mono<Void> filter(ServerWebExchange serverWebExchange,
                             WebFilterChain webFilterChain) {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        serverWebExchange.getRequest().getBody().doOnNext(dataBuffer -> {
            try {
                Channels.newChannel(baos).write(dataBuffer.asByteBuffer().asReadOnlyBuffer());
                String body = new String(baos.toByteArray());
                LOGGER.info("Request: payload={}", body);
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    baos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });
        return webFilterChain.filter(serverWebExchange);
    }
}
Akhil Bojedla
  • 1,968
  • 12
  • 19
Oten
  • 186
  • 1
  • 1
  • 13
  • you are breaking the chain here `request.getBody().doOnNext( ... ` – Toerktumlare May 10 '20 at 12:05
  • @ThomasAndolf - you mean i need to flat map it before doOn ? Thanks! – Oten May 10 '20 at 15:56
  • no i'm meaning that it looks like you are ignoring the return value https://stackoverflow.com/a/61541336/1840146 – Toerktumlare May 10 '20 at 18:33
  • So i get the required request body if I doOnNext().subscribe() but then like you have explained in your referred answer above, I dont have the data available anymore for further consumption. I have gone through your post multiple times but cant find the right place to put 'return Mono.empty()'. Thank you ! – Oten May 10 '20 at 20:34
  • 1
    okey, you have clearly never worked with webflux before. It's not imperative programming. You need to always chain on, you can't just write `request.getBody().doOnNext( ...` and ignore the response. Please produce a small reproducible example so i can se the bigger picture. Your small code snippet is not enough. – Toerktumlare May 10 '20 at 21:26
  • Note that the approach with overriding of the `getBody()` method only works for controllers which access the body (see @RequestBody). – OSGI Java Sep 08 '22 at 13:48
  • you can use the CacheRequestBody feature, check this answer: https://stackoverflow.com/a/75182399/6157415 – Mike D3ViD Tyson Jan 20 '23 at 09:58

1 Answers1

15

You are doing it partially right except that you are extracting the body at the wrong place. You will have to do it in a ServerHttpRequestDecorator implementation and plug it in the WebFilter implementation.

public class RequestLoggingDecorator extends ServerHttpRequestDecorator {

  private static final Logger LOGGER = LoggerFactory.getLogger(RequestLoggingDecorator.class);

  public RequestLoggingDecorator(ServerHttpRequest delegate) {
    super(delegate);
  }

  @Override
  public Flux<DataBuffer> getBody() {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    return super.getBody().doOnNext(dataBuffer -> {
      try {
        Channels.newChannel(baos).write(dataBuffer.asByteBuffer().asReadOnlyBuffer());
        String body = new String(baos.toByteArray(), StandardCharsets.UTF_8);
        LOGGER.info("Request: payload={}", body);
      } catch (IOException e) {
        e.printStackTrace();
      } finally {
        try {
          baos.close();
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
    });
  }
}

Then you can configure this in a WebFilter implementation as follows:

@Configuration
public class RequestLoggingFilter implements WebFilter {
  @Override
  public Mono<Void> filter(ServerWebExchange serverWebExchange, WebFilterChain webFilterChain) {
    ServerWebExchangeDecorator decorator =
        new ServerWebExchangeDecorator(serverWebExchange) {
          @Override
          public ServerHttpRequest getRequest() {
            return new RequestLoggingDecorator(serverWebExchange.getRequest());
          }
        };

    return webFilterChain.filter(decorator);
  }
}

This should log your request body on every incoming Http request.

Akhil Bojedla
  • 1,968
  • 12
  • 19
  • 3
    How do I programmatically wire this `WebFilter` in the `WebClient.Builder` without using the `@Configuration` annotations and autoconfiguration? – ChrisDekker Apr 09 '21 at 13:28
  • Note that the approach with overriding of the `getBody()` method only works for controllers which access the body (see @RequestBody). – OSGI Java Sep 08 '22 at 13:48
  • this solution is blocking, Intellij warns against using `ByteArrayOutputStream baos` and `Channels.newChannel(baos).write()` – downvoteit Feb 06 '23 at 09:47