web-dev-qa-db-fra.com

quelle est la bonne façon de gérer les erreurs dans spring-webflux

J'ai fait quelques recherches avec spring-webflux et j'aime bien comprendre quelle devrait être la bonne façon de gérer les erreurs avec les fonctions de routeur.

J'ai créé un petit projet pour tester plusieurs scénarios et j'aime bien avoir des retours à ce sujet et voir ce que font les autres.

Jusqu'à présent, ce que je fais est.

Donner la fonction de routage suivante:

@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));
    }
}

J'ai fait ça sur mon 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);
    }
}

Eux mon gestionnaire d'erreur faire:

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());
    }
}

Voici l'échantillon complet de repo:

https://github.com/LearningByExample/reactive-ms-example

9
Juan Medina

Spring 5 fournit un WebHandler , et dans JavaDoc, il y a la ligne:

Utilisez HttpWebHandlerAdapter pour adapter un WebHandler à un HttpHandler. WebHttpHandlerBuilder fournit un moyen pratique de le faire tout en configurant éventuellement un ou plusieurs filtres et/ou gestionnaires d'exceptions.

Actuellement, la documentation officielle suggère que nous devrions envelopper la fonction de routeur dans un gestionnaire HttpHandler avant de démarrer un serveur:

HttpHandler httpHandler = RouterFunctions.toHttpHandler(routerFunction);

Avec l'aide de WebHttpHandlerBuilder , nous pouvons configurer des gestionnaires d'exceptions personnalisés:

HttpHandler httpHandler = WebHttpHandlerBuilder.webHandler(toHttpHandler(routerFunction))
  .prependExceptionHandler((serverWebExchange, exception) -> {

      /* custom handling goes here */
      return null;

  }).build();
6
punkboyee

Si vous pensez que les fonctions de routeur ne sont pas le bon endroit pour gérer les exceptions, vous lancez des exceptions HTTP, ce qui donnera les codes d'erreur HTTP corrects . Pour Spring-Boot (également webflux), il s'agit de:

  import org.springframework.http.HttpStatus;
  import org.springframework.web.server.ResponseStatusException;
  .
  .
  . 

  new ResponseStatusException(HttpStatus.NOT_FOUND,  "Collection not found");})

les valeurs de printemps AccessDeniedException seront également traitées correctement (codes de réponse 403/401).

Si vous disposez d'un microservice et souhaitez utiliser REST pour cela, cela peut être une bonne option, car ces exceptions http sont assez proches de la logique métier et doivent être placées près de la logique métier dans ce cas. Et comme dans un microservice, vous ne devriez pas avoir trop de businesslogic ni d'exceptions, cela ne devrait pas encombrer votre code aussi ... (mais bien sûr, tout dépend).

6
Frischling

Pourquoi ne pas le faire à l'ancienne en lançant des exceptions à partir de fonctions de gestionnaire et en implémentant votre propre WebExceptionHandler pour les récupérer tous:

@Component
class ExceptionHandler : WebExceptionHandler {
    override fun handle(exchange: ServerWebExchange?, ex: Throwable?): Mono<Void> {
        /* Handle different exceptions here */
        when(ex!!) {
            is NoSuchElementException -> exchange!!.response.statusCode = HttpStatus.NOT_FOUND
            is Exception -> exchange!!.response.statusCode = HttpStatus.INTERNAL_SERVER_ERROR
        }

        /* Do common thing like logging etc... */

        return Mono.empty()
    }
}

L'exemple ci-dessus est à Kotlin, car je viens de le copier-coller à partir d'un projet sur lequel je travaille actuellement, et puisque la question d'origine n'était pas balisée pour Java.

3
Alphaone

Un moyen rapide de mapper vos exceptions sur le statut de réponse http consiste à lancer org.springframework.web.server.ResponseStatusException/ou à créer vos propres sous-classes ...

Un contrôle total sur l'état de la réponse http + spring ajoutera un corps de réponse avec la possibilité d'ajouter reason.

À Kotlin, cela peut paraître aussi simple que

@Component
class MyHandler(private val myRepository: MyRepository) {

    fun getById(req: ServerRequest) = req.pathVariable("id").toMono()
            .map { id -> uuidFromString(id) }  // throws ResponseStatusException
            .flatMap { id -> noteRepository.findById(id) }
            .flatMap { entity -> ok().json().body(entity.toMono()) }
            .switchIfEmpty(notFound().build())  // produces 404 if not found

}

fun uuidFromString(id: String?) = try { UUID.fromString(id) } catch (e: Throwable) { throw BadRequestStatusException(e.localizedMessage) }

class BadRequestStatusException(reason: String) : ResponseStatusException(HttpStatus.BAD_REQUEST, reason)

Corps de réponse:

{
    "timestamp": 1529138182607,
    "path": "/api/notes/f7b.491bc-5c86-4fe6-9ad7-111",
    "status": 400,
    "error": "Bad Request",
    "message": "For input string: \"f7b.491bc\""
}
1
Hartmut

Ce que je fais actuellement fournit simplement un haricot à mon WebExceptionHandler:

@Bean
@Order(0)
public WebExceptionHandler responseStatusExceptionHandler() {
    return new MyWebExceptionHandler();
}

L’avantage de créer HttpHandler moi-même est que j’ai une meilleure intégration avec WebFluxConfigurer si je fournis ma propre ServerCodecConfigurer par exemple ou en utilisant SpringSecurity

0
adrien le roy