聊聊Spring Cloud Gateway过滤器精确控制异常返回问题

欢迎访问我的GitHub

这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos

本篇概览在《Spring Cloud Gateway修改请求和响应body的内容》一文中,咱们通过filter成功修改请求body的内容,当时留下个问题:在filter中如果发生异常(例如请求参数不合法),抛出异常信息的时候,调用方收到的返回码和body都是Spring Cloud Gateway框架处理后的,调用方无法根据这些内容知道真正的错误原因,如下图:

本篇任务就是分析上述现象的原因,通过阅读源码搞清楚返回码和响应body生成的具体逻辑

  • 这里将分析结果提前小结出来,如果您很忙碌没太多时间却又想知道最终原因,直接关注以下小结即可:
  1. Spring Cloud Gateway应用中,有个ErrorAttributes类型的bean,它的getErrorAttributes方法返回了一个map
  2. 应用抛出异常时,返回码来自上述map的status的值,返回body是整个map序列化的结果
  3. 默认情况下ErrorAttributes的实现类是DefaultErrorAttributes
  • 再看上述map的status值(也就是response的返回码),在DefaultErrorAttributes是如何生成的:

先看异常对象是不是ResponseStatusException类型

  1. 如果是ResponseStatusException类型,就调用异常对象的getStatus方法作为返回值
  2. 如果不是ResponseStatusException类型,再看异常类有没有ResponseStatus注解,
  3. 如果有,就取注解的code属性作为返回值
  4. 如果异常对象既不是ResponseStatusException类型,也没有ResponseStatus注解,就返回500

最后看map的message字段(也就是response body的message字段),在DefaultErrorAttributes是如何生成的:

  1. 异常对象是不是BindingResult类型
  2. 如果不是BindingResult类型,就看是不是ResponseStatusException类型
  3. 如果是,就用getReason作为返回值
  4. 如果也不是ResponseStatusException类型,就看异常类有没有ResponseStatus注解,如果有就取该注解的reason属性作为返回值
  5. 如果通过注解取得的reason也无效,就返回异常的getMessage字段

上述内容就是本篇精华,但是并未包含分析过程,如果您对Spring Cloud源码感兴趣,请允许欣宸陪伴您来一次短暂的源码阅读之旅

Spring Cloud Gateway错误处理源码

首先要看的是配置类ErrorWebFluxAutoConfiguration.java,这里面向spring注册了两个实例,每个都非常重要,咱们先关注第一个,也就是说ErrorWebExceptionHandler的实现类是DefaultErrorWebExceptionHandler:

处理异常时,会通过FluxOnErrorResume调用到这个ErrorWebExceptionHandler的handle方法处理,该方法在其父类AbstractErrorWebExceptionHandler.java中,如下图,红框位置的代码是关键,异常返回内容就是在这里决定的:

展开这个getRoutingFunction方法,可见会调用renderErrorResponse来处理响应:

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

打开renderErrorResponse方法,如下所示,真相大白了!

protected Mono<ServerResponse> renderErrorResponse(ServerRequest request) {
  // 取出所有错误信息
  Map<String, Object> error = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL));

  // 构造返回的所有信息
  return ServerResponse
           // 控制返回码
           .status(getHttpStatus(error))
           // 控制返回ContentType
           .contentType(MediaType.APPLICATION_JSON)
           // 控制返回内容
           .body(BodyInserters.fromValue(error));
}

通过上述代码,咱们得到两个重要结论:

  • 返回给调用方的状态码,取决于getHttpStatus方法的返回值
  • 返回给调用方的body,取决于error的内容

都已经读到了这里,自然要看看getHttpStatus的内部,如下所示,status来自入参:

protected int getHttpStatus(Map<String, Object> errorAttributes) {
  return (int) errorAttributes.get("status");
}
  • 至此,咱们可以得出一个结论:getErrorAttributes方法的返回值是决定返回码和返回body的关键!
  • 来看看这个getErrorAttributes方法的庐山真面吧,在DefaultErrorAttributes.java中(回忆刚才看ErrorWebFluxAutoConfiguration.java的时候,前面曾提到里面的东西都很重要,也包括errorAttributes方法):
public Map<String, Object> getErrorAttributes(ServerRequest request, ErrorAttributeOptions options) {
        Map<String, Object> errorAttributes = this.getErrorAttributes(request, options.isIncluded(Include.STACK_TRACE));
        if (Boolean.TRUE.equals(this.includeException)) {
            options = options.including(new Include[]{Include.EXCEPTION});
        }

        if (!options.isIncluded(Include.EXCEPTION)) {
            errorAttributes.remove("exception");
        }

        if (!options.isIncluded(Include.STACK_TRACE)) {
            errorAttributes.remove("trace");
        }

        if (!options.isIncluded(Include.MESSAGE) && errorAttributes.get("message") != null) {
            errorAttributes.put("message", "");
        }

        if (!options.isIncluded(Include.BINDING_ERRORS)) {
            errorAttributes.remove("errors");
        }

        return errorAttributes;
    }

