Copy of the request/response body on a Spring reactive app?

javabeats :

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 :

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();
}

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=468912&siteId=1