14

I'm looking into optimal ways of accessing the HTTP request and response bodies for tracing in a Spring reactive application.

For previous versions, we've leveraged Servlet filters and Servlet request wrappers to consume the incoming request's input stream and hold a copy of it for asynchronous processing of the traces (we send them to Elasticsearch).

But for a Spring reactive app (using webflux), I'm wondering what'd be the most appropriate way to access the requests before they're decoded. Any thoughts?

javabeats
  • 1,082
  • 3
  • 12
  • 26
  • To get request-body you can easily use the `CacheRequestBody` feature, check this answer: https://stackoverflow.com/a/75182399/6157415 – Mike D3ViD Tyson Jan 20 '23 at 10:02

1 Answers1

16

Turns out this can be achieved using the provided decorators: ServerWebExchangeDecorator, ServerHttpRequestDecorator and ServerHttpResponseDecorator, respectively.

Here's a sample request decorator that accumulates the DataBuffer contents as its read by the request's default subscriber:

@Slf4j
public class CachingServerHttpRequestDecorator extends ServerHttpRequestDecorator {

    @Getter
    private final OffsetDateTime timestamp = OffsetDateTime.now();
    private final StringBuilder cachedBody = new StringBuilder();

    CachingServerHttpRequestDecorator(ServerHttpRequest delegate) {
        super(delegate);
    }

    @Override
    public Flux<DataBuffer> getBody() {
        return super.getBody().doOnNext(this::cache);
    }

    @SneakyThrows
    private void cache(DataBuffer buffer) {
        cachedBody.append(UTF_8.decode(buffer.asByteBuffer())
         .toString());
    }

    public String getCachedBody() {
        return cachedBody.toString();
    }

Just make sure that, when you decorate the ServerWebExchange passed by the WebFilter, you also override getRequest() to return the request decorator as well:

public final class PartnerServerWebExchangeDecorator extends ServerWebExchangeDecorator {

    private final ServerHttpRequestDecorator requestDecorator;
    private final ServerHttpResponseDecorator responseDecorator;

    public PartnerServerWebExchangeDecorator(ServerWebExchange delegate) {
        super(delegate);
        this.requestDecorator = new PartnerServerHttpRequestDecorator(delegate.getRequest());
        this.responseDecorator = new PartnerServerHttpResponseDecorator(delegate.getResponse());
    }

    @Override
    public ServerHttpRequest getRequest() {
        return requestDecorator;
    }

    @Override
    public ServerHttpResponse getResponse() {
        return responseDecorator;
    }

}

On the filter:

@Component
public class TracingFilter implements WebFilter {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        return chain.filter(new PartnerServerWebExchangeDecorator(exchange));
    }
}

Which can be used as such (beware the statically imported functions):

@Bean
public HttpHandler myRoute(MyHandler handler) {
    final RouterFunction<ServerResponse> routerFunction =
        route(POST("/myResource"), handler::persistNotification);
    return webHandler(toWebHandler(routerFunction))
        .filter(new TracingFilter())
        .build();
}
javabeats
  • 1,082
  • 3
  • 12
  • 26
  • 1
    Can you give an example of how to do this in `WebFilter` of Spring Boot 2 ? Can't get the request body copied correctly thus when reading the body the payload is drained and the request fails – Gilad Peleg Dec 02 '18 at 11:57
  • 1
    I was able to make it work with the decorators above. I'll double check when I get to work and post if I find any differences. – javabeats Dec 03 '18 at 12:47
  • 1
    I've updated the answer with the exact code being used by the decorator in our production service. – javabeats Dec 03 '18 at 19:42
  • 1
    @javabeats Thanks for sharing this great piece of code with us. Would you mind sharing, how you came up with this solution? We've spend ages searching for a possibility that doesn't already consume the request body before it is read by the request mapping. – PeMa Aug 23 '19 at 13:23
  • I remembered that Spring MVC used to offer decorators for ServletRequest/Response objects, and got curious to see if something similar was offered via webflux. Browsing the code I found these, but at the time there was little to no documentation around them. – javabeats Aug 26 '19 at 17:13
  • @javabeats I am unable use this cache in multiple filters. Please, can you suggest how can we use this cache body in multiple filters as well ? – vaspaean Nov 19 '21 at 22:26
  • Consuming the body in filter removes the body for controller usage. Any workarounds for this problem ? – Giri Mar 20 '23 at 06:26