返回 Mono 和 Flux 错误总是返回 500

Posted

技术标签:

【中文标题】返回 Mono 和 Flux 错误总是返回 500【英文标题】:Returning Mono and Flux error always returns 500 【发布时间】:2020-10-28 22:12:36 【问题描述】:

据我所知,我仍在尝试了解 WebFlux 异常的工作原理,当返回 Flux 或 Mono 中的对象时,消费者应该收到从服务器发送的相同异常。

但是,例如,当我在 Mono 中返回 HTTPException 401 时,我在消费者中收到的响应正文与我发送的不同,我读到的是 500 Internal Server 错误而不是 401。

这是我为这个问题制作的一个简单的控制器类

package com.example.demo;

import javax.xml.ws.http.HTTPException;

import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import reactor.core.publisher.Mono;

@RestController
public class Controller 

@RequestMapping(
        path="/getExceptionMono",
        method = RequestMethod.GET,
        produces = MediaType.APPLICATION_JSON_VALUE)
    public Mono<String> getException()
        return Mono.error(new HTTPException(401));
    

这里是消费者:

package com.example.demo;

import org.springframework.boot.CommandLineRunner;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.client.WebClient;

@Component
public class ReactiveDemoConsumer implements CommandLineRunner 
    @Override
    public void run(String... args) throws Exception 
        String url = "http://localhost:8080/getExceptionMono";
        WebClient.create(url)
            .get()
            .accept(MediaType.APPLICATION_JSON)
            .retrieve()
            .bodyToMono(String.class)
            .subscribe(value -> System.err.println("Received String: " + value),
                        err -> System.err.println("Received error " + err));

    

这是消费者的控制台日志

Received error org.springframework.web.reactive.function.client.WebClientResponseException$InternalServerError: 500 Internal Server Error from GET http://localhost:8080/getExceptionMono

如何传递我的异常以便消费者看到我在 Mono 中传递的原始异常? 希望我的问题很清楚,提前谢谢

【问题讨论】:

可以使用onStatus查看响应码吗? Mono 结果 = client.get() .uri("/persons/id", id).accept(MediaType.APPLICATION_JSON) .retrieve() .onStatus(HttpStatus::is4xxClientError, response -> ... ) .onStatus(HttpStatus::is5xxServerError, response -> ...) .bodyToMono(Person.class); 刚刚尝试了这个建议,它只是跳过 4xx 谓词直接到 5xx,打印响应并返回“org.springframework.web.reactive.function.client.DefaultClientResponse@c7a55c43” @EdenDupont 请看一遍我的回答。我希望它能澄清你的疑惑 【参考方案1】:

这不是从应用程序返回 4xx 响应的正确方法。 应用程序中抛出的任何类型的异常都将被 WebClientResponseException 包装,并在客户端作为 500 Internal Server Error 接收。

改变这种情况的一种方法是在控制器中设置一个异常处理程序,如下所示:

  @ExceptionHandler(UnsupportedMediaTypeException.class)
  public ResponseEntity<String> exceptionHandler(Exception ex) 
    return ResponseEntity.badRequest().body(ex.getMessage());
  

另一种方法是在代码中使用全局异常处理程序: (这里的 Ex1 类似于 HTTPException)

@Component
public class GlobalExceptionHandler implements ErrorWebExceptionHandler 

  @Override
  public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) 
    ServerHttpResponse httpResponse = exchange.getResponse();
    setResponseStatus(httpResponse, ex);
    return httpResponse.writeWith(Mono.fromSupplier(() -> 
      DataBufferFactory bufferFactory = httpResponse.bufferFactory();
      try 
        //Not displaying any error msg to client for internal server error
        String errMsgToSend = (httpResponse.getStatusCode() == HttpStatus.INTERNAL_SERVER_ERROR) ? "" : ex.getMessage();
        return bufferFactory.wrap(new ObjectMapper().writeValueAsBytes(errMsgToSend));
       catch (JsonProcessingException e) 
        return bufferFactory.wrap(new byte[0]);
      
    ));
  

  private void setResponseStatus(ServerHttpResponse httpResponse, Throwable ex) 
    if (ex instanceof Ex1 || ex instanceof Ex2 || ex instanceof Ex3) 
      httpResponse.setStatusCode(HttpStatus.BAD_REQUEST);
     else 
      httpResponse.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
    
  

或者你可以像这样重写你的控制器:

@RequestMapping(
        path="/getExceptionMono",
        method = RequestMethod.GET,
        produces = MediaType.APPLICATION_JSON_VALUE)
    public Mono<ResponseEntity> getException()
        return Mono.just(ResponseEntity.badRequest().build());
    

然后在 webclient 代码中,你可以这样做:

webClient
 .get()
 .uri("/some/url")
 .exchange()
 .flatMap(clientResponse -> 
     if (clientResponse.statusCode().is5xxServerError()) 
        //do something and return some mono
     
     else if(clientResponse.statusCode().is4xxClientError()) 
        //do something and return some mono
     
     else 
       //do something and return some mono  
     
);

【讨论】:

现在事情似乎有点清楚了,所以发送对象的正确方法也被一个 ResponseEntity 包裹,比如说我想发送一个字符串,它在控制器中看起来像这样:public Mono > getException() return Mono.just(ResponseEntity.ok().body(new String("MyString"))); 正确吗? 如果直接返回对象,spring会默认设置响应码为200。如果您需要 202 接受或 204 无内容之类的其他内容,则需要返回响应实体

以上是关于返回 Mono 和 Flux 错误总是返回 500的主要内容,如果未能解决你的问题,请参考以下文章

如何使用返回 Mono 的生成包装调用创建 Flux

如何从返回 Mono 的 Stream 创建 Flux? [复制]

让 HTTP 端点返回 Flux/Mono 实例而不是 DTO 的好处 [关闭]

如何使用 Micrometer Timer 记录异步方法的持续时间(返回 Mono 或 Flux)

如何在Flux的onComplete执行后返回一个Mono?

Reactor响应式编程(Mono)