web-dev-qa-db-fra.com

Gestion des exceptions et renvoi du code HTTP approprié avec webflux

J'utilise les terminaux fonctionnels de WebFlux. Je traduis les exceptions envoyées par la couche de service en un code d'erreur HTTP à l'aide de onErrorResume:

public Mono<String> serviceReturningMonoError() {
    return Mono.error(new RuntimeException("error"));  
}

public Mono<ServerResponse> handler(ServerRequest request) {
    return serviceReturningMonoError().flatMap(e -> ok().syncBody(e))
                            .onErrorResume( e -> badRequest(e.getMessage()));
}

Cela fonctionne bien dès que le service retourne un Mono. Que dois-je faire en cas de service renvoyant un flux?

public Flux<String> serviceReturningFluxError() {
    return Flux.error(new RuntimeException("error"));  
}

public Mono<ServerResponse> handler(ServerRequest request) {
    ???
}

Modifier

J'ai essayé l'approche ci-dessous, mais malheureusement cela ne fonctionne pas. Le fichier Flux.error n'est pas géré par la variable onErrorResume et est propagé au framework. Lorsque l'exception est décapsulée lors de la sérialisation de la réponse http, la gestion des exceptions de démarrage de printemps intercepte cette erreur et la convertit en 500.

public Mono<ServerResponse> myHandler(ServerRequest request) {
      return ok().contentType(APPLICATION_JSON).body( serviceReturningFluxError(), String.class)
             .onErrorResume( exception -> badRequest().build());
}

Je suis réellement surpris du comportement, est-ce un bug?

5
Nicolas Barbé

Votre premier exemple utilise Mono (c'est-à-dire au plus une valeur). Il fonctionne donc bien avec Mono<ServerResponse>; la valeur sera résolue de manière asynchrone en mémoire et, en fonction du résultat, nous renverrons une réponse différente ou gérerons les exceptions métier manuellement.

Dans le cas d'une Flux (c'est-à-dire des valeurs 0..N), une erreur peut survenir à tout moment.

Dans ce cas, vous pouvez utiliser l'opérateur collectList pour transformer votre Flux<String> en un Mono<List<String>>, avec un gros avertissement: tous les éléments seront mis en mémoire tampon. Si le flux de données est important ou si votre contrôleur/client s'appuie sur le flux de données en continu, ce n'est pas le meilleur choix ici.

Je crains de ne pas avoir une meilleure solution à ce problème et voici pourquoi: puisqu’une erreur peut survenir à tout moment pendant la Flux, il n’est pas garanti que nous puissions changer le statut et la réponse HTTP: les choses auraient peut-être déjà été vidées. le réseau. C'est déjà le cas lorsque vous utilisez Spring MVC et que vous retournez une InputStream ou une Resource.

La fonctionnalité de traitement des erreurs Spring Boot tente d’écrire une page d’erreur et de modifier le statut HTTP (voir ErrorWebExceptionHandler et les classes d’implémentation). Toutefois, si la réponse est déjà validée, elle enregistre les informations relatives aux erreurs et vous indique que le statut HTTP est probablement incorrect.

2
Brian Clozel

J'ai trouvé un autre moyen de résoudre ce problème en interceptant l'exception dans la méthode body et en la mappant sur ResponseStatusException

public Mono<ServerResponse> myHandler(ServerRequest request) {
    return ok().contentType(MediaType.APPLICATION_JSON)
            .body( serviceReturningFluxError()
                    .onErrorMap(RuntimeException.class, e -> new ResponseStatusException( BAD_REQUEST, e.getMessage())), String.class);
}

Avec cette approche, Spring gère correctement la réponse et renvoie le code d'erreur HTTP attendu.

1
Nicolas Barbé