篇幅所限,就不再展开上述代码了,直接上结果吧:

  • 返回码来自determineHttpStatus的返回
  • message字段来自determineMessage的返回打开determineHttpStatus方法,终极答案揭晓,请关注中文注释:
private HttpStatus determineHttpStatus(Throwable error, MergedAnnotation<ResponseStatus> responseStatusAnnotation) {
        // 异常对象是不是ResponseStatusException类型
        return error instanceof ResponseStatusException
        // 如果是ResponseStatusException类型,就调用异常对象的getStatus方法作为返回值
        ? ((ResponseStatusException)error).getStatus()
        // 如果不是ResponseStatusException类型,再看异常类有没有ResponseStatus注解,
        // 如果有,就取注解的code属性作为返回值
        : (HttpStatus)responseStatusAnnotation.getValue("code", HttpStatus.class)
        // 如果异常对象既不是ResponseStatusException类型,也没有ResponseStatus注解,就返回500
        .orElse(HttpStatus.INTERNAL_SERVER_ERROR);
    }

另外,message字段的内容也确定了:

 private String determineMessage(Throwable error, MergedAnnotation<ResponseStatus> responseStatusAnnotation) {
        // 异常对象是不是BindingResult类型
        if (error instanceof BindingResult) {
            // 如果是,就用getMessage作为返回值
            return error.getMessage();
        }
        // 如果不是BindingResult类型,就看是不是ResponseStatusException类型
        else if (error instanceof ResponseStatusException) {
            // 如果是,就用getReason作为返回值
            return ((ResponseStatusException)error).getReason();
        } else {
            // 如果也不是ResponseStatusException类型,
            // 就看异常类有没有ResponseStatus注解,如果有就取该注解的reason属性作为返回值
            String reason = (String)responseStatusAnnotation.getValue("reason", String.class).orElse("");
            if (StringUtils.hasText(reason)) {
                return reason;
            } else {
                // 如果通过注解取得的reason也无效,就返回异常的getMessage字段
                return error.getMessage() != null ? error.getMessage() : "";
            }
        }
    }
  • 至此,源码分析已完成,最终的返回码和返回内容究竟如何控制,相信聪明的您心里应该有数了,下一篇《实战篇》咱们趁热打铁,写代码试试精确控制返回码和返回内容
  • 提前剧透,接下来的《实战篇》会有以下内容呈现:
  • 直接了当,控制返回码和body中的error字段
  • 小小拦路虎,见招拆招
  • 简单易用,通过注解控制返回信息
  • 终极方案,完全定制返回内容

到此这篇关于Spring Cloud Gateway过滤器精确控制异常返回(分析篇)的文章就介绍到这了,更多相关Spring Cloud Gateway过滤器内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

时间: 2021-11-24

如何为Spring Cloud Gateway加上全局过滤器

既然是一个网关.那么全局过滤器肯定是少不了的一个存在.像是鉴权.认证啥的不可能每个服务都做一次,一般都是在网关处就搞定了. Zuul他就有很强大的过滤器体系来给人使用. Gateway当然也不会差这么点东西. 对于SpringCloud体系来说,一切的实现都是那么的简单.那么废话不多说,直接开始写起来.   Gateway内部有一个接口 名为GlobalFilter,这个就是Gateway的全局过滤器接口,只要在应用中实现此接口后注册为Spring的Bean,背后就会帮你将这个实现注册到全局过滤

SpringCloud Gateway加载断言predicates与过滤器filters的源码分析

我们今天的主角是Gateway网关,一听名字就知道它基本的任务就是去分发路由.根据不同的指定名称去请求各个服务,下面是Gateway官方的解释: https://spring.io/projects/spring-cloud-gateway,其他的博主就不多说了,大家多去官网看看,只有官方的才是最正确的,回归主题,我们的过滤器与断言如何加载进来的,并且是如何进行对请求进行过滤的. 大家如果对SpringBoot自动加载的熟悉的话,一定知道要看一个代码的源码,要找到META-INF下的spring

spring cloud gateway全局过滤器实现向request header中放数据

gateway全局过滤器向request header放数据 exchange.getRequest().getHeaders().set(); 是不能向 headers中放文件的 这时配置一个gateway全局过滤器 filter中 做了向 header放数据 @Component public class AuthSignatureFilter implements GlobalFilter, Ordered { static Logger logger = LoggerFactory.ge

spring cloud gateway 全局过滤器的实现

全局过滤器作用于所有的路由,不需要单独配置,我们可以用它来实现很多统一化处理的业务需求,比如权限认证,IP访问限制等等. 接口定义类:org.springframework.cloud.gateway.filter.GlobalFilter public interface GlobalFilter { Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain); } gateway自带的GlobalFilte

