跳到主要内容

06、SpringCloud Gateway GlobalFilter全局过滤器详解

GlobalFilter概述

GlobalFilter是应用于所有路由的特殊过滤器。

GlobalFilter接口的实现类如下图所示:

 

每个类的说明如下:

 

当请求与路由匹配时,Web 处理程序会将所有的GlobalFilter和特定的GatewayFilter添加到过滤器链中。这个组合过滤器链是按org.springframework.core.Ordered接口排序的,也通过实现getOrder()方法来设置。

默认全局过滤器

ForwardRoutingFilter

RouteToRequestUrlFilter,转发路由网关过滤器。其根据 forward:// 前缀( Scheme )过滤处理,将请求转发到当前网关实例本地接口。

public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

 
      // 获得 请求Url
    URI requestUrl = (URI)exchange.getRequiredAttribute(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR);
    // 获取协议
    String scheme = requestUrl.getScheme();
    if (!ServerWebExchangeUtils.isAlreadyRouted(exchange) && "forward".equals(scheme)) {

 
        if (log.isTraceEnabled()) {

 
            log.trace("Forwarding to URI: " + requestUrl);
        }
        //  如果是forward ,则DispatcherHandler 匹配并转发到当前网关实例本地接口
        return this.getDispatcherHandler().handle(exchange);
    } else {

 
        return chain.filter(exchange);
    }
}

ReactiveLoadBalancerClientFilter

该过滤器,可以使用服务发现机制,即通过注册服务名去访问实际IP地址,多个相同服务时,还可以实现负载均衡。

