Spring Cloud Gateway 简介(二)

书接上文, 前一篇介绍了 Spring Cloud Gateway 的基本组成、工作原理和路由, 本篇为

spring cloud gateway 第二小节, 主要介绍断言和过滤器

断言

基本概念

Predicate 来自于 Java8 的接口, 其接受一个输入参数(ServerWebExchange),返回一个布尔值结果。该接口包含多种默认方法来将 Predicate 组合成其他复杂的逻辑(比如:与,或,非)。可以用于接口请求参数校验、判断新老数据是否有变化需要进行更新操作。 支持的逻辑运算有:

  • and, 与操作, 即两个 Predicate 组成一个, 需要同时满足;
  • negate, 取反操作, 即对 Predicate 匹配结果取反;
  • or, 或操作, 即两个 Predicate 组成一个, 只需要满足其中之一即可

Spring Cloud Gateway 中 Predicate 为 Route 所定义的部分,用于条件匹配, 当定义多个 Predicate 时, 其逻辑连接运算符为 and.

Spring Cloud Gateway 内置了许多 Predicate, 这些 Predicate 的源码位于 org.springframework.cloud.gateway.handler.predicate 包下, 具体作用如下:


Spring Cloud Gateway 简介(二)


每一种 Predicate 都会对当前的客户端请求进行判断, 是否满足当前的要求, 如果满足则交给当前请求, 如果有很多个 Predicate, 并且一个请求满足这些 Predicate, 则按照配置的顺序第一个生效。

应用

官方提供的 Predicate 应用举例:

  1. After。 接受一个时间类型的参数, 当请求在这个设置的时间后发生, 就会被转发到此路由上. 以下示例表示匹配上海时间2019-09-20 12:12:12后所有的请求
<code>spring:
      cloud:
        gateway:
          routes:
          - id: after_route
            uri: http://example.org
            predicates:
            - After=2019-09-20T12:12:12.789+08:00[China/Shanghai]
/<code>
  1. Before。 与 After 相反, 接受一个时间类型的参数, 当请求在这个设置的时间前发生, 就会被转发到此路由上. 以下示例表示匹配上海时间2019-09-20 12:12:12前所有的请求
<code>spring:
      cloud:
        gateway:
          routes:
          - id: after_route
            uri: http://example.org
            predicates:
            - Before=2019-09-20T12:12:12.789+08:00[China/Shanghai]
/<code>
  1. Between。 接受两个时间类型的参数, 当请求在这个设置的时间区间发生, 就会被转发到此路由上. 以下示例表示匹配上海时间2019-09-20 12:12:12 ~ 2019-09-30 12:12:12之间所有的请求
<code>spring:
   cloud:
     gateway:
       routes:
       - id: after_route
         uri: http://example.org
         predicates:
         - Between=2019-09-20T12:12:12.789+08:00[China/Shanghai], 2019-09-30T12:12:12.789+08:00[China/Shanghai]
/<code>
  1. Cookie。 接受两个参数, 分别表示 cookie 的 name 和 cookie 的 值, 当请求信息中 cookie 带有配置的 name, 且其值为匹配的配置的值(正则匹配), 则通过此路由。 以下示例表示匹配请求信息中的 Cookie 中存在foo=bar的所有请求。
<code>spring:
  cloud:
    gateway:
      routes:
      - id: after_route
        uri: http://example.org
        predicates:
        - Cookie=foo, bar
/<code>
  1. Header。 接受两个参数,分别表示请求头名称,对应值的正则表达式。 当请求头中能有匹配的对应的键值对时, 则通过此路由, 以下示例表示匹配请求头中存在foo=bar的所有请求。
<code>spring:
   cloud:
     gateway:
       routes:
       - id: after_route
         uri: http://example.org
         predicates:
         - Header=foo, bar
/<code>
  1. Host。 接受一个参数,为 host 列表,多个 host 之间用英文,隔开。 host 是通过Ant风格的模式匹配的。 满足列表中的任一 host, 则通过此路由。 以下示例表示匹配请求头中 hostAnt匹配所有已foo.com结尾或者www.bar.开头的请求
<code>spring:
   cloud:
     gateway:
       routes:
       - id: after_route
         uri: http://example.org
         predicates:
         - Host=**.foo.com, www.bar.**
