7Gateway 异常处理 · SpringCloud微服务实战 · 看云

7. Gateway 异常处理

导航

入门篇Zuul的时候一样,Spring Cloud Gateway的全局异常处理也不能直接用@ControllerAdvice来处理,通过自定义异常处理器来实现业务需求。
网关是给接口做代理转发的,后端对应的都是REST API,返回数据格式一般都是JSON。如果不做处理,当发生异常时,Gateway默认给出的错误信息是页面,不方便前端进行异常处理。
需要对异常信息进行处理,返回JSON格式的数据给客户端。

本节代码地址


7.1 应用配置

这里的配置只是简单的代码配置,只有是当前系统时间是2020年1月8号以后的都可以转发,否则就会返回404

server:
  port: 8711
spring:
  application:
    name: fw-gateways-gateway
  profiles:
    active: exception_route

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/
---
spring:
  cloud:
    gateway:
      routes:
        - id: exception_route
          uri: lb://fw-cloud-ribbon-server
          predicates:
          - After=2020-01-08T18:30:11.965+08:00[Asia/Shanghai]
  profiles: exception_route

7.2 自定义异常

先看默认异常处理
org.springframework.boot.autoconfigure.web.reactive.error.DefaultErrorWebExceptionHandler中的getRoutingFunction()方法就是控制返回格式的,原代码如下,默认异常信息返回的是HTML格式。这里我们自定义的时候需要改变为JSON的。

protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
    return RouterFunctions.route(this.acceptsTextHtml(), this::renderErrorView).andRoute(RequestPredicates.all(), this::renderErrorResponse);
}

原始的方法是通过status来获取对应的HttpStatus的,对应的是整形数字,如果你的状态字段不是status,需要重写此方法。

protected int getHttpStatus(Map<String, Object> errorAttributes) {
    return (Integer)errorAttributes.get("status");
}

重写后的如下


@Slf4j
public class FwGatewayExceptionHandler extends DefaultErrorWebExceptionHandler {

    public FwGatewayExceptionHandler(ErrorAttributes errorAttributes, ResourceProperties resourceProperties,
                                     ErrorProperties errorProperties, ApplicationContext applicationContext) {
        super(errorAttributes, resourceProperties, errorProperties, applicationContext);
    }

    
    @Override
    protected Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace) {
        Throwable error = super.getError(request);
        log.error(
                "请求发生异常,请求URI:{},请求方法:{},异常信息:{}",
                request.path(), request.methodName(), error.getMessage()
        );
        String errorMessage;
        if (error instanceof NotFoundException) {
            String serverId = StringUtils.substringAfterLast(error.getMessage(), "Unable to find instance for ");
            serverId = StringUtils.replace(serverId, "\"", StringUtils.EMPTY);
            errorMessage = String.format("无法找到%s服务", serverId);
        } else if (StringUtils.containsIgnoreCase(error.getMessage(), "connection refused")) {
            errorMessage = "目标服务拒绝连接";
        } else if (error instanceof TimeoutException) {
            errorMessage = "访问服务超时";
        } else if (error instanceof ResponseStatusException
                && StringUtils.containsIgnoreCase(error.getMessage(), HttpStatus.NOT_FOUND.toString())) {
            errorMessage = "未找到该资源";
        } else {
            errorMessage = "网关转发异常";
        }
        Map<String, Object> errorAttributes = new HashMap<>(2);
        errorAttributes.put("msg", errorMessage);
        errorAttributes.put("code", HttpStatus.INTERNAL_SERVER_ERROR.value());
        return errorAttributes;
    }
    
    @Override
    protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
        return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse);
    }
    
    @Override
    protected int getHttpStatus(Map<String, Object> errorAttributes) {
        return (int) errorAttributes.get("code");
    }
}

7.3 覆盖默认的异常处理

重写errorWebExceptionHandler()方法,里面的实现用我们自己定义的异常处理(FwGatewayExceptionHandler)来实现,否则仍然走默认的。


@Configuration
public class FwGatewayErrorConfigure {

        private final ServerProperties serverProperties;
        private final ApplicationContext applicationContext;
        private final ResourceProperties resourceProperties;
        private final List<ViewResolver> viewResolvers;
        private final ServerCodecConfigurer serverCodecConfigurer;

        public FwGatewayErrorConfigure(ServerProperties serverProperties,
                                         ResourceProperties resourceProperties,
                                         ObjectProvider<List<ViewResolver>> viewResolversProvider,
                                         ServerCodecConfigurer serverCodecConfigurer,
                                         ApplicationContext applicationContext) {
            this.serverProperties = serverProperties;
            this.applicationContext = applicationContext;
            this.resourceProperties = resourceProperties;
            this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
            this.serverCodecConfigurer = serverCodecConfigurer;
        }

        @Bean
        @Order(Ordered.HIGHEST_PRECEDENCE)
        public ErrorWebExceptionHandler errorWebExceptionHandler(ErrorAttributes errorAttributes) {
            FwGatewayExceptionHandler exceptionHandler = new FwGatewayExceptionHandler(
                    errorAttributes,
                    this.resourceProperties,
                    this.serverProperties.getError(),
                    this.applicationContext);
            exceptionHandler.setViewResolvers(this.viewResolvers);
            exceptionHandler.setMessageWriters(this.serverCodecConfigurer.getWriters());
            exceptionHandler.setMessageReaders(this.serverCodecConfigurer.getReaders());
            return exceptionHandler;
    }
}

7.4 应用启动

ribbon-server 不启动
9e998ac1d032ed815437ec6616b24c78_MD5.png

验证出现异常时候的统一处理,浏览器或Postman输入localhost:8711/user/1
6c634890e2f5960f27b1558d2a14cfb9_MD5.png

控制台错误如下:
93a3f59d0032b26bfd4f03a11c4d4de4_MD5.png

可以看到错误信息被统一处理了