ReactiveLoadBalancerClientFilter会在ServerWebExchange 查询gatewayRequestUrl 属性。如果 URL 是lb开头(例如lb://myservice),则它使用ReactorLoadBalancer将名称myservice解析为实际主机和端口,并替换同一属性中的 URI。

未修改的原始 URL 将附加到ServerWebExchangeUtils.GATEWAY_ORIGINAL_REQUEST_URL_ATTR属性中的列表中。过滤器还会查看ServerWebExchangeUtils.GATEWAY_SCHEME_PREFIX_ATTR属性以查看它是否等于lb。如果是这样,则适用相同的规则。

spring:
  cloud:
    gateway:
      routes:
      - id: myRoute
        uri: lb://service
        predicates:
        - Path=/service/**

默认情况下,当无法找到服务实例时LoadBalancer,将503返回。您可以通过设置将网关配置为返回 404
。Gateway 支持所有 LoadBalancer 功能。

spring.cloud.gateway.loadbalancer.use404=true。

NettyRoutingFilter

Netty 路由网关过滤器。其根据 http:// 或 https:// 前缀( Scheme )过滤处理,使用基于 Netty 实现的 HttpClient 请求后端 Http 服务。

如果ServerWebExchangeUtils.GATEWAY_ REQUEST_ URL_ ATTR请求属性中的URL 具有http 或https 前缀,NettyRoutingFilter 路由过滤器将运行,它使用 Netty Http Client 代理对下游的请求。响应信息放在ServerWebExchangeUtils.CLIENT_ RESPONSE_ ATTR属性中,在过滤器链中进行传递。

首先获取请求的URL及前缀,判断前缀是不是http或者https,如果该请求已经被路由或者前缀不合法,则调用过滤器链直接向后传递;否则正常对头部进行过滤操作。

NettyRoutingFilter 过滤器的构造函数有三个参数:

  • HttpClient httpClient : 基于 Netty 实现的 HttpClient,通过该属性请求后端 的 Http 服务
  • ObjectProvider headersFilters: ObjectProvider 类型 的 headersFilters,用于头部过滤
  • HttpClientProperties properties: Netty HttpClient 的配置属性

NettyWriteResponseFilter

如果有一个运行的NettyHttpClientResponse在ServerWebExchangeUtils.CLIENT_RESPONSE_ATTR属性。它在所有其他过滤器完成后运行,并将代理响应写回网关客户端响应。

public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
   
     
    return chain.filter(exchange)
        // 这个cleanup()的作用是关闭连接,此处的意思是出错时关闭response connection
        .doOnError(throwable -> cleanup(exchange))
        .then(Mono.defer(() -> {
   
     
            // 1.从exchange拿到response connection,它是被NettyRoutingFilter发请求后塞进去的
            Connection connection = exchange.getAttribute(CLIENT_RESPONSE_CONN_ATTR);
            if (connection == null) {
   
     
                return Mono.empty();
            }
            ServerHttpResponse response = exchange.getResponse();
            // 2.从connection接收字节流写入response
            final Flux<DataBuffer> body = connection
                .inbound()
                .receive()
                .retain()
                .map(byteBuf -> wrap(byteBuf, response));

            MediaType contentType = null;
            try {
   
     
                contentType = response.getHeaders().getContentType();
            }
            catch (Exception e) {
   
     }
            // 3.针对response是否为流媒体内容采取不同的返回调用,最终就返回给请求方了
            return (isStreamingMediaType(contentType)
                ? response.writeAndFlushWith(body.map(Flux::just))
                : response.writeWith(body));
        })).doOnCancel(() -> cleanup(exchange));
}

NettyWriteResponseFilterNettyRoutingFilter成对使用的网关过滤器。其将 NettyRoutingFilter 请求后端 Http 服务的响应写回客户端。

大体流程如下 :

 

另外,Spring Cloud Gateway 实现了WebClientHttpRoutingFilter / WebClientWriteResponseFilter ,功能上和 NettyRoutingFilter / NettyWriteResponseFilter 相同,差别在于基于 org.springframework.cloud.gateway.filter.WebClient实现的 HttpClient 请求后端 Http 服务。

RouteToRequestUrlFilter

这个过滤器用于将从request里获取的原始url转换成Gateway进行请求转发时所使用的url。

当请求进来时,RouteToRequestUrlFilter 会从 exchange 中获取 ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR 属性的值,该值是一个 Route 对象。若该对象不为空的话,RouteToRequestUrlFilter会基于请求 URL 及 Route 对象里的 URL 来创建一个新的 URL。新 URL 会被放到 exchange 的
ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR属性中。

如果URL 具有 scheme 前缀,例如 lb:ws://serviceid ,该 lb scheme将从URL中剥离,并放到 ServerWebExchangeUtils.GATEWAY_SCHEME_PREFIX_ATTR中,方便后面的过滤器使用。

源码如下:

public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

 
    // 获取Route 对象
    Route route = (Route)exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
    if (route == null) {

 
        return chain.filter(exchange);
    } else {

 
        log.trace("RouteToRequestUrlFilter start");
        // 获取请求URI 
        URI uri = exchange.getRequest().getURI();
        boolean encoded = ServerWebExchangeUtils.containsEncodedParts(uri);
        // 路由中的uri
        URI routeUri = route.getUri();
        if (hasAnotherScheme(routeUri)) {

 
            exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_SCHEME_PREFIX_ATTR, routeUri.getScheme());
            routeUri = URI.create(routeUri.getSchemeSpecificPart());
        }
        
        if ("lb".equalsIgnoreCase(routeUri.getScheme()) && routeUri.getHost() == null) {

 
            throw new IllegalStateException("Invalid host: " + routeUri.toString());
        } else {

 
            // 是将原始的url请求根据route中配置的uri,将请求的具体资源信息组合到一起,形成一个真正往后端服务的请求,将真实的请求url路径,配置到exchange中attribute的Map中
            URI mergedUrl = UriComponentsBuilder.fromUri(uri).scheme(routeUri.getScheme()).host(routeUri.getHost()).port(routeUri.getPort()).build(encoded).toUri();
            exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR, mergedUrl);
            return chain.filter(exchange);
        }
    }
}

WebsocketRoutingFilter

如果位于ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR交换属性中的 URL具有ws或wss方案,则 WebsocketRoutingFilter 路由过滤器运行。它使用 Spring Web Socket 基础结构向下游转发 web socket 请求。

您可以通过在 URI 前加上lb 前缀来对 websockets 进行负载平衡,例如lb:ws://serviceid.

如果你使用Sock JS作为普通 HTTP 的后备,你应该配置一个普通的 HTTP 路由以及 websocket 路由。
以下清单配置了一个 websocket 路由过滤器:

spring:
  cloud:
    gateway:
      routes:
      SockJS route
      - id: websocket_sockjs_route
        uri: http://localhost:3001
        predicates:
        - Path=/websocket/info/**
      Normal Websocket route
      - id: websocket_route
        uri: ws://localhost:3001
        predicates:
        - Path=/websocket/**

GatewayMetricsFilter

要启用网关指标,请将 spring-boot-starter-actuator 添加为项目依赖项。然后,默认情况下,只要属性spring.cloud.gateway.metrics.enabled未设置为false,网关指标过滤器就会运行。此过滤器添加了一个以spring.cloud.gateway.requests以下标签命名的计时器指标:

  • routeId: 路由ID。
  • routeUri:API 路由到的 URI。
  • outcome:结果,按HttpStatus.Series分类。
  • status:返回给客户端的请求的 HTTP 状态。
  • httpStatusCode:返回给客户端的请求的 HTTP 状态。
  • httpMethod:用于请求的 HTTP 方法。

此外,通过属性spring.cloud.gateway.metrics.tags.path.enabled(默认情况下,设置为 false),您可以使用标签激活额外的指标:

  • path: 请求的路径。

然后可以从/actuator/metrics/spring.cloud.gateway.requests中抓取这些指标,并且可以轻松地与 Prometheus 集成以创建Grafana 仪表板。

要启用prometheus 端点,请添加micrometer-registry-prometheus为项目依赖项。

案例演示

1. 添加相关依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/io.micrometer/micrometer-registry-prometheus -->
<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-registry-prometheus</artifactId>
</dependency>

1、 添加Bean:;

@Bean
MeterRegistryCustomizer<MeterRegistry> configurer(
        @Value("${spring.application.name}") String applicationName) {

 
    return (registry) -> registry.config().commonTags("application", applicationName);
}

3. 添加YML配置:

server:
  port: 80
spring:
  cloud:
    gateway:
      enabled: true
      metrics:
        开启 GatewayMetricsFilter
        enabled: true
      routes:
        - id: app-service001 路由唯一ID
          uri: http://localhost:9000    目标URI,
          predicates:   断言,为真则匹配成功
            - Path=/app1/** 配置规则Path,如果是app1开头的请求,则会将该请求转发到目标URL
          filters:
            - AddRequestHeader=X-Request-Foo, Bar  定义了一个 Filter,所有的请求转发至下游服务时会添加请求头 X-Request-Foo:Bar ,由AddRequestHeaderGatewayFilterFactory 来生产。
  application:
    name: gateway
management:
  endpoint:
    health:
      是否显示health详细信息
      show-details: always
      show-components: always
  endpoints:
    Web端点的配置属性
    web:
      exposure:
         开放端点的ID集合(eg:['health','info','beans','env']),配置为“*”表示全部
        include: '*'
  metrics:
    tags:
       应用名称添加到计量器注册表的tag中
      application: ${
   
     spring.application.name}

4. 访问Gateway 监控端点,返回了一些访问总次数、路由状态码等信息

http://localhost/actuator/metrics/spring.cloud.gateway.requests

 

5. 访问Promethues 监控端点,可以看到网关的监控指标被采集到。

http://localhost/actuator/prometheus

 

6. 集成Grafana,导入监控面板的时候,发现这个已经很久没更新了,导入后,发现查询指标时都没有数据,集成还有有点问题,不推荐使用。

 

WebClientHttpRoutingFilter

WebClientHttpRoutingFilterHttp 路由网关过滤器。其根据 http:// 或 https:// 前缀( Scheme )过滤处理,使用基于 org.springframework.cloud.gateway.filter.WebClient实现的 HttpClient 请求后端 Http 服务。

WebClientWriteResponseFilter,与 WebClientHttpRoutingFilter成对使用的网关过滤器。其将 WebClientWriteResponseFilter请求后端 Http 服务的响应写回客户端。

大体流程如下 :

 

参考文档

https://www.iocoder.cn/Spring-Cloud-Gateway/filter-netty-routing/