Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

onErrorResume not working as expected

I'm trying to use WebFlux and I'm seeing a behavior I don't quite understand, I suspect this is a bug in WebFlux or possibly Reactor, but I need confirmation.

I've attempted to create a minimally reproducible case that consist of a very simple HandlerFunction that attempts to return an 200 response, but throws an exception during body creation and then attempts to use onErrorResume to instead return a 404 response.

The handler looks like so:

public Mono<ServerResponse> reproduce(ServerRequest request){
        return ServerResponse.ok()
        .contentType(APPLICATION_JSON)
        .body(Mono.fromCallable(this::trigger),String.class)
        .onErrorResume(MinimalExampleException.class,e->ServerResponse.notFound().build());
}

I would expect when calling the associated endpoint that I would get a 404 response. Instead what I'm seeing is a 500 response with log messages indicating Spring believes there was an unhandled exception during request processing.

When I breakpoint inside of onErrorResume I see two handlers being registered, the one I register in the method above, as well as one that's being registered by Spring (inside of RouterFunctions.toHttpHandler) for instances of ResponseStatusException. Then during processing of the request I see only the second handler (the one registered by Spring) being called, not matching on the exception being thrown and then falling through to the root level handler.

Near as I can tell, Spring is overwriting onErrorResume at the router level preventing the one I registered in the Handler from being called. Is this the expected behavior? Is there a better way to accomplish what I'm attempting?

like image 357
Orclev Avatar asked May 30 '17 22:05

Orclev


People also ask

What is onErrorResume () in Java?

The final option using onErrorResume() is to catch, wrap and re-throw an error, e.g., as a NameRequiredException: public Mono<ServerResponse> handleRequest(ServerRequest request) { return ServerResponse.ok() .body(sayHello(request) .onErrorResume(e -> Mono.error(new NameRequiredException( HttpStatus.BAD_REQUEST, " ...

What does Mono empty return?

Mono. empty() is a method invocation that returns a Mono that that completes without emitting any item.

What is a Mono in java?

It is a specialization of Flux that can emit at most 1 <T> element: a Mono is either valued (complete with element), empty (complete without element) or failed (error).


1 Answers

The framework indeed has a "catch-all" that is invoked before whatever follows .body(...) can be invoked. This is by design and I think it will be hard to avoid.

I see 3 solutions:

  1. Turn the operators chain around

Like so:

return Mono.fromCallable(this::trigger)
           .flatMap(s -> ServerResponse.ok()
                        .contentType(MediaType.TEXT_PLAIN)
                        .syncBody(s))
           .onErrorResume(MinimalExampleException.class,
                        e -> ServerResponse.notFound().build());
  1. Use a ResponseStatusException

You could put the onErrorResume at the same level as the fromCallable() (inside the body call) to transform the specific error into a ResponseStatusException via an error mono:

Mono.fromCallable(this::trigger)
    .onErrorResume(MinimalExampleException.class,
        e->Mono.error(new ResponseStatusException(404, "reason message", e)))
  1. Use an exception handler

You should be able to annotate a method with @ExceptionHandler to deal with your particular exception.

like image 61
Simon Baslé Avatar answered Sep 24 '22 13:09

Simon Baslé