在微服务架构中,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的条件之一即可
四、自定义断言
除了使用内置断言,你还可以自定义断言逻辑。以下是实现步骤和示例代码:
实现步骤
- 创建一个类实现
RoutePredicateFactory接口。 - 重写
apply方法,编写自定义匹配逻辑。 - 将自定义断言注册到 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 后,断言的执行流程如下:
- Gateway 依次检查每个路由(Route)的断言。
- 若请求满足某个路由的所有断言,则该路由被选中。
- 选中的路由会执行对应的过滤器(Filter),最终将请求转发到目标服务。
六、断言 vs 过滤器
断言和过滤器共同构成了 Gateway 的核心功能,但它们的作用不同:
- 断言:决定请求是否应该被路由到目标服务,主要用于筛选请求。
- 过滤器:对请求或响应进行修改,例如添加请求头、限流、记录日志等。
总结
通过利用内置断言和自定义断言,结合灵活的组合方式,我们可以根据各种条件(路径、时间、参数、请求头、Cookie 等)智能地筛选请求,精确控制哪些请求会被处理,哪些会被拒绝,从而实现灵活的路由规则。