Tratamento de erros no Spring WebFlux

Tratamento de erros no Spring WebFlux

1. Visão geral

Neste tutorial,we’ll look at various strategies available for handling errors in a Spring WebFlux project enquanto percorre um exemplo prático.

Também apontaremos onde pode ser vantajoso usar uma estratégia em vez de outra e fornecer um link para o código-fonte completo no final.

2. Configurando o exemplo

A configuração do Maven é igual a nossoprevious article, que fornece uma introdução ao Spring Webflux.

Para nosso exemplo,we’ll usea RESTful endpoint that takes a username as a query parameter and returns “Hello username” como resultado.

Primeiro, vamos criar uma função de roteador que roteie a solicitação/hello para um método denominadohandleRequest no manipulador passado:

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

A seguir, definiremos o métodohandleRequest() que chama o métodosayHello() e encontra uma maneira de incluir / retornar seu resultado no corpoServerResponse:

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

Finalmente, o métodosayHello() é um método utilitário simples que concatena o “Hello”Stringe o nome de usuário:

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

Desde que um nome de usuário esteja presente como parte de nossa solicitação, por exemplo, se o endpoint for chamado como“/hello?username=Tonni “, então, este endpoint funcionará sempre corretamente.

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

Abaixo, veremos onde e como podemos reorganizar nosso código para lidar com essa exceção no WebFlux.

3. Tratamento de erros em um nível funcional

Existem dois operadores principais integrados às APIsMonoeFlux para lidar com erros em um nível funcional.

Vamos explorá-los brevemente e seu uso.

3.1. Tratamento de erros comonErrorReturn

We can use onErrorReturn() to return a static default value sempre que ocorre um erro:

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

Aqui, estamos retornando um "Hello Stranger" estático sempre que a função de concatenação com bugssayHello() lança uma exceção.

3.2. Tratamento de erros comonErrorResume

Existem três maneiras de usaronErrorResume para lidar com erros:

  • Calcular um valor de fallback dinâmico

  • Executar um caminho alternativo com um método de fallback

  • Capture, envolva e lance novamente um erro, por exemplo como uma exceção comercial personalizada

Vamos ver como podemos calcular um valor:

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

Aqui, estamos retornando uma String que consiste na mensagem de erro obtida dinamicamente anexada à string "Erro" sempre quesayHello() lança uma exceção.

A seguir, vamoscall 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)));
}

Aqui, estamos chamando o método alternativosayHelloFallback() sempre quesayHello() lança uma exceção.

A opção final usandoonErrorResume() écatch, wrap, and re-throw an error, por exemplo como umNameRequiredException:

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

Aqui, estamos lançando uma exceção personalizada com a mensagem: “nome de usuário é obrigatório” sempre quesayHello() lança uma exceção.

4. Tratamento de erros em nível global

Até agora, todos os exemplos que apresentamos abordaram o tratamento de erros em um nível funcional.

We can, however, opt to handle our WebFlux errors at a global level. Para fazer isso, precisamos realizar apenas duas etapas:

  • Personalizar os atributos globais de resposta a erros

  • Implementar o manipulador de erros global

A exceção que nosso manipulador lança será traduzida automaticamente para um status HTTP e um corpo de erro JSON. Para personalizá-los, podemos simplesmenteextend the DefaultErrorAttributes classe substituir seu métodogetErrorAttributes():

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

}

Aqui, queremos o status:BAD_REQUESTe a mensagem: “username is required” retornados como parte dos atributos de erro quando ocorrer uma exceção.

Em seguida, vamosimplement the Global Error Handler. Para isso, o Spring fornece uma classeAbstractErrorWebExceptionHandler conveniente para estendermos e implementarmos no tratamento de erros globais:

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

Neste exemplo, definimos a ordem do nosso manipulador de erros global como -2. Este é paragive it a higher priority than the DefaultErrorWebExceptionHandler que está registrado em@Order(-1).

O objetoerrorAttributes será a cópia exata daquele que passamos no construtor do Web Exception Handler. Idealmente, essa deve ser nossa classe de atributos de erro personalizada.

Então, estamos declarando claramente que queremos encaminhar todas as solicitações de tratamento de erros para o métodorenderErrorResponse().

Por fim, obtemos os atributos de erro e os inserimos dentro de um corpo de resposta do servidor.

Isso produz uma resposta JSON com detalhes do erro, o status HTTP e a mensagem de exceção para os clientes da máquina. Para clientes de navegador, ele possui um manipulador de erros 'whitelabel' que renderiza os mesmos dados no formato HTML. Obviamente, isso pode ser personalizado.

5. Conclusão

Neste artigo, analisamos várias estratégias disponíveis para lidar com erros em um projeto Spring WebFlux e apontamos onde pode ser vantajoso usar uma estratégia em detrimento de outra.

Conforme prometido, o código-fonte completo que acompanha o artigo está disponívelover on GitHub.