what is the right way to handle errors in spring-webflux

前端 未结 6 2056
轮回少年
轮回少年 2020-12-09 11:12

I\'ve been doing some research using spring-webflux and I like to understand what should be the right way to handle errors using Router Functions.

I\'ve created an s

相关标签:
6条回答
  • 2020-12-09 11:41

    Why not do it the old fashioned way by throwing exceptions from handler functions and implementing your own WebExceptionHandler to catch 'em all:

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

    Above example is in Kotlin, since I just copy pasted it from a project I´m currently working on, and since the original question was not tagged for java anyway.

    0 讨论(0)
  • 2020-12-09 11:42

    If you think, router functions are not the right place to handle exceptions, you throw HTTP Exceptions, that will result in the correct HTTP Error codes. For Spring-Boot (also webflux) this is:

      import org.springframework.http.HttpStatus;
      import org.springframework.web.server.ResponseStatusException;
      .
      .
      . 
    
      new ResponseStatusException(HttpStatus.NOT_FOUND,  "Collection not found");})
    

    spring securities AccessDeniedException will be handled correctly, too (403/401 response codes).

    If you have a microservice, and want to use REST for it, this can be a good option, since those http exceptions are quite close to business logic, and should be placed near the business logic in this case. And since in a microservice you shouldn't have to much businesslogic and exceptions, it shouldn't clutter your code, too... (but of course, it all depends).

    0 讨论(0)
  • 2020-12-09 11:45

    A quick way to map your exceptions to http response status is to throw org.springframework.web.server.ResponseStatusException / or create your own subclasses...

    Full control over http response status + spring will add a response body with the option to add a reason.

    In Kotlin it could look as simple as

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

    Response Body:

    {
        "timestamp": 1529138182607,
        "path": "/api/notes/f7b.491bc-5c86-4fe6-9ad7-111",
        "status": 400,
        "error": "Bad Request",
        "message": "For input string: \"f7b.491bc\""
    }
    
    0 讨论(0)
  • 2020-12-09 11:48

    Spring 5 provides a WebHandler, and in the JavaDoc, there's the line:

    Use HttpWebHandlerAdapter to adapt a WebHandler to an HttpHandler. The WebHttpHandlerBuilder provides a convenient way to do that while also optionally configuring one or more filters and/or exception handlers.

    Currently, the official documentation suggests that we should wrap the router function into an HttpHandler before booting up any server:

    HttpHandler httpHandler = RouterFunctions.toHttpHandler(routerFunction);
    

    With the help of WebHttpHandlerBuilder, we can configure custom exception handlers:

    HttpHandler httpHandler = WebHttpHandlerBuilder.webHandler(toHttpHandler(routerFunction))
      .prependExceptionHandler((serverWebExchange, exception) -> {
    
          /* custom handling goes here */
          return null;
    
      }).build();
    
    0 讨论(0)
  • 2020-12-09 11:54

    You can write a Global exception handler with custom response data and response code as follows. The code is in Kotlin. But you can convert it to java easily:

    @Component
    @Order(-2)
    class GlobalWebExceptionHandler(
      private val objectMapper: ObjectMapper
    ) : ErrorWebExceptionHandler {
    
      override fun handle(exchange: ServerWebExchange, ex: Throwable): Mono<Void> {
    
        val response = when (ex) {
          // buildIOExceptionMessage should build relevant exception message as a serialisable object
          is IOException -> buildIOExceptionMessage(ex)
          else -> buildExceptionMessage(ex)
        }
    
        // Or you can also set them inside while conditions
        exchange.response.headers.contentType = MediaType.APPLICATION_PROBLEM_JSON
        exchange.response.statusCode = HttpStatus.valueOf(response.status)
        val bytes = objectMapper.writeValueAsBytes(response)
        val buffer = exchange.response.bufferFactory().wrap(bytes)
        return exchange.response.writeWith(Mono.just(buffer))
      }
    }
    
    0 讨论(0)
  • 2020-12-09 12:05

    What I am currently doing is simply providing a bean my WebExceptionHandler :

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

    The advantage than creating the HttpHandler myself is that I have a better integration with WebFluxConfigurer if I provide my own ServerCodecConfigurer for example or using SpringSecurity

    0 讨论(0)
提交回复
热议问题