断言:灵活路由的关键。

127 阅读4分钟

在微服务架构中,API 网关扮演着至关重要的角色,它是系统的入口,负责处理所有客户端的请求。Spring Cloud Gateway 作为 Spring 生态中强大的 API 网关解决方案,其断言机制是实现灵活路由的核心功能。简单来说,断言就像是 “门卫”,只有满足特定条件的请求才能通过并被处理,那么它和过滤器有什么区别呢?

一、断言(Predicate)的本质

首先我们先从断言开始讲

断言本质上是一个 Java 函数式接口 java.util.function.Predicate,在 Spring Cloud Gateway 中,它接收一个 ServerWebExchange 对象,该对象包含了 HTTP 请求和响应的相关信息。断言返回一个布尔值,以此决定请求是否继续处理。

  • 返回 true:请求符合条件,继续处理,例如将请求路由到目标服务。
  • 返回 false:请求被过滤掉,不会继续处理。

二、断言的分类

Spring Cloud Gateway 内置了多种断言工厂,下面介绍几种常见的断言类型。

1. 基于请求路径(Path)

该断言依据请求的 URL 路径进行匹配。例如:

predicates:
  - Path=/api/users/**  # 匹配所有以/api/users/开头的路径

2. 基于请求方法(Method)

根据 HTTP 方法(如 GET、POST 等)进行匹配。示例如下:

predicates:
  - Method=GET,POST  # 只允许GET和POST请求

3. 基于请求参数(Query)

判断请求参数是否存在或其值是否匹配。例如:

predicates:
  - Query=token  # 要求请求必须包含token参数
  - Query=page, \d+  # 要求page参数的值是数字

4. 基于请求头(Header)

依据请求头是否存在或其值是否匹配来筛选请求。示例:

predicates:
  - Header=X-Request-Id, \d+  # 要求X-Request-Id头的值是数字

5. 基于请求时间(Time)

根据请求时间是否在指定范围内进行匹配。例如:

predicates:
  - Between=2023-01-01T00:00:00, 2023-12-31T23:59:59  # 只允许2023年内的请求

6. 基于 Cookie

判断 Cookie 是否存在或其值是否匹配。示例:

predicates:
  - Cookie=sessionId, [0-9a-f]+  # 要求sessionId是数字或字母

7. 基于 Host

根据请求的 Host(域名)进行匹配。示例:

predicates:
  - Host=**.example.com  # 匹配所有example.com的子域名

三、断言的组合使用

多个断言可以组合起来,形成更复杂的匹配规则。

AND 关系

默认情况下,多个断言之间是 AND 关系,即所有断言都必须满足。例如:

predicates:
  - Path=/api/users/**
  - Method=GET
  # 必须同时满足:路径以/api/users/开头 且 是GET请求

OR 关系

通过 - id 给断言分组,不同组之间是 OR 关系。示例:

routes:
  - id: route1
    uri: http://service1
    predicates:
      - Path=/api/users/**
      - Method=GET
  - id: route2
    uri: http://service2
    predicates:
      - Path=/api/products/**
      - Method=POST
  # 满足route1或route2的条件之一即可

四、自定义断言

除了使用内置断言,你还可以自定义断言逻辑。以下是实现步骤和示例代码:

实现步骤

  1. 创建一个类实现 RoutePredicateFactory 接口。
  2. 重写 apply 方法,编写自定义匹配逻辑。
  3. 将自定义断言注册到 Spring 容器中。

示例代码

import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import java.util.function.Predicate;

@Component
public class CustomHeaderRoutePredicateFactory 
    extends AbstractRoutePredicateFactory<CustomHeaderRoutePredicateFactory.Config> {

    public CustomHeaderRoutePredicateFactory() {
        super(Config.class);
    }

    @Override
    public Predicate<ServerWebExchange> apply(Config config) {
        return exchange -> {
            // 获取请求头
            String headerValue = exchange.getRequest().getHeaders().getFirst(config.getName());
            // 判断是否包含特定值
            return headerValue != null && headerValue.contains(config.getValue());
        };
    }

    public static class Config {
        private String name;
        private String value;

        // getters and setters
        public String getName() { return name; }
        public void setName(String name) { this.name = name; }
        public String getValue() { return value; }
        public void setValue(String value) { this.value = value; }
    }
}

配置文件中使用自定义断言

predicates:
  - CustomHeader=X-Custom-Header, custom-value  # 自定义断言

五、断言的执行流程

客户端发送请求到 Gateway 后,断言的执行流程如下:

  1. Gateway 依次检查每个路由(Route)的断言。
  2. 若请求满足某个路由的所有断言,则该路由被选中。
  3. 选中的路由会执行对应的过滤器(Filter),最终将请求转发到目标服务。

六、断言 vs 过滤器

断言和过滤器共同构成了 Gateway 的核心功能,但它们的作用不同:

  • 断言:决定请求是否应该被路由到目标服务,主要用于筛选请求。
  • 过滤器:对请求或响应进行修改,例如添加请求头、限流、记录日志等。

总结

通过利用内置断言和自定义断言,结合灵活的组合方式,我们可以根据各种条件(路径、时间、参数、请求头、Cookie 等)智能地筛选请求,精确控制哪些请求会被处理,哪些会被拒绝,从而实现灵活的路由规则。