I've been doing some research using spring-webflux and I like to understand what should be the right way to handle errors using Router Functions.
I've created an small project to test a couple of scenarios, and I like to get feedback about it, and see what other people is doing.
So far what I doing is.
Giving the following routing function:
@Component public class HelloRouter { @Bean RouterFunction<?> helloRouterFunction() { HelloHandler handler = new HelloHandler(); ErrorHandler error = new ErrorHandler(); return nest(path("/hello"), nest(accept(APPLICATION_JSON), route(GET("/"), handler::defaultHello) .andRoute(POST("/"), handler::postHello) .andRoute(GET("/{name}"), handler::getHello) )).andOther(route(RequestPredicates.all(), error::notFound)); } }
I've do this on my handler
class HelloHandler { private ErrorHandler error; private static final String DEFAULT_VALUE = "world"; HelloHandler() { error = new ErrorHandler(); } private Mono<ServerResponse> getResponse(String value) { if (value.equals("")) { return Mono.error(new InvalidParametersException("bad parameters")); } return ServerResponse.ok().body(Mono.just(new HelloResponse(value)), HelloResponse.class); } Mono<ServerResponse> defaultHello(ServerRequest request) { return getResponse(DEFAULT_VALUE); } Mono<ServerResponse> getHello(ServerRequest request) { return getResponse(request.pathVariable("name")); } Mono<ServerResponse> postHello(ServerRequest request) { return request.bodyToMono(HelloRequest.class).flatMap(helloRequest -> getResponse(helloRequest.getName())) .onErrorResume(error::badRequest); } }
Them my error handler do:
class ErrorHandler { private static Logger logger = LoggerFactory.getLogger(ErrorHandler.class); private static BiFunction<HttpStatus,String,Mono<ServerResponse>> response = (status,value)-> ServerResponse.status(status).body(Mono.just(new ErrorResponse(value)), ErrorResponse.class); Mono<ServerResponse> notFound(ServerRequest request){ return response.apply(HttpStatus.NOT_FOUND, "not found"); } Mono<ServerResponse> badRequest(Throwable error){ logger.error("error raised", error); return response.apply(HttpStatus.BAD_REQUEST, error.getMessage()); } }
Here is the full sample repo:
https://github.com/LearningByExample/reactive-ms-example
While Initialising WebClient As mentioned in the code block, whenever a 5XX/4XX Error occurs, we can throw a user defined exception, and then execute error handling logic based on those user defined exceptions. Once this error Handler is defined, we can add it in the WebClient Initialisation.
Spring Webflux does not block a thread to handle each request, because no thread is kept waiting for something to be done (e.g. waiting for an answer from a database). As written in 1., it can be blocked while waiting for an answer from a database or from another service that is called via HTTP.
If you think, router functions are not the right place to handle exceptions, you throw HTTP Exceptions, that will result in the correct HTTP Error codes. For Spring-Boot (also webflux) this is:
import org.springframework.http.HttpStatus; import org.springframework.web.server.ResponseStatusException; . . . new ResponseStatusException(HttpStatus.NOT_FOUND, "Collection not found");})
spring securities AccessDeniedException will be handled correctly, too (403/401 response codes).
If you have a microservice, and want to use REST for it, this can be a good option, since those http exceptions are quite close to business logic, and should be placed near the business logic in this case. And since in a microservice you shouldn't have to much businesslogic and exceptions, it shouldn't clutter your code, too... (but of course, it all depends).
Spring 5 provides a WebHandler, and in the JavaDoc, there's the line:
Use HttpWebHandlerAdapter to adapt a WebHandler to an HttpHandler. The WebHttpHandlerBuilder provides a convenient way to do that while also optionally configuring one or more filters and/or exception handlers.
Currently, the official documentation suggests that we should wrap the router function into an HttpHandler before booting up any server:
HttpHandler httpHandler = RouterFunctions.toHttpHandler(routerFunction);
With the help of WebHttpHandlerBuilder, we can configure custom exception handlers:
HttpHandler httpHandler = WebHttpHandlerBuilder.webHandler(toHttpHandler(routerFunction)) .prependExceptionHandler((serverWebExchange, exception) -> { /* custom handling goes here */ return null; }).build();
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With