Spring Cloud Gateway全局通用异常处理的实现

为什么需要全局异常处理 在传统 Spring Boot 应用中, 我们 @ControllerAdvice 来处理全局的异常,进行统一包装返回 // 摘至 spring cloud alibaba console 模块处理 @ControllerAdvice public class ConsoleExceptionHandler { @ExceptionHandler(AccessException.class) private ResponseEntity<String> handleAc

Spring Cloud Gateway全局异常处理的方法详解

前言 Spring Cloud Gateway是Spring官方基于Spring 5.0,Spring Boot 2.0和Project Reactor等技术开发的网关,Spring Cloud Gateway旨在为微服务架构提供一种简单而有效的统一的API路由管理方式.Spring Cloud Gateway作为Spring Cloud生态系中的网关,目标是替代Netflix ZUUL,其不仅提供统一的路由方式,并且基于Filter链的方式提供了网关基本的功能,例如:安全,监控/埋点,和限流等

Spring Cloud Gateway入门解读

Spring Cloud Gateway介绍 前段时间刚刚发布了Spring Boot 2正式版,Spring Cloud Gateway基于Spring Boot 2,是Spring Cloud的全新项目,该项目提供了一个构建在Spring 生态之上的API网关,包括:Spring 5,Spring Boot 2和Project Reactor. Spring Cloud Gateway旨在提供一种简单而有效的途径来发送API,并为他们提供横切关注点,例如:安全性,监控/指标和弹性.当前最新的

Spring Cloud Gateway使用Token验证详解

引入依赖 <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <ty

Spring Cloud GateWay 路由转发规则介绍详解

Spring在因Netflix开源流产事件后,在不断的更换Netflix相关的组件,比如:Eureka.Zuul.Feign.Ribbon等,Zuul的替代产品就是SpringCloud Gateway,这是Spring团队研发的网关组件,可以实现限流.安全认证.支持长连接等新特性. Spring Cloud Gateway Spring Cloud Gateway是SpringCloud的全新子项目,该项目基于Spring5.x.SpringBoot2.x技术版本进行编写,意在提供简单方便.可

详解Spring Cloud Gateway基于服务发现的默认路由规则

1.Spring Gateway概述 1.1 什么是Spring Cloud Gateway Spring Cloud Gateway是Spring官方基于Spring 5.0,Spring Boot 2.0和Project Reactor等技术开发的网关,Spring Cloud Gateway旨在为微服务架构提供一种简单而有效的统一的API路由管理方式.Spring Cloud Gateway作为Spring Cloud生态系中的网关,目标是替代Netflix ZUUL,其不仅提供统一的路由

Spring Cloud Gateway 服务网关快速实现解析

Spring Cloud Gateway 服务网关 API 主流网关有NGINX.ZUUL.Spring Cloud Gateway.Linkerd等:Spring Cloud Gateway构建于 Spring 5+,基于 Spring Boot 2.x 响应式的.非阻塞式的 API.同时,它支持 websockets,和 Spring 框架紧密集成,用来代替服务网关Zuul,开发体验相对来说十分不错. Spring Cloud Gateway 是 Spring Cloud 微服务平台的一个子

spring cloud gateway 限流的实现与原理

在高并发的系统中,往往需要在系统中做限流,一方面是为了防止大量的请求使服务器过载,导致服务不可用,另一方面是为了防止网络攻击. 常见的限流方式,比如Hystrix适用线程池隔离,超过线程池的负载,走熔断的逻辑.在一般应用服务器中,比如tomcat容器也是通过限制它的线程数来控制并发的:也有通过时间窗口的平均速度来控制流量.常见的限流纬度有比如通过Ip来限流.通过uri来限流.通过用户访问频次来限流. 一般限流都是在网关这一层做,比如Nginx.Openresty.kong.zuul.Spring

详解Spring Cloud Gateway 限流操作

开发高并发系统时有三把利器用来保护系统:缓存.降级和限流. API网关作为所有请求的入口,请求量大,我们可以通过对并发访问的请求进行限速来保护系统的可用性. 常用的限流算法比如有令牌桶算法,漏桶算法,计数器算法等. 在Zuul中我们可以自己去实现限流的功能 (Zuul中如何限流在我的书 <Spring Cloud微服务-全栈技术与案例解析>  中有详细讲解) ,Spring Cloud Gateway的出现本身就是用来替代Zuul的. 要想替代那肯定得有强大的功能,除了性能上的优势之外,Spr