Spring Reactive API网关转发微服务错误响应异常排查
问题:API网关无法原样转发微服务的错误响应
构建包含User Service、API Gateway和Discovery Service的Spring应用时,遇到以下问题:
- 直接调用User Service创建已注册邮箱的用户,返回正确的400错误JSON:
{ "type": "about:blank", "title": "Bad Request", "status": 400, "instance": "/users", "errors": [ "Email already registered" ] }
- 但通过API Gateway发起请求时,返回的却是:
An unknown error occurred (with a code 500)
问题根源
从控制台日志和代码逻辑可定位问题:
- WebClient错误处理有误:UserController中调用User Service时,将错误响应包装成通用
RuntimeException抛出,导致业务错误被当作系统异常处理:
.onStatus(HttpStatusCode::isError, clientResponse -> clientResponse.bodyToMono(String.class) .flatMap(errorBody -> Mono.error(new RuntimeException(errorBody))) )
- CircuitBreaker fallback逻辑覆盖业务错误:fallback方法捕获所有异常后,默认返回500状态响应,没有区分业务错误和服务不可用异常:
return Mono.just(ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("An unknown error occurred"));
- 全局异常处理器未识别业务异常:GlobalExceptionHandler仅处理特定类型异常,通用
RuntimeException被归类为未知异常,返回500。
解决方案
方案1:直接返回错误响应,避免触发fallback(推荐)
修改UserController的WebClient调用逻辑,遇到错误响应时直接返回,不抛出异常:
@PostMapping public Mono<ResponseEntity<?>> createUser(@RequestBody CreateUserDto userDto) { return circuitBreaker.run( webClientBuilder.build() .post() .uri("http://user-service/users") .bodyValue(userDto) .retrieve() // 遇到错误响应时直接封装为ResponseEntity返回,不抛出异常 .onStatus(HttpStatusCode::isError, clientResponse -> clientResponse.bodyToMono(String.class) .map(errorBody -> ResponseEntity.status(clientResponse.statusCode()).body(errorBody)) .flatMap(Mono::just) ) .toEntity(User.class) .map(userResponse -> ResponseEntity.status(userResponse.getStatusCode()).body(userResponse.getBody())), throwable -> fallbackMethod(throwable) ); }
方案2:调整fallback逻辑,区分业务错误与服务异常
修改fallback方法,仅对服务不可用类异常返回默认响应,业务错误则解析原错误体返回:
private Mono<ResponseEntity<?>> fallbackMethod(Throwable throwable) { log.error("Error occurred: ", throwable); if (throwable instanceof ClientAuthorizationRequiredException) { return Mono.just(ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Authorization required")); } else if (throwable instanceof NoFallbackAvailableException || throwable instanceof WebClientRequestException) { return Mono.just(ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).body("Service unavailable")); } // 解析业务错误体(假设错误体是JSON格式) if (throwable.getMessage() != null && throwable.getMessage().startsWith("{")) { return Mono.just(ResponseEntity.status(HttpStatus.BAD_REQUEST).body(throwable.getMessage())); } return Mono.just(ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("An unknown error occurred")); }
方案3:自定义异常类,增强全局异常处理
- 创建自定义异常类,携带状态码和错误体:
public class ServiceResponseException extends RuntimeException { private final HttpStatusCode statusCode; private final String responseBody; public ServiceResponseException(HttpStatusCode statusCode, String responseBody) { super(responseBody); this.statusCode = statusCode; this.responseBody = responseBody; } public HttpStatusCode getStatusCode() { return statusCode; } public String getResponseBody() { return responseBody; } }
- 修改WebClient错误处理,抛出自定义异常:
.onStatus(HttpStatusCode::isError, clientResponse -> clientResponse.bodyToMono(String.class) .flatMap(errorBody -> Mono.error(new ServiceResponseException(clientResponse.statusCode(), errorBody))) )
- 在GlobalExceptionHandler中添加自定义异常处理:
// 修改determineHttpStatus方法 private HttpStatusCode determineHttpStatus(Throwable throwable) { if (throwable instanceof ServiceResponseException) { return ((ServiceResponseException) throwable).getStatusCode(); } // 原有其他逻辑... } // 修改renderErrorResponse方法 private Mono<ServerResponse> renderErrorResponse(ServerRequest request) { ErrorAttributeOptions options = ErrorAttributeOptions.of(ErrorAttributeOptions.Include.MESSAGE); Map<String, Object> errorPropertiesMap = getErrorAttributes(request, options); Throwable throwable = getError(request); HttpStatusCode httpStatus = determineHttpStatus(throwable); if (throwable instanceof ServiceResponseException) { ServiceResponseException serviceException = (ServiceResponseException) throwable; return ServerResponse.status(serviceException.getStatusCode()) .contentType(MediaType.APPLICATION_JSON) .body(BodyInserters.fromValue(serviceException.getResponseBody())); } // 原有其他逻辑... }
验证
修改完成后,通过API Gateway调用创建已注册邮箱的用户,即可原样返回User Service的400错误JSON响应。
内容的提问来源于stack exchange,提问作者emmariescurrena
相关产品推荐
相关产品推荐

