Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring webflux filter: How to get the reactor context after the query execution?

Spring boot 2.1.5 Project Reactor 3.2.9

In my webflux project I extensively use the reactor contexts in order to pass around some values.

I set up a filter and am trying to log things which are in the context and to log different things in case of error/success.

I have checked this documentation: https://projectreactor.io/docs/core/release/reference/#context

I still struggle (especially on the error side) to get it.

Basically, I have this filter:

@Component
public class MdcWebFilter implements WebFilter {

    @NotNull
    @Override
    public Mono<Void> filter(@NotNull ServerWebExchange serverWebExchange,
                             WebFilterChain webFilterChain) {

        Mono<Void> filter = webFilterChain.filter(serverWebExchange);

        return filter
            .doAfterSuccessOrError(new BiConsumer<Void, Throwable>() {
                @Override
                public void accept(Void aVoid, Throwable throwable) {
                    //Here i would like to be able to access to the request's context
                    System.out.println("doAfterSuccessOrError:" + (throwable==null ? "OK" : throwable.getMessage())+"log the context");
                }
            })
            .doOnEach(new Consumer<Signal<Void>>() {
                @Override
                public void accept(Signal<Void> voidSignal) {
                    //Here i have the context but i don't really know if i am in success or error
                    System.out.println("doOnEach:"+"Log OK/KO and the exception" + voidSignal.getContext());
                }
            })
            .subscriberContext(context -> context.put("somevar", "whatever"));
    }

}

I also tried with a flatMap() and a Mono.subscriberContext() but i am not sure how to plug correctly with the filter (especially in error).

What would be the best way to achieve this ?

like image 782
Arnaud Villevieille Avatar asked Jun 13 '19 06:06

Arnaud Villevieille


2 Answers

I'm not sure whether it possible access request reactor context from within WebFilter. WebFilter context exists in another Mono chain. But it is do possible to assosiate attributes with request and able to fetch these attributes during request life time RequestContextHolder for Reactive Web Very similar to Servlet API.

Controller:

@GetMapping(path = "/v1/customers/{customerId}")
public Mono<Customer> getCustomerById(
    @PathVariable("customerId") String customerId,
    ServerWebExchange serverWebExchange)
{
  serverWebExchange.getAttributes().put("traceId", "your_trace_id");
  return customerService.findById(customerId);
}

WebFilter:

public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
    // ...
    String traceId = exchange.getAttributeOrDefault("traceId", "default_value_goes_here");
    //...
    return chain.filter(exchange);
}
like image 134
Valeriy Avatar answered Oct 19 '22 02:10

Valeriy


I know this is probably not the cleanest of the solutions, but you could create a container class that would keep the context between your two callbacks.

You would store the context at doOnEach and then you would be able to load it back at doAfterSuccessOrError:

    public Mono<Void> filter(@NotNull ServerWebExchange serverWebExchange, WebFilterChain webFilterChain) {

        @lombok.Data
        class MyContextContainer {
            private Context context;
        }

        MyContextContainer container = new MyContextContainer();

        Mono<Void> filter = webFilterChain.filter(serverWebExchange);

        return filter
            .doAfterSuccessOrError(new BiConsumer<Void, Throwable>() {
                @Override
                public void accept(Void aVoid, Throwable throwable) {
                    // load the context here
                    Context context = container.getContext();
                    // then do your stuff here
                }
            })
            .doOnEach(new Consumer<Signal<Void>>() {
                @Override
                public void accept(Signal<Void> voidSignal) {
                    // store the context here
                    container.setContext(voidSignal.getContext());
                }
            })
            .subscriberContext(context -> context.put("somevar", "whatever"));
    }

It doesn't need to be a class, really. It could be an AtomicReference, but you get the idea.

Again, this might be just a workaround. I believe there must be a better way to access the context.

like image 2
Guillermo Calvo Avatar answered Oct 19 '22 01:10

Guillermo Calvo