You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Cloud Gateway全局异常处理器未返回504,客户端收500问题

问题分析:Spring Cloud Gateway全局异常处理器返回500而非预期504

问题场景

基于Spring Boot 3构建的Spring Cloud Gateway应用中,实现了全局异常处理器,但当被代理服务触发504 Gateway Timeout时,日志显示处理器已执行,但客户端收到的响应状态码是500,而非处理器中预期设置的504。

全局异常处理器代码

@Component
@Order(-99)
public class GlobalErrorWebExceptionHandler implements WebExceptionHandler {

    public static final String ERR_TAG_SOURCE = "source";
    public static final String ERR_TAG_CODE = "code";
    public static final String ERR_TAG_MESSAGE = "message";
    
    public static final String ERR_SOURCE_INTERNAL = "internal";
    public static final String ERR_SOURCE_UPSTREAM = "upstream";
    
    protected static final String LOG_MARKER = "PROXY";
    protected static final Logger LOGGER = LoggerFactory.getLogger(GlobalErrorWebExceptionHandler.class);

    public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {

        LOGGER.error(MarkerFactory.getMarker(LOG_MARKER), "Error ({}) encountered while filtering request {}.", 
                ex.getClass().getCanonicalName(), exchange.getRequest().getId(), ex); 
        HttpStatus status = ex instanceof ErrorResponseException ? (HttpStatus) ((ErrorResponseException) ex).getStatusCode() : HttpStatus.INTERNAL_SERVER_ERROR;
        String source = ex instanceof ErrorResponseException ? ERR_SOURCE_UPSTREAM : ERR_SOURCE_INTERNAL;
        String errorMessage = ex.getMessage();
        exchange.getResponse().setStatusCode(status);
        return exchange.getResponse().writeWith(
                Flux.just(
                        exchange.getResponse().bufferFactory().wrap(
                                (
                                        "{\"" + ERR_TAG_MESSAGE + "\": \"" + errorMessage 
                                        + "\", \"" + ERR_TAG_CODE + "\": " + status.value()
                                        + ", \"" + ERR_TAG_SOURCE + "\": \"" + source + "\"}")
                                    .getBytes())));
    }
}

异常日志片段

2023-06-26 21:19:03,435 ERROR --- pod-86f446c7dd-fn47g parallel-7 com.applicationGlobalErrorWebExceptionHandler: app-name, , , , GATEWAY,  - Error (org.springframework.web.server.ResponseStatusException) encountered while filtering request f5eb8577-292.
org.springframework.web.server.ResponseStatusException: 504 GATEWAY_TIMEOUT "504 GATEWAY_TIMEOUT \"Response took longer than timeout: PT5M\""
...

原因分析

  • 处理器代码中仅判断ErrorResponseException类型的异常,才会提取原异常的状态码;其他异常统一返回HttpStatus.INTERNAL_SERVER_ERROR(500)。
  • 但触发504超时的异常实际是org.springframework.web.server.ResponseStatusException,而非ErrorResponseException,因此走了默认分支,返回了500状态码。
  • 在Spring Boot 3/Spring Framework 6生态中,ResponseStatusException是WebFlux传统的带状态码异常,ErrorResponseException是Spring Boot 3新增的标准化错误响应异常,二者为不同类,但均实现了ErrorResponse接口。

解决方案

方案1:基于ErrorResponse接口判断(推荐)

通过ErrorResponse接口统一处理所有带状态码的标准化异常,覆盖范围更广:

public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {

    LOGGER.error(MarkerFactory.getMarker(LOG_MARKER), "Error ({}) encountered while filtering request {}.", 
            ex.getClass().getCanonicalName(), exchange.getRequest().getId(), ex); 
    
    HttpStatus status;
    String source;
    String errorMessage = ex.getMessage();
    
    if (ex instanceof ErrorResponse errorResponse) {
        status = HttpStatus.valueOf(errorResponse.getStatusCode().value());
        source = ERR_SOURCE_UPSTREAM;
    } else {
        status = HttpStatus.INTERNAL_SERVER_ERROR;
        source = ERR_SOURCE_INTERNAL;
    }
    
    exchange.getResponse().setStatusCode(status);
    return exchange.getResponse().writeWith(
            Flux.just(
                    exchange.getResponse().bufferFactory().wrap(
                            ("{\"" + ERR_TAG_MESSAGE + "\": \"" + errorMessage 
                            + "\", \"" + ERR_TAG_CODE + "\": " + status.value()
                            + ", \"" + ERR_TAG_SOURCE + "\": \"" + source + "\"}")
                                .getBytes())));
}

方案2:显式判断两种异常类型

如果需要针对特定异常做差异化处理,可以同时判断ErrorResponseException和ResponseStatusException:

public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {

    LOGGER.error(MarkerFactory.getMarker(LOG_MARKER), "Error ({}) encountered while filtering request {}.", 
            ex.getClass().getCanonicalName(), exchange.getRequest().getId(), ex); 
    
    HttpStatus status;
    String source;
    String errorMessage = ex.getMessage();
    
    if (ex instanceof ErrorResponseException ere) {
        status = (HttpStatus) ere.getStatusCode();
        source = ERR_SOURCE_UPSTREAM;
    } else if (ex instanceof ResponseStatusException rse) {
        status = rse.getStatusCode();
        source = ERR_SOURCE_UPSTREAM;
    } else {
        status = HttpStatus.INTERNAL_SERVER_ERROR;
        source = ERR_SOURCE_INTERNAL;
    }
    
    exchange.getResponse().setStatusCode(status);
    return exchange.getResponse().writeWith(
            Flux.just(
                    exchange.getResponse().bufferFactory().wrap(
                            ("{\"" + ERR_TAG_MESSAGE + "\": \"" + errorMessage 
                            + "\", \"" + ERR_TAG_CODE + "\": " + status.value()
                            + ", \"" + ERR_TAG_SOURCE + "\": \"" + source + "\"}")
                                .getBytes())));
}

内容的提问来源于stack exchange,提问作者user625488

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 23:07:00