8
votes

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.

My purpose here is to be able to get the context inside of the Exception handler.

A simple example:

@Component
@Order(-2)
public class GlobalErrorWebExceptionHandler extends
    AbstractErrorWebExceptionHandler {

    public GlobalErrorWebExceptionHandler(ErrorAttributes errorAttributes, ResourceProperties resourceProperties, ApplicationContext applicationContext, ServerCodecConfigurer configurer) {
        super(errorAttributes, resourceProperties, applicationContext);
        this.setMessageWriters(configurer.getWriters());
    }

    @Override
    protected RouterFunction<ServerResponse> getRoutingFunction(
      ErrorAttributes errorAttributes) {
        return RouterFunctions
            .route(RequestPredicates.all(), request -> {
                Throwable error = errorAttributes.getError(request);

                return ServerResponse.status(500).syncBody(error.getMessage()).doOnEach(serverResponseSignal -> {
                    //Here the context is empty because i guess i created a new flux
                    System.out.println("What is in my context ? " + serverResponseSignal.getContext());
                    System.out.println("What is my exception ? " + error);
                });
            });
    }

}

I am not sure how to achieve that goal in a clean way with reactor. Anyone an idea ?

1
this is not possible, because you have left the reactive context, what you have to do is to that where you are throwing your exception you have to pass your context values from your exception, then map out the values in the exceptionToerktumlare
@ThomasAndolf would you give us some clue where Spring WebFlux left reactive context during the exceptions handler?Chris
This usually happens when the exception is thrown from a Mono.errorToerktumlare
After an error in Reactor we can recover, right? Isn't it a problem if we lose the context?Arnaud Villevieille
I wonder if it's related to this statement: "Context simply cannot be reached outside of a continuous chain of Reactor operators, so there's no way around it. Can't the serializer be invoked from a Mono.fromCallable ?" seen in this issue github.com/reactor/reactor-core/issues/1667Pinelopi Kouleri

1 Answers

7
votes

I found a trick to be able to achieve that. It does not sound clean but it seems to work.

In a filter, I keep the subscribed context into a request attribute:

@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
            .subscriberContext((context) -> {
                //This code is executed before the query

                Context contextTmp = context.put("whatever", "whichever");

                //I save the context in an attribute attribute
                serverWebExchange.getAttributes().put("context", contextTmp);

                return contextTmp;
            });
    }
}

Then after that it is possible to get it from the reactive error handler:

@Component
@Order(-2)
public class GlobalErrorWebExceptionHandler extends
    AbstractErrorWebExceptionHandler {

    public GlobalErrorWebExceptionHandler(ErrorAttributes errorAttributes, ResourceProperties resourceProperties, ApplicationContext applicationContext, ServerCodecConfigurer configurer) {
        super(errorAttributes, resourceProperties, applicationContext);
        this.setMessageWriters(configurer.getWriters());
    }

    @Override
    protected RouterFunction<ServerResponse> getRoutingFunction(
      ErrorAttributes errorAttributes) {
        return RouterFunctions
            .route(RequestPredicates.all(), request -> {
                Throwable error = errorAttributes.getError(request);

                //The context will be visible in the whole error handling flow
                return ServerResponse.status(500).syncBody(error.getMessage())
                   .subscriberContext((Context) request.attribute("context").orElse(Context.empty())));
            });
    }

}