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
相关产品推荐
相关产品推荐

