什么是处理spring-webflux错误的正确方法

Jua*_*ina 12 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));
    }
}
Run Code Online (Sandbox Code Playgroud)

我在我的经纪人身上这样做了

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

他的错误处理程序:

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());
    }
}
Run Code Online (Sandbox Code Playgroud)

以下是完整的样本回购:

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

pun*_*yee 8

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

使用HttpWebHandlerAdapter将WebHandler调整为HttpHandler.WebHttpHandlerBuilder提供了一种方便的方法,同时还可以选择配置一个或多个过滤器和/或异常处理程序.

目前,官方文档建议我们应该在启动任何服务器之前将路由器功能包装到HttpHandler中:

HttpHandler httpHandler = RouterFunctions.toHttpHandler(routerFunction);
Run Code Online (Sandbox Code Playgroud)

WebHttpHandlerBuilder的帮助下,我们可以配置自定义异常处理程序:

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

      /* custom handling goes here */
      return null;

  }).build();
Run Code Online (Sandbox Code Playgroud)


小智 6

为什么不通过从处理程序函数中抛出异常并实现您自己的 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()
    }
}
Run Code Online (Sandbox Code Playgroud)

上面的例子是在 Kotlin 中,因为我只是从我目前正在处理的项目中复制粘贴它,并且因为原始问题无论如何都没有标记为 java。

  • 最后让它工作,在无启动应用程序中,手动注册它,见[这里](https://github.com/hantsy/spring-reactive-sample/blob/master/exception-handler/src/main/java/com /example/demo/Application.java#L50) (2认同)

Fri*_*ing 6

如果您认为路由器功能不是处理异常的正确位置,则抛出HTTP异常,这将导致正确的HTTP错误代码.对于Spring-Boot(也是webflux),这是:

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

  new ResponseStatusException(HttpStatus.NOT_FOUND,  "Collection not found");})
Run Code Online (Sandbox Code Playgroud)

spring证书AccessDeniedException也将被正确处理(403/401响应代码).

如果你有一个微服务,并且想要使用REST,那么这可能是一个很好的选择,因为那些http异常非常接近业务逻辑,在这种情况下应该放在业务逻辑附近.因为在微服务中你不应该有太多的businesslogic和异常,它也不应该混乱你的代码......(但当然,这一切都取决于).


Har*_*mut 6

将异常映射到 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)
Run Code Online (Sandbox Code Playgroud)

响应机构:

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


Akh*_*dla 5

您可以使用自定义响应数据和响应代码编写一个全局异常处理程序,如下所示。代码在 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))
  }
}
Run Code Online (Sandbox Code Playgroud)