簡體   English   中英

在 spring-webflux 中處理錯誤的正確方法是什么

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

我一直在使用 spring-webflux 進行一些研究,我想了解使用路由器功能處理錯誤的正確方法應該是什么。

我創建了一個小項目來測試幾個場景,我喜歡得到關於它的反饋,看看其他人在做什么。

到目前為止,我所做的是。

給出以下路由功能:

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

我已經在我的處理程序上這樣做了

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

他們我的錯誤處理程序做:

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

這是完整的示例回購:

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

如果您認為路由器功能不是處理異常的正確位置,您可以拋出 HTTP 異常,這將導致正確的 HTTP 錯誤代碼。 對於 Spring-Boot(也是 webflux),這是:

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

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

spring 證券 AccessDeniedException 也將被正確處理(403/401 響應代碼)。

如果您有一個微服務,並希望對其使用 REST,這可能是一個不錯的選擇,因為那些 http 異常非常接近業務邏輯,在這種情況下應該放在業務邏輯附近。 因為在微服務中你不應該有太多的業務邏輯和異常,它也不應該使你的代碼混亂......(當然,這一切都取決於)。

Spring 5 提供了一個WebHandler ,在 JavaDoc 中,有一行:

使用 HttpWebHandlerAdapter 將 WebHandler 適配到 HttpHandler。 WebHttpHandlerBuilder 提供了一種方便的方法來做到這一點,同時還可以選擇配置一個或多個過濾器和/或異常處理程序。

目前,官方文檔建議我們應該在啟動任何服務器之前將路由器功能包裝到 HttpHandler 中:

HttpHandler httpHandler = RouterFunctions.toHttpHandler(routerFunction);

WebHttpHandlerBuilder的幫助下,我們可以配置自定義異常處理程序:

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

      /* custom handling goes here */
      return null;

  }).build();

為什么不通過從處理程序函數中拋出異常並實現您自己的 WebExceptionHandler 來捕獲所有異常來使用老式方法:

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

上面的例子是在 Kotlin 中,因為我只是從我目前正在處理的項目中復制粘貼它,並且因為原始問題無論如何都沒有標記為 java。

將異常映射到 http 響應狀態的一種快速方法是拋出org.springframework.web.server.ResponseStatusException / 或創建自己的子類...

完全控制 http 響應狀態 + spring 將添加一個帶有添加reason選項的響應主體。

在 Kotlin 中,它看起來很簡單

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

響應機構:

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

您可以使用自定義響應數據和響應代碼編寫一個全局異常處理程序,如下所示。 代碼在 Kotlin 中。 但是您可以輕松地將其轉換為 java:

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

我目前正在做的只是提供一個 bean 我的 WebExceptionHandler :

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

比自己創建HttpHandler的優勢在於,如果我提供自己的ServerCodecConfigurer或使用SpringSecurity ,我可以更好地與WebFluxConfigurer集成

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM