Spring Reactive WebFlux - 如何自定义 BadRequest 错误消息

Posted

技术标签:

【中文标题】Spring Reactive WebFlux - 如何自定义 BadRequest 错误消息【英文标题】:Spring Reactive WebFlux - how to customize the BadRequest error message 【发布时间】:2020-12-20 07:07:14 【问题描述】:

在我的请求处理程序中,如果传入的accountId 无法转换为有效的ObjectId,我想捕获错误并发回有意义的消息;但是,这样做会导致返回类型不兼容,我无法弄清楚如何实现这个非常简单的用例。

我的代码:

  @GetMapping("/accountId")
  public Mono<ResponseEntity<Account>> get(@PathVariable String accountId) 
      log.debug(GETTING_DATA_FOR_ACCOUNT, accountId);

      try 
        ObjectId id = new ObjectId(accountId);
        return repository.findById(id)
            .map(ResponseEntity::ok)
            .switchIfEmpty(Mono.just(ResponseEntity.notFound().build()));
       catch (IllegalArgumentException ex) 
        log.error(MALFORMED_OBJECT_ID, accountId);
        // TODO(marco): find a way to return the custom error message. This seems to be currently
        //  impossible with the Reactive API, as using body(message) changes the return type to
        //  be incompatible (and Mono<ResponseEntity<?>> does not seem to cut it).
        return Mono.just(ResponseEntity.badRequest().build());
      
  

body(T body) 方法将返回的Mono 的类型更改为(假设只发送一个StringMono&lt;ResponseEntity&lt;String&gt;&gt;;但是,将方法的返回类型更改为 Mono&lt;ResponseEntity&lt;?&gt;&gt; 也不起作用:

        ...
        return Mono.just(ResponseEntity.badRequest().body(
            MALFORMED_OBJECT_ID.replace("", accountId)));

因为它在另一个 return 语句上给出了“不兼容的类型”错误:

error: incompatible types: Mono<ResponseEntity<Account>> cannot be converted to Mono<ResponseEntity<?>>
            .switchIfEmpty(Mono.just(ResponseEntity.notFound().build()));

显然,将方法的返回类型更改为 Mono&lt;?&gt; 会起作用,但随后的响应是 ResponseEntity 的序列化 JSON,这不是我想要的。

我也尝试过使用onErrorXxxx() 方法,但它们在这里也不起作用,因为转换错误甚至在计算通量之前就发生了,我只是得到一个“vanilla”400 错误和一条空消息。

我能想到解决此问题的唯一方法是将message 字段添加到我的Account 对象并返回该对象,但这确实是一个可怕的黑客攻击。

【问题讨论】:

您需要阅读响应式编程的基础知识。你不应该使用try/catch,如果你想向客户端返回一个错误应该返回一个包含ResponseStatusExceptionMono.error。我建议阅读 reactor 文档中的入门指南。 projectreactor.io/docs/core/release/reference/#getting-started 【参考方案1】:

@thomas-andolf 的回答帮助我找出了实际的解决方案。

对于将来遇到此问题的任何人,以下是我实际解决难题的方法(并且,是的,您仍然需要 try/catch 来拦截 ObjectId 构造函数抛出的错误):

  @GetMapping("/accountId")
  public Mono<ResponseEntity<Account>> get(@PathVariable String accountId) 
    return Mono.just(accountId)
        .map(acctId -> 
          try 
            return new ObjectId(accountId);
           catch (IllegalArgumentException ex) 
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST,
                MALFORMED_OBJECT_ID));
          
        )
        .flatMap(repository::findById)
        .map(ResponseEntity::ok)
        .switchIfEmpty(Mono.just(ResponseEntity.notFound().build()));
  

要在返回的正文中实际看到message,您需要在application.properties 中添加server.error.include-message=always(请参阅here)。

在这里使用onError() 将不起作用(我确实尝试过,在其所有变体中),因为它需要Mono&lt;ResponseEntity&lt;Account&gt;&gt;,并且无法从错误状态生成一个(添加消息正文时)。

【讨论】:

不知道为什么有人对此投了反对票,我从字面上尝试了“已接受”答案中的代码,但它没有编译(也没有我在原始问题中打算做的事情)。这个编译并确实将错误消息返回给调用者。 我遇到了同样的问题,您的问题帮助解决了我的问题 (***.com/a/67413913/2096986)。您的 try/catch 解决方案有效,尽管它没有使用函数式风格,这是最好的选择,因为您使用的是反应式编程。 很高兴它有帮助,@Felipe - 如果有帮助,请投赞成票,这样它会帮助其他人:有人(甚至懒得评论和解释自己)投了反对票,所以它显示低于(赞成,但不正确)之一。正如我在上面提到的,try/catch 在这里是 necessary 因为我调用的库方法不使用函数式/反应式风格:该方法是给定的,不能修改; not 在此处使用 try/catch 会导致异常传播并破坏响应式功能链。

以上是关于Spring Reactive WebFlux - 如何自定义 BadRequest 错误消息的主要内容,如果未能解决你的问题,请参考以下文章

Spring Webflux - 02 Reactive介绍

Spring Webflux - 02 Reactive介绍

Spring WebFlux Reactive 和 Kotlin Coroutines 启动错误

Spring Reactive WebFlux - 如何自定义 BadRequest 错误消息

Spring Reactive Programming with Webflux - 多个操作作为非阻塞流

如何在 Spring WebFlux Security(Reactive Spring Security)配置中将多个用户角色添加到单个 pathMatcher/Route?