Spring Cloud Gateway将路由匹配作为Spring WebFlux HandlerMapping基础架构的一部分。
Spring Cloud Gateway 包含许多内置的Route Predicate Factories。所有这些Predicate 都匹配HTTP请求的不同属性。多种Predicate 工厂可以通过逻辑and进行组合。
Spring Cloud Gateway 创建 Route 对象时, 使用 RoutePredicateFactory 创建 Predicate 对象,Predicate 对象可以赋值给 Route。
如上图所示,gateway默认提供了11中路由断言工厂供用户组合使用。项目启动时会默认加载这些工厂:
【1】 After Route Predicate Factory
匹配发生在指定日期后
的请求。
The after route predicate factory takes one parameter, a datetime
. This predicate matches requests that happen after
the specified datetime.
- id: payment_routh2 # 路由id,没有
uri: lb://cloud-payment-service
predicates:
- After=2024-02-05T15:10:03.685+08:00[Asia/Shanghai] # 断言,路径相匹配的进行路由
其中After后的时间串可以如下得到:
public class ZonedDateTimeDemo
{
public static void main(String[] args)
{
ZonedDateTime zbj = ZonedDateTime.now(); // 默认时区
System.out.println(zbj);
// ZonedDateTime zny = ZonedDateTime.now(ZoneId.of("America/New_York")); // 用指定时区获取当前时间
// System.out.println(zny);
}
}
【2】Before Route Predicate Factory
匹配发生在指定日期时间 前
的请求。
- id: payment_routh2 # 路由id,没有
uri: lb://cloud-payment-service
predicates:
- Before=2024-02-05T15:10:03.685+08:00[Asia/Shanghai] # 断言,路径相匹配的进行路由
【3】Between Route Predicate Factory
匹配发生在两个指定日期时间 中间
的请求。
- id: payment_routh2 # 路由id,没有
uri: lb://cloud-payment-service
predicates:
- Between=2024-02-05T15:10:03.685+08:00[Asia/Shanghai],2024-02-25T15:10:03.685+08:00[Asia/Shanghai]
# 断言,路径相匹配的进行路由
【4】Cookie Route Predicate Factory
Cookie Route Predicate需要两个参数,一个是 Cookie name ,一个是正则表达式。
路由规则会通过获取对应的 Cookie name 值和正则表达式去匹配,如果匹配上就会执行路由,如果没有匹配上则不执行
spring:
cloud:
gateway:
routes:
- id: cookie_route
uri: https://example.org
predicates:
- Cookie=chocolate, ch.p
【5】Header Route Predicate Factory
请求头匹配,需要两个参数:请求头名称和正则表达式。如果请求头属性值和正则表达式匹配,则执行路由。
spring:
cloud:
gateway:
routes:
- id: header_route
uri: https://example.org
predicates:
- Header=X-Request-Id, \d+
【6】Host Route Predicate Factory
主机地址匹配,只有一个参数:一组主机列表,其是Ant风格,以.为分隔符。如果请求头中的Host匹配主机列表,则执行路由。
spring:
cloud:
gateway:
routes:
- id: host_route
uri: https://example.org
predicates:
- Host=**.somehost.org,**.anotherhost.org
参数值同样支持URI 模板变量,比如{sub}.myhost.org
。如果一个请求有请求头Host且值比如www.somehost.org或beta.somehost.org或www.anotherhost.org均会执行路由。
【7】Method Route Predicate Factory
请求方法匹配,参数值为一个或多个请求方法。
spring:
cloud:
gateway:
routes:
- id: method_route
uri: https://example.org
predicates:
- Method=GET,POST
【8】Path Route Predicate Factory
路径匹配,支持通配符和URI template variables。
spring:
cloud:
gateway:
routes:
- id: host_route
uri: https://example.org
predicates:
- Path=/red/{segment},/blue/{segment}
{segment},其实就是ServerWebExchangeUtils.URI_TEMPLATE_VARIABLES_ATTRIBUTE
得到的map中,map.get("segment")
对应的值。
匹配所有请求:
- Path=/**
【9】Query Route Predicate Factory
查询参数匹配,两个参数:参数名和值正则表达式(可选)。
- 如果只有一个参数,参数名,则表示查询参数必须包含该参数
- 如果有两个参数,则表示参数值还要符合正则表达式
一个参数实例:
spring:
cloud:
gateway:
routes:
- id: query_route
uri: https://example.org
predicates:
- Query=green
两个参数实例:
spring:
cloud:
gateway:
routes:
- id: query_route
uri: https://example.org
predicates:
- Query=red, gree.
【10】The RemoteAddr Route Predicate Factory
请求目标地址匹配,只有一个参数:为一组IPV4或IPV6的地址,比如192.168.0.1/16。
spring:
cloud:
gateway:
routes:
- id: remoteaddr_route
uri: https://example.org
predicates:
- RemoteAddr=192.168.1.1/24
【11】Weight Route Predicate Factory
The weight route predicate factory takes two arguments: group and weight. The weights are calculated per group.
spring:
cloud:
gateway:
routes:
- id: weight_high
uri: https://weighthigh.org
predicates:
- Weight=group1, 8
- id: weight_low
uri: https://weightlow.org
predicates:
- Weight=group1, 2
This route would forward ~80% of traffic to weighthigh.org and ~20% of traffic to weighlow.org
【12】自定义路由断言
这里我们可以参考AfterRoutePredicateFactory 来自定义路由断言。
public class AfterRoutePredicateFactory extends AbstractRoutePredicateFactory<AfterRoutePredicateFactory.Config> {
/**
* DateTime key.
*/
public static final String DATETIME_KEY = "datetime";
public AfterRoutePredicateFactory() {
super(Config.class);
}
@Override
public List<String> shortcutFieldOrder() {
return Collections.singletonList(DATETIME_KEY);
}
@Override
public Predicate<ServerWebExchange> apply(Config config) {
return new GatewayPredicate() {
@Override
public boolean test(ServerWebExchange serverWebExchange) {
final ZonedDateTime now = ZonedDateTime.now();
return now.isAfter(config.getDatetime());
}
@Override
public Object getConfig() {
return config;
}
@Override
public String toString() {
return String.format("After: %s", config.getDatetime());
}
};
}
public static class Config {
@NotNull
private ZonedDateTime datetime;
public ZonedDateTime getDatetime() {
return datetime;
}
public void setDatetime(ZonedDateTime datetime) {
this.datetime = datetime;
}
}
}
总结如下:
- 要么继承AbstractRoutePredicateFactory抽象类
- 要么实现RoutePredicateFactory接口
- 开头任意取名,但是必须以RoutePredicateFactory后缀结尾
① 新建MyRoutePredicateFactory继承AbstractRoutePredicateFactory类
@Component
public class MyRoutePredicateFactory extends AbstractRoutePredicateFactory<MyRoutePredicateFactory.Config>
{
}
② 重写apply方法
假设需要请求传递参数userType,且值满足某种规则。
@Component
public class MyRoutePredicateFactory extends AbstractRoutePredicateFactory<MyRoutePredicateFactory.Config>
{
@Override
public Predicate<ServerWebExchange> apply(MyRoutePredicateFactory.Config config)
{
return new Predicate<ServerWebExchange>()
{
@Override
public boolean test(ServerWebExchange serverWebExchange)
{
//检查request的参数里面,userType是否为指定的值,符合配置就通过
//http://localhost:9527/pay/gateway/get/1?userType=diamond
String userType = serverWebExchange.getRequest().getQueryParams().getFirst("userType");
if (userType == null) {
return false;
}
//如果说参数存在,就和config的数据进行比较
if(userType.equalsIgnoreCase(config.getUserType())){
return true;
}
return false;
}
};
}
}
③ 新建apply方法所需要的静态内部类MyRoutePredicateFactory.Config
@Component
public class MyRoutePredicateFactory extends AbstractRoutePredicateFactory<MyRoutePredicateFactory.Config>
{
//空参构造方法,内部调用super
public MyRoutePredicateFactory()
{
super(MyRoutePredicateFactory.Config.class);
}
//这个Config类就是我们的路由断言规则,重要
@Validated
public static class Config
{
@Setter
@Getter
@NotEmpty
private String userType; //钻/金/银和yml配置的会员等级
}
@Override
public Predicate<ServerWebExchange> apply(MyRoutePredicateFactory.Config config)
{
return new Predicate<ServerWebExchange>()
{
@Override
public boolean test(ServerWebExchange serverWebExchange)
{
//检查request的参数里面,userType是否为指定的值,符合配置就通过
//http://localhost:9527/pay/gateway/get/1?userType=diamond
String userType = serverWebExchange.getRequest().getQueryParams().getFirst("userType");
if (userType == null) {
return false;
}
//如果说参数存在,就和config的数据进行比较
if(userType.equalsIgnoreCase(config.getUserType())){
return true;
}
return false;
}
};
}
}
④ 支持短路配置
重写shortcutFieldOrder方法:
@Component
public class MyRoutePredicateFactory extends AbstractRoutePredicateFactory<MyRoutePredicateFactory.Config>
{
public MyRoutePredicateFactory()
{
super(MyRoutePredicateFactory.Config.class);
}
//这个Config类就是我们的路由断言规则,重要
@Validated
public static class Config
{
@Setter
@Getter
@NotEmpty
private String userType; //钻/金/银和yml配置的会员等级
}
@Override
public List<String> shortcutFieldOrder() {
return Collections.singletonList("userType");
}
@Override
public Predicate<ServerWebExchange> apply(MyRoutePredicateFactory.Config config)
{
return new Predicate<ServerWebExchange>()
{
@Override
public boolean test(ServerWebExchange serverWebExchange)
{
//检查request的参数里面,userType是否为指定的值,符合配置就通过
//http://localhost:9527/pay/gateway/get/1?userType=diamond
String userType = serverWebExchange.getRequest().getQueryParams().getFirst("userType");
if (userType == null) {
return false;
}
//如果说参数存在,就和config的数据进行比较
if(userType.equalsIgnoreCase(config.getUserType())){
return true;
}
return false;
}
};
}
}
Fully Expanded Arguments
- name: My
args:
userType: diamond
Shortcut Configuration:
- My=diamond
浏览器请求测试:http://localhost:9527/pay/gateway/get/1?userType=diamond