Gestion des erreurs dans Spring WebFlux

Gestion des erreurs dans Spring WebFlux

1. Vue d'ensemble

Dans ce didacticiel,we’ll look at various strategies available for handling errors in a Spring WebFlux project en parcourant un exemple pratique.

Nous indiquerons également dans quels domaines il peut être avantageux d'utiliser une stratégie plutôt qu'une autre et fournirons un lien vers le code source complet à la fin.

2. Mise en place de l'exemple

La configuration de Maven est la même que celle de notreprevious article, qui fournit une introduction à Spring Webflux.

Pour notre exemple,we’ll usea RESTful endpoint that takes a username as a query parameter and returns “Hello username” en conséquence.

Commençons par créer une fonction de routeur qui achemine la requête/hello vers une méthode nomméehandleRequest dans le gestionnaire transmis:

@Bean
public RouterFunction routeRequest(Handler handler) {
    return RouterFunctions.route(RequestPredicates.GET("/hello")
      .and(RequestPredicates.accept(MediaType.TEXT_PLAIN)),
        handler::handleRequest);
    }

Ensuite, nous allons définir la méthodehandleRequest() qui appelle la méthodesayHello() et trouve un moyen d'inclure / renvoyer son résultat dans le corps deServerResponse:

public Mono handleRequest(ServerRequest request) {
    return
      //...
        sayHello(request)
      //...
}

Enfin, la méthodesayHello() est une méthode utilitaire simple qui concatène les «Hello»String et le nom d'utilisateur:

private Mono sayHello(ServerRequest request) {
    //...
    return Mono.just("Hello, " + request.queryParam("name").get());
    //...
}

Tant qu'un nom d'utilisateur est présent dans le cadre de notre requête, par exemple. si le point final est appelé comme“/hello?username=Tonni “, alors, ce point final fonctionnera toujours correctement.

Cependant,if we call the same endpoint without specifying a username e.g. “/hello”, it will throw an exception.

Ci-dessous, nous verrons où et comment nous pouvons réorganiser notre code pour gérer cette exception dans WebFlux.

3. Gestion des erreurs au niveau fonctionnel

Il existe deux opérateurs clés intégrés dans les APIMono etFlux pour gérer les erreurs au niveau fonctionnel.

Explorons-les brièvement et leur utilisation.

3.1. Gestion des erreurs aveconErrorReturn

We can use onErrorReturn() to return a static default value chaque fois qu'une erreur se produit:

public Mono handleRequest(ServerRequest request) {
    return sayHello(request)
      .onErrorReturn("Hello Stranger")
      .flatMap(s -> ServerResponse.ok()
      .contentType(MediaType.TEXT_PLAIN)
      .syncBody(s));
}

Ici, nous renvoyons un "Hello Stranger" statique chaque fois que la fonction de concaténation boguéesayHello() lève une exception.

3.2. Gestion des erreurs aveconErrorResume

Il y a trois façons d'utiliseronErrorResume pour gérer les erreurs:

  • Calculer une valeur de repli dynamique

  • Exécuter un chemin alternatif avec une méthode de secours

  • Attrapez, emballez et relancez une erreur, par exemple en tant qu'exception commerciale personnalisée

Voyons comment nous pouvons calculer une valeur:

public Mono handleRequest(ServerRequest request) {
    return sayHello(request)
      .flatMap(s -> ServerResponse.ok()
      .contentType(MediaType.TEXT_PLAIN)
          .syncBody(s))
        .onErrorResume(e -> Mono.just("Error " + e.getMessage())
          .flatMap(s -> ServerResponse.ok()
            .contentType(MediaType.TEXT_PLAIN)
            .syncBody(s)));
}

Ici, nous renvoyons une chaîne composée du message d'erreur obtenu dynamiquement ajouté à la chaîne "Erreur" chaque fois quesayHello() lève une exception.

Ensuite, disonscall a fallback method when an error occurs:

public Mono handleRequest(ServerRequest request) {
    return sayHello(request)
      .flatMap(s -> ServerResponse.ok()
      .contentType(MediaType.TEXT_PLAIN)
      .syncBody(s))
      .onErrorResume(e -> sayHelloFallback()
      .flatMap(s ->; ServerResponse.ok()
      .contentType(MediaType.TEXT_PLAIN)
      .syncBody(s)));
}

