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