/<code>
  1. Method. 接受一个参数, 为请求方法。即GET/POST/PUT/DETELE/OPTION/HEADER等, 以下示例表示匹配请求方式为GET的所有请求
<code>spring:
    cloud:
      gateway:
        routes:
        - id: after_route
          uri: http://example.org
          predicates:
          - Method=GET
/<code>
  1. Path. 接受两个参数。 分别为 Spring 的PathMatcher列表和matchOptionalTrailingSeparator(可选)。 表示请求的路径匹配列表中的任一一个, 则通过此路由。
<code>spring:
  cloud:
    gateway:
      routes:
      - id: after_route
        uri: http://example.org
        predicates:
        - Path=/foo/{segment},/bar/{segment}
/<code>
  1. Query。 接受两个参数, 分别为请求的参数和请求参数值的正则(可选)。
<code>spring:
  cloud:
    gateway:
      routes:
      - id: after_route
        uri: http://example.org
        predicates:
        - Query=foo # 请求参数中有 foo,即通过此路由
        # - Query=foo, ba. # 请求参数中有 foo,且其值是以 ba 开头, 即通过此路由
/<code>
  1. RemoteAddr。 接受一个参数。 该参数为一个IPV4/IPV6网段的列表。注意,此处列表的大小不能小于1。
<code>spring:
   cloud:
     gateway:
       routes:
       - id: after_route
         uri: http://example.org
         predicates:
         - RemoteAddr=192.168.1.1/24
/<code>

一般情况下, 官方提供的 Predicate, 已经能满足我们的需求了。 为了可扩展性, 我们也可以自定义 Predicate。 主要的方法是继承 AbstractRoutePredicateFactory, 主要实现其中的两个方法:

  • shortcutFieldOrder(). Config 对应的字段
  • Predicate apply(Config config)。 具体的断言逻辑。

同时需要注意以下两点:

  • 自定义 Predicate 的构造方法用来装配置的类程序会自动把配置的 value 传入 apply 中的入参;
  • 自定义 Predicate 类需要加载到 Spring 容器中, 方式不限。

原理

spring cloud gateway 使用的 Spring WebFlux, 其入口的请求分发处理器为 org.springframework.web.reactive.DispatcherHandler (类似于 Spring MVC 中 DispatcherServlet)。 DispatcherHandler 在处理请求分发时, 通过 HandlerMapping#getHandler(ServerWebExchange) 获取 Handler。 如果匹配不到 WebHandler, 则会返回 HANDLER_NOT_FOUND_EXCEPTION。 若存在匹配的 WebHandler, 则执行其 handler() 方法(通过调用 HandlerAdapter#handle(ServerWebExchange, Object) 方法)。

在以上的过程中, 在获取 Handler 时, 是通过 RoutePredicateHandlerMapping#getHandlerInternal, 查找已配置的 Route 中,是否有匹配当前请求的 Route, 若能匹配到 Route, 则返回处理 Route 的 FilteringWebHandler, 否则不返回处理器。 由此可见, 我们配置的 Route 中的 Predicate 的具体应用到的地方就在此方法中, 有兴趣的小伙伴,可以跟踪下源代码。

过滤器

基本概念

Gateway 中的 Filter 与其他框架一样, 都是用于实现可扩展的切面逻辑。 Filter 最终是通过 filter chain 来形成链式调用的。

filter 的主要作用是将统一处理通过网关请求的服务的共性需求。 在 pre filter 可以做参数校验、 权限校验、 流量控制、 日志输出、 协议转换等, 在 post filter 可以做响应内容、 响应头的修改, 日志的修改, 流量控制等。

PS: 此处需要注意与 spring cloud netflex zuul 的对比, zuul 支持的过滤器类型有 pre/routing/post/error.

Spring Cloud Gateway 中从作用域来区分, 有两种, 一种是针对单个路由的 gateway filter, 它在配置文件中进行配置, 与 Predicate 类似。 另外一种是针对于所有路由的 global gateway filter。

应用

针对 Spring Cloud Gateway 2.1.1.RELEASE, 官方提供的常见的 Global Filter 有:


Spring Cloud Gateway 简介(二)


常见的 Route Filter 有:


Spring Cloud Gateway 简介(二)


配置时, 有默认的过滤器和路由指定的过滤器之分, 针对默认的过滤器的配置, 在spring.cloud.gateway.default-filters属性下配置, 示例如下:

<code>spring
  cloud:
    gateway:
      default-filters:
        - name: Hystrix
          args:
            name: fallbackcmd
            fallbackUri: forward:/fallback?service=default/<code>

具体配置,与Predicate一样, 在 spring.cloud.gateway.routes[i].filters[i] 属性下配置, 示例如下

<code>spring:
  cloud:
    gateway:
      routes:
      - id: add_request_header_route
        uri: lb://test-service
        filters:
        - AddRequestHeader=X-Request-Foo, Bar/<code>

关于自定义过滤器, 其实很简单, 只需要继承 GlobalFilter/GatewayFilter 和 Ordered 接口, 实现其中的 filter() 和 getOrder() 方法即可。 在 filter() 方法中定义需要实现的功能逻辑, getOrder() 方法确定当前过滤器执行的顺序。

PS:

- filter() 方法中不能断了过滤器链的处理,即需要通过 chain.filter(exchange, chain) 向下传递过滤器链;

- 官方声明, GlobalFilter 接口的定义以及用法在未来的版本里可能会发生变化。但是, 就目前来看, 是可用于生产的, 如果有自定义 GlobalFilter 的需求,理论上也可放心使用——未来即使接口定义以及使用方式发生变化,应该也是平滑过渡的(比如 Zuul 的 Fallback,原先叫ZuulFallbackProvider,后来改叫 FallbackProvider,中间就有段时间新旧使用方式都支持,后面才逐步废弃老的使用方式)

原理

常见全局过滤器

  • ForwardRoutingFilter。 该 Filter 会查看 exchange 的属性 ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR 的值,如果该值的scheme 是 forward,比如:forward://forward-to,则它会使用 Spring 的 DispatcherHandler 处理该请求。请求 URL 的路径部分,会被 forward URL中的路径覆盖。未修改的原始URL,会被追加到 ServerWebExchangeUtils.GATEWAY_ORIGINAL_REQUEST_URL_ATTR 属性中。 即其作用是用来做本地 forward。深入理解, 请查看相关源码。
  • LoadBalancerClientFilter。 该 Filter 会查看 exchange 的属性 ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR 的值,如果该值的 scheme 是 lb,比如:lb://lb-service ,它将会使用 Spring Cloud 的 LoadBalancerClient 来将 lb-service 解析成实际的 host/ip 和 port,并替换掉 ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR 的内容。原始地址会追加到 ServerWebExchangeUtils.GATEWAY_ORIGINAL_REQUEST_URL_ATTR 中。该过滤器还会查看 ServerWebExchangeUtils.GATEWAY_SCHEME_PREFIX_ATTR 属性,如果发现该属性的值是 lb ,也会执行相同逻辑。 默认情况下, 若无法在 LoadBalancer 找到指定服务的实例, 则会返回的 HTTP Status 为 503(Unavailable service), 可通过配置 spring.cloud.gateway.loadbalancer.use404=true 让 HTTP Status 返回 404。 当 LoadBalancer 返回的 ServiceInstance 的 isSecure 的值, 会覆盖请求的scheme。如若请求打到Gateway上使用的是 HTTPS ,但 ServiceInstance 的 isSecure 是 false,那么下游收到的则是 HTTP 请求,反之亦然。然而,如果该路由指定了 GATEWAY_SCHEME_PREFIX_ATTR 属性,那么前缀将会被剥离,并且路由URL中的 scheme 会覆盖 ServiceInstance 的配置。 LoadBalancer 是基于 Ribbon 实现的。
  • NettyRoutingFilter. ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR 的值的 scheme 是 http 或 https ,则会进过此 Filter。它使用 Netty HttpClient 向下游发送代理请求。获得的响应将放在 exchange的 ServerWebExchangeUtils.CLIENT_RESPONSE_ATTR 属性中,以便在后面的 filter中使用。 对于不使用 Netty 的场景, 官方也提供了 WebClientHttpRoutingFilter 过滤器。
  • NettyWriteResponseFilter。如果 exchange 中的 ServerWebExchangeUtils.CLIENT_RESPONSE_ATTR 属性中有 HttpClientResponse,则运行 NettyWriteResponseFilter 。该过滤器在所有其他过滤器执行完成后执行,并将代理响应协会网关的客户端侧。 对于不使用 Netty 的场景, 官方也提供了 WebClientWriteResponseFilter 过滤器。
  • RouteToRequestUrlFilter
    。如果 exchange 中的 ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR 属性中有一个 Route 对象,则运行 RouteToRequestUrlFilter 。它根据请求 URI 创建一个新 URI,但会使用该 Route 对象的 URI属性进行更新。新 URI 放到 exchange 的 ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR 属性中。如果 URI具有 scheme前缀,例如 lb//serviceid ,该 lb scheme将从 URI 中剥离,并放到 ServerWebExchangeUtils.GATEWAY_SCHEME_PREFIX_ATTR 中,方便后面的过滤器使用。
  • WebsocketRoutingFilter。如果 exchange 中的 ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR 属性的值的 scheme 是 ws/wss ,则运行 WebsocketRoutingFilter。它底层使用 Spring Web Socket 将Websocket 请求转发到下游。可为 URI添加 lb 前缀实现负载均衡,如 lb//serviceid。
  • GatewayMetricsFilter。 需要启用 Gateway Metrics,即添加 spring-boot-starter-actuator 依赖。 当 spring.cloud.gateway.metrics.enabled 的值为 true 时,会运行 GatewayMetricsFilter。此过滤器添加名为 gateway.requests 的metric 信息,其中包含以下标记:routeId:路由ID;routeUri:API将路由到的URI;outcome:由 HttpStatus.Series 分类;status:返回给客户端的 Http Status