Ici, nous appelons la méthode alternativesayHelloFallback() chaque fois quesayHello() lève une exception.

La dernière option utilisantonErrorResume() est decatch, wrap, and re-throw an error par ex. enNameRequiredException:

public Mono handleRequest(ServerRequest request) {
    return ServerResponse.ok()
      .body(sayHello(request)
      .onErrorResume(e -> Mono.error(new NameRequiredException(
        HttpStatus.BAD_REQUEST,
        "username is required", e))), String.class);
}

Ici, nous lançons une exception personnalisée avec le message: "nom d'utilisateur est requis" chaque fois quesayHello() lève une exception.

4. Traitement des erreurs au niveau mondial

Jusqu'à présent, tous les exemples que nous avons présentés ont abordé la gestion des erreurs au niveau fonctionnel.

We can, however, opt to handle our WebFlux errors at a global level. Pour ce faire, nous n'avons besoin que de deux étapes:

  • Personnaliser les attributs de réponse d'erreur globale

  • Implémenter le gestionnaire d'erreur global

L'exception que notre gestionnaire génère sera automatiquement traduite en un statut HTTP et un corps d'erreur JSON. Pour les personnaliser, nous pouvons simplementextend the DefaultErrorAttributes class et remplacer sa méthodegetErrorAttributes():

public class GlobalErrorAttributes extends DefaultErrorAttributes{

    @Override
    public Map getErrorAttributes(ServerRequest request,
      boolean includeStackTrace) {
        Map map = super.getErrorAttributes(
          request, includeStackTrace);
        map.put("status", HttpStatus.BAD_REQUEST);
        map.put("message", "username is required");
        return map;
    }

}

Ici, nous voulons que l'état:BAD_REQUEST et le message: «username is required» soient renvoyés dans le cadre des attributs d'erreur lorsqu'une exception se produit.

Ensuite, disonsimplement the Global Error Handler. Pour cela, Spring fournit une classeAbstractErrorWebExceptionHandler pratique que nous pouvons étendre et implémenter dans la gestion des erreurs globales:

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

    // constructors

    @Override
    protected RouterFunction getRoutingFunction(
      ErrorAttributes errorAttributes) {

        return RouterFunctions.route(
          RequestPredicates.all(), this::renderErrorResponse);
    }

    private Mono renderErrorResponse(
       ServerRequest request) {

       Map errorPropertiesMap = getErrorAttributes(request, false);

       return ServerResponse.status(HttpStatus.BAD_REQUEST)
         .contentType(MediaType.APPLICATION_JSON_UTF8)
         .body(BodyInserters.fromObject(errorPropertiesMap));
    }
}

Dans cet exemple, nous définissons l'ordre de notre gestionnaire d'erreurs global sur -2. C'est àgive it a higher priority than the DefaultErrorWebExceptionHandler qui est enregistré à@Order(-1).

L’objeterrorAttributes sera la copie exacte de celui que nous transmettons au constructeur du gestionnaire d’exceptions Web. Cela devrait idéalement être notre classe personnalisée d'attributs d'erreur.

Ensuite, nous déclarons clairement que nous voulons acheminer toutes les demandes de gestion des erreurs vers la méthoderenderErrorResponse().

Enfin, nous obtenons les attributs d'erreur et les insérons dans un corps de réponse du serveur.

Cela produit ensuite une réponse JSON avec les détails de l'erreur, l'état HTTP et le message d'exception pour les clients de la machine. Pour les clients de navigateur, il dispose d’un gestionnaire d’erreur «étiquette blanche» qui restitue les mêmes données au format HTML. Cela peut, bien sûr, être personnalisé.

5. Conclusion

Dans cet article, nous avons examiné diverses stratégies disponibles pour le traitement des erreurs dans un projet Spring WebFlux et avons indiqué les domaines dans lesquels il serait avantageux d'utiliser une stratégie plutôt qu'une autre.

Comme promis, le code source complet qui accompagne l'article est disponibleover on GitHub.