2

With a very simple project, error handling is unclear.

public class WebfluxApplication {
    public static void main(String[] args) {
        SpringApplication.run(WebfluxApplication.class, args);
    }
}
public class EndpointRouter
{

@Bean
public WebClient webClient() {
    return WebClient.builder().build();
}

@Bean
public RouterFunction<ServerResponse> goodRoute(Handler handler, ConfigProperties configProperties) {
    log.info(configProperties.toString());
    return
            RouterFunctions.route(RequestPredicates.GET("/api/v1/integration/ok"),
                    handler::goodEndpoint)
    .and(
            RouterFunctions.route(RequestPredicates.GET("/api/v1/integration/notfound") {
                    handler::badEndpoint));
}
public Mono<ServerResponse> goodEndpoint(ServerRequest r) {
    return ServerResponse.ok().build();
}

public Mono<ServerResponse> badEndpoint(ServerRequest r) {
    var result = service.badEndpoint();
    return ServerResponse
            .ok()
            .body(result, String.class);
}
public class Service
{
private final WebClient webClient;
private final ConfigProperties configProperties;

public Service(WebClient webClient, ConfigProperties configurationProperties) {
    this.webClient = webClient;
    this.configProperties = configurationProperties;
}

public Mono<String> badEndpoint() {
    return webClient
            .get()
            .uri(configProperties.getNotfound())
            .retrieve()
            .onStatus(HttpStatus::is4xxClientError, clientResponse -> {
                if(clientResponse.statusCode().equals(HttpStatus.NOT_FOUND)){
                    return Mono.error(new HttpClientErrorException(HttpStatus.NOT_FOUND,
                            "Entity not found."));
                } else {
                    return Mono.error(new HttpClientErrorException(HttpStatus.INTERNAL_SERVER_ERROR));
                }
            })
            .bodyToMono(String.class);
}

From reading the docs, I shouldn't need to set up a Global error handler for the entire project, I should be able to handle the 404, and return a 404 back to the original caller.

This is the output

   2020-08-29 16:52:46.301 ERROR 25020 --- [ctor-http-nio-4] a.w.r.e.AbstractErrorWebExceptionHandler : [b339763e-1]  500 Server Error for HTTP GET "/api/v1/integration/notfound"

org.springframework.web.client.HttpClientErrorException: 404 Entity not found.
    at com.stevenpg.restperformance.webflux.Service.lambda$badEndpoint$0(Service.java:30) ~[main/:na]
    Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: 
Error has been observed at the following site(s):
    |_ checkpoint ⇢ 404 from GET https://httpbin.org/status/404 [DefaultWebClient]
    |_ checkpoint ⇢ Handler com.stevenpg.restperformance.webflux.EndpointRouter$$Lambda$445/0x00000008003ae040@7799b58 [DispatcherHandler]
    |_ checkpoint ⇢ HTTP GET "/api/v1/integration/notfound" [ExceptionHandlingWebHandler]

I've also tried using onErrorResume on my Mono from the service, but it never works correct and requires a return of Mono rather than Mono.

The documentation and Stack Overflow don't have many/any examples of making a WebClient call inside a RouterFunction and handling different types of responses.

StevenPG
  • 311
  • 4
  • 16
  • Why do you need that to handle badRequest. By default, in Spring boot nonmapped urls return 404 error. Besides your 500 error is due to your /api/v1/integration/notfound not being obsolete. It should be like http://localhost:8080/api/v1/integration/notfound when called from WebClient. But this time there will be cycle. What is your purpose? – gungor Aug 29 '20 at 23:17
  • You can see from the error, the 404 is coming from https://httpbin.org/status/404. I'm trying to handle a 404 from an external client. If our endpoint calls another, it ought to be able to handle any status code. The /ok endpoint works correctly. – StevenPG Aug 29 '20 at 23:20

1 Answers1

2

Just adding onErrorResume solves your problem. Otherwise error is handled in AbstractErrorWebExceptionHandler.

 return webClient
        .get()
        .uri(configProperties.getNotfound())
        .retrieve()
        .onStatus(HttpStatus::is4xxClientError, clientResponse -> {
            if(clientResponse.statusCode().equals(HttpStatus.NOT_FOUND)){
                return Mono.error(new HttpClientErrorException(HttpStatus.NOT_FOUND,
                        "Entity not found."));
            } else {
                return Mono.error(new HttpClientErrorException(HttpStatus.INTERNAL_SERVER_ERROR));
            }
        })
        .bodyToMono(String.class)
        .onErrorResume( e -> Mono.just(e.getMessage()) );
gungor
  • 393
  • 3
  • 13
  • I was totally misunderstanding exactly how the error handling works, this worked perfectly! Thank you! – StevenPG Aug 30 '20 at 13:31