这些指标暴露在 /actuator/metrics/gateway.requests 端点中,并且可以轻松与 Prometheus 整合

Filter 的执行顺序

针对于全局 filter, 每个 GlobalFilter 的实现类都需要实现一个 Ordered#getOrder() 以确定其执行的顺序, 默认全局过滤器的执行顺序如下:


Spring Cloud Gateway 简介(二)

当因实际的业务场景的需要我们自定义 Global Filter 时, 它的 order 根据以下两种情况决定:

  • 如果自定义的 Global Filter 实现了 Ordered 接口或者加了 @Order 注解, 那么它的 order 就是它自己设定的值
  • 否则, 它就没有 order

针对每个路由的 Filter, 其执行顺序的规则如下:

  • 如果 RouteFilter 实现了 Ordered 接口或者写了 @Order 注解, 那么它的 order就是它自己设定的值。
  • 否则, 它的 order 则是从 1 开始, 按照 Route 中定义的顺序依次排序。

在实际执行某个 Route 的时候, Spring Cloud Gateway 会将 Global Filter 和 Route Filter 结合起来并排序:

  • 没有给 order 的 Global Filter 则保持 order 为 null 去排序
  • 没有给 order 的 Route Filter 的 order 则从 1 开始, 根据 Route 中定义的顺序执行
  • 排序逻辑根据 AnnotationAwareOrderComparator
  • 对于 Pre Filter, 执行顺序与排序顺序
  • 对于 Post Filter, 执行顺序与排序顺序相反
  • 对于自定义 Global Filter, 则自定义的 Global Pre Filter 要在 Routing Filter 之前执行自定义的 Global Post Filter 要在 Routing Filter 之后执行或者 NettyWriteResponseFilter 之后执行
  • 对于自定义 Route Filter, 则自定义 Route Pre Filter 要在 ForwardPathFilter 和 RouteToRequestUrlFilter 之间,而且不需要实现 Ordered 接口或添加 @Order注解自定义的Route Post Filter比较少见,放在 Routing Filter 或者NettyWriteResponseFilter 之后执行


后续系列计划如下, 可以关注我, 及时收到最新文档:

  • Spring Cloud Alibaba
  • Spring Cloud Netflix
  • Ribbon
  • Feign
  • Hystrix
  • Resilience4J
  • Spring Cloud Config
  • Apollo
  • Spring Cloud Stream
  • Nacos
  • ZipKin
  • Skywalking

如果你有什么想了解的,也可以在文章下评论或者私信我。


分享到:


相關文章: