SpringCloud-Gateway之RoutePredicateFactory
前言:本篇介绍SpringCloud的网关模块的RoutePredicateFactory,通过提供的RoutePredicateFactory来实现配置不同的路由规则。目前支持12种RoutePredicateFactory,简介如下:QueryRoutePredicateFactory:根据query参数路由PathRoutePredicateFactory:根据Path路由AfterRout
前言:
本篇介绍SpringCloud的网关模块的RoutePredicateFactory,通过提供的RoutePredicateFactory来实现配置不同的路由规则。目前支持12种RoutePredicateFactory,简介如下:
-
QueryRoutePredicateFactory:根据query参数路由
-
PathRoutePredicateFactory:根据Path路由
-
AfterRoutePredicateFactory:根据请求的时间路由,在指定时间之后
-
BeforeRoutePredicateFactory:根据请求的时间路由,在指定时间之前
-
BetweenRoutePredicateFactory:根据请求的时间路由,在指定时间范围内
-
CookieRoutePredicateFactory:根据请求头中的Cookie路由
-
HeaderRoutePredicateFactory:根据请求头中的值路由
-
HostRoutePredicateFactory:根据请求头中的Host路由
-
MethodRoutePredicateFactory:根据请求Method路由
-
RemoteAddrRoutePredicateFactory:根据请求的远程IP路由
-
WeightRoutePredicateFactory:根据权重路由
-
ReadBodyPredicateFactory :暂未官方文档说明
1、QueryRoutePredicateFactory
根据查询的参数路由,配置关键词:Query,示例如下:
spring:
cloud:
gateway:
routes:
- id: query_hello # 自定义的路由ID
uri: forward:/hello # 跳转的路径,这里forward协议是跳转到当前的服务的API
predicates:
- Query=q, hello # 路由规则,根据查询条件,需要满足{url}?q=hello
-
Query={key}:只需要满足query参数中存在key即可
-
Query={key}, {value},需要满足query的参数中存在key的参数值必须为value
-
其中,key不支持正则表达式,value支持正则表达式
源码:
public boolean test(ServerWebExchange exchange) {
//如果没有配置{value},则只需要判断{key}是否存在即可
if (!StringUtils.hasText(config.regexp)) {
return exchange.getRequest().getQueryParams()
.containsKey(config.param);
}
//校验{value}是否匹配
List<String> values = exchange.getRequest().getQueryParams()
.get(config.param);
if (values == null) {
return false;
}
for (String value : values) {
if (value != null && value.matches(config.regexp)) {
return true;
}
}
return false;
}
2、PathRoutePredicateFactory
根据请求Path路由,配置关键词:Path,示例如下:
spring:
cloud:
gateway:
routes:
- id: path_baidu
uri: https://www.baidu.com
predicates:
- Path=/baidu
- id: path_var
uri: forward:/hello?var={ziya}
predicates:
- Path=/var/{ziya}
filters:
- AddRequestParameter=ziya, zy-{ziya} # 在AddRequestParameter Filter中引用URI模板变量:ziya
- id: path_regex
uri: forward:/hello
predicates:
- Path=/regex/**
支持的配置格式:
-
Path=/baidu:固定路径
-
Path=/var/{ziya}:支持定义URL模板变量,该变量可以在自定义的GatewayFilter中使用,也可以结合springcloud-gateway自带的GatewayFilter使用,在配置文件中使用该变量
//在自定义的GatewayFilter中读取URL模板变量:hello
String value = ServerWebExchangeUtils.getUriTemplateVariables(exchange).get("hello");
-
Path=/regex/**:支持模糊匹配的路径,该配置将/regex下的所有请求转到指定的uri。
源码:
//根据配置的路径创建Pattern
synchronized (this.pathPatternParser) {
pathPatternParser.setMatchOptionalTrailingSeparator(
config.isMatchOptionalTrailingSeparator());
config.getPatterns().forEach(pattern -> {
PathPattern pathPattern = this.pathPatternParser.parse(pattern);
pathPatterns.add(pathPattern);
});
}
public boolean test(ServerWebExchange exchange) {
//获得Path
PathContainer path = parsePath(
exchange.getRequest().getURI().getRawPath());
//用编译好的Patterns来匹配Path
Optional<PathPattern> optionalPathPattern = pathPatterns.stream()
.filter(pattern -> pattern.matches(path)).findFirst();
//如果匹配到,将URI中的模板参数提取出来,放到Exchange中
if (optionalPathPattern.isPresent()) {
PathPattern pathPattern = optionalPathPattern.get();
traceMatch("Pattern", pathPattern.getPatternString(), path, true);
PathMatchInfo pathMatchInfo = pathPattern.matchAndExtract(path);
putUriTemplateVariables(exchange, pathMatchInfo.getUriVariables());
return true;
}
else {
traceMatch("Pattern", config.getPatterns(), path, false);
return false;
}
}
3、AfterRoutePredicateFactory
根据请求时间路由,配置关键词:After,示例如下:
spring:
cloud:
gateway:
routes:
- id: after_world
uri: forward:/world
predicates:
- After=2020-05-28T22:10:00.000+08:00[Asia/Shanghai]
- id: after_baidu
uri: https://www.baidu.com
predicates:
- After=2020-05-28T22:00:00.000+08:00[Asia/Shanghai]
当请求的时间在指定时间之后,就会将请求路由到指定的uri上
注意:
-
如果有多个满足条件的after配置,则会路由到第一个uri。
-
参数格式,满足ZonedDateTime格式,
如:2020-05-28T22:00:00.000+08:00[Asia/Shanghai]
源码:
public boolean test(ServerWebExchange serverWebExchange) {
final ZonedDateTime now = ZonedDateTime.now();
return now.isAfter(config.getDatetime());
}
4、BeforeRoutePredicateFactory
根据请求时间路由,配置关键词:Before,示例如下:
spring:
cloud:
gateway:
routes:
- id: before_hello
uri: forward:/hello
predicates:
- Before=2020-05-29T22:01:00.000+08:00[Asia/Shanghai]
- id: before_world
uri: forward:/world
predicates:
- Before=2020-05-29T22:00:00.000+08:00[Asia/Shanghai]
当请求的时间在指定时间之前,将会将请求路由到指定的uri上
注意(同after):
-
如果有多个满足条件的before配置,则会路由到第一个uri。
-
参数格式,满足ZonedDateTime格式,
如:2020-05-28T22:00:00.000+08:00[Asia/Shanghai]
源码:
public boolean test(ServerWebExchange serverWebExchange) {
final ZonedDateTime now = ZonedDateTime.now();
return now.isBefore(config.getDatetime());
}
5、BetweenRoutePredicateFactory
根据请求时间路由,配置关键词:Between,示例如下:
spring:
cloud:
gateway:
routes:
- id: before_world
uri: forward:/world
predicates:
- Between=2020-05-29T10:00:00.000+08:00[Asia/Shanghai], 2020-05-29T11:00:00.000+08:00[Asia/Shanghai]
- id: before_hello
uri: forward:/hello
predicates:
- Between=2020-05-29T10:40:00.000+08:00[Asia/Shanghai], 2020-05-29T10:55:00.000+08:00[Asia/Shanghai]
当请求的时间在指定时间范围内,将会将请求路由到指定的uri上
注意(同after):
-
如果有多个满足条件的between配置,则会路由到第一个uri。
-
参数格式,满足ZonedDateTime格式,
如:2020-05-28T22:00:00.000+08:00[Asia/Shanghai]
-
如果同时存在:before,after,between配置,会路由到第一个满足条件的uri
源码:
public boolean test(ServerWebExchange serverWebExchange) {
final ZonedDateTime now = ZonedDateTime.now();
return now.isAfter(config.getDatetime1())
&& now.isBefore(config.getDatetime2());
}
6、CookieRoutePredicateFactory
根据请求头中的Cookie路由,配置关键词:Cookie,示例如下:
spring:
cloud:
gateway:
routes:
- id: cookie_hello2
uri: forward:/hello
filters:
- AddRequestParameter=var, cookie-regexp
predicates:
- Cookie=X-key, v.*
- id: cookie_hello
uri: forward:/hello
filters:
- AddRequestParameter=var, cookie-no-value
predicates:
- Cookie=X-key, value
当请求中存在X-key=value的Cookie,则将请求路由到指定的uri,如果有多个满足,则路由到第一个匹配的URI。匹配规则如下:
-
Cookie=X-key, value:指定Cookie的值
-
Cookie=X-key, v.*:正则表达式匹配Cookie的值
源码:
public boolean test(ServerWebExchange exchange) {
List<HttpCookie> cookies = exchange.getRequest().getCookies()
.get(config.name);
if (cookies == null) {
return false;
}
for (HttpCookie cookie : cookies) {
//正则表达式匹配
if (cookie.getValue().matches(config.regexp)) {
return true;
}
}
return false;
}
7、HeaderRoutePredicateFactory
根据请求头中的值路由,配置关键词:Header,示例如下:
spring:
cloud:
gateway:
routes:
- id: header_hello2
uri: forward:/hello
filters:
- AddRequestParameter=var, header-value-zy
predicates:
- Header=X-zy, v-zy
- id: header_hello1
uri: forward:/hello
filters:
- AddRequestParameter=var, header-no-value
predicates:
- Header=X-zy
当请求中存在X-key=value的Header,则将请求路由到指定的uri,如果有多个满足,则路由到第一个匹配的URI。配置Value时,支持正则表达式。
注意:如果需要按先匹配指定值优先,然后再匹配Header名,则需要将指定Header值的路由配置在前面。
源码:
//是否配置了Header值
boolean hasRegex = !StringUtils.isEmpty(config.regexp);
public boolean test(ServerWebExchange exchange) {
List<String> values = exchange.getRequest().getHeaders()
.getOrDefault(config.header, Collections.emptyList());
//如果不存在指定的Header名,返回不匹配
if (values.isEmpty()) {
return false;
}
//如果指定了Header值
if (hasRegex) {
// 通过正则表达式匹配
return values.stream()
.anyMatch(value -> value.matches(config.regexp));
}
//如果配置的时没有指定Header值,则不用判断值,直接匹配
return true;
}
8、HostRoutePredicateFactory
根据请求头中的Host路由,配置关键词:Host,示例如下
spring:
cloud:
gateway:
routes:
- id: host-hello-dev
uri: forward:/hello
filters:
- AddRequestParameter=var, dev
predicates:
- Host=dev.wx.com
- id: host-hello-multi
uri: forward:/hello
filters:
- AddRequestParameter=var, multi
predicates:
- Host=dev.wx.com, qa.wx.com
- id: host-hello-multi-var
uri: forward:/hello
filters:
- AddRequestParameter=var, {env}
predicates:
- Host={env}.wx.com, {env}.bd.com
- id: host-hello-star
uri: forward:/hello
filters:
- AddRequestParameter=var, env-star
predicates:
- Host=**.wx.com
- id: host-hello-env
uri: forward:/hello
filters:
- AddRequestParameter=var, env-{env}
predicates:
- Host={env}.wx.com
根据请求头中的Host属性来匹配,如果能匹配到,则将请求路由到指定URI上,如果配置了多个Host,则路由到第一个匹配。匹配规则如下:
-
Host=dev.wx.com:根据固定的Host值匹配
-
Host=**.wx.com:根据通配符匹配
-
Host={env}.wx.com:和通配符效果相同,区别是:可以在自定义的GatewayFilter中通过API获取URI模板变量或者通过配置springcloud-gateway提供的GatewayFilter时引用URI模板变量。
-
Host=dev.wx.com, qa.wx.com:可以配置多个Host来进行匹配
URI模板变量的使用方式:
//通过API获取
ServerWebExchangeUtils.getUriTemplateVariables(exchange).get("env");
//配置文件中配置
filter:
- AddRequestParameter=var, env-{env}
源码:
public boolean test(ServerWebExchange exchange) {
//取Header中的第一个host
String host = exchange.getRequest().getHeaders().getFirst("Host");
//如果配置了多个pattern,查找第一个匹配的pattern
Optional<String> optionalPattern = config.getPatterns().stream()
.filter(pattern -> pathMatcher.match(pattern, host)).findFirst();
if (optionalPattern.isPresent()) {
//将URI中的模板变量添加到exchange中,供其他GatewayFilter使用
Map<String, String> variables = pathMatcher
.extractUriTemplateVariables(optionalPattern.get(), host);
ServerWebExchangeUtils.putUriTemplateVariables(exchange, variables);
return true;
}
return false;
}
注意:
-
通过源码发现,要注意的是获取Header中的第一个Host,如果通过代理添加了多个Host,其他的会被忽略。
-
如果配置:Host=dev.wx.com, {env}.bd.com,而其他地方引用了{env}这个模板变量,当通过dev.wx.com访问时,就会出现500错误,无法获取{env}变量。
9、MethodRoutePredicateFactory
根据请求Method路由,配置关键词:Method,示例如下
spring:
cloud:
gateway:
routes:
- id: method-post
uri: forward:/hello
filters:
- AddRequestParameter=var, post
predicates:
- Method=post
- id: method-get
uri: forward:/hello
filters:
- AddRequestParameter=var, get
predicates:
- Method=get, post
根据请求的Method进行匹配,如果能匹配到,则将请求路由到指定URI上;支持指定多个Method;配置的method不区分大小写;如果有多个匹配的Method,则路由到匹配到的第一个URI。
源码:
public boolean test(ServerWebExchange exchange) {
HttpMethod requestMethod = exchange.getRequest().getMethod();
return stream(config.getMethods())
.anyMatch(httpMethod -> httpMethod == requestMethod);
}
10、RemoteAddrRoutePredicateFactory
根据请求的远程IP路由,配置关键词:RemoteAddr,示例如下
spring:
cloud:
gateway:
routes:
- id: remote-addr1
uri: forward:/hello
filters:
- AddRequestParameter=var, addr1
predicates:
- RemoteAddr=192.168.0.100/24
- id: method-addr2
uri: forward:/hello
filters:
- AddRequestParameter=var, addr2
predicates:
- RemoteAddr=192.168.0.100
- id: method-addr3
uri: forward:/hello
filters:
- AddRequestParameter=var, addr2
predicates:
- RemoteAddr=192.168.0.100,192.168.0.101,192.168.0.102
根据请求的远程IP进行匹配,如果能匹配到,则将请求路由到指定URI上;配置规则如下:
-
指定一个固定的IP
-
指定多个IP,用逗号隔开
-
如果配置的IP没有指定子网掩码({ip}/{子网掩码}),则默认32(255.255.255.255)
源码:
public boolean test(ServerWebExchange exchange) {
InetSocketAddress remoteAddress = config.remoteAddressResolver
.resolve(exchange);
if (remoteAddress != null && remoteAddress.getAddress() != null) {
String hostAddress = remoteAddress.getAddress().getHostAddress();
String host = exchange.getRequest().getURI().getHost();
for (IpSubnetFilterRule source : sources) {
if (source.matches(remoteAddress)) {
return true;
}
}
}
return false;
}
11、WeightRoutePredicateFactory
根据权重路由,配置关键词:Weight,示例如下:
spring:
cloud:
gateway:
routes:
- id: weight-high
uri: forward:/hello
filters:
- AddRequestParameter=var, high
predicates:
- Weight=group1, 15
- Path=/weight
- id: method-low
uri: forward:/hello
filters:
- AddRequestParameter=var, low
predicates:
- Weight=group1, 5
- Path=/weight
根据配置的权重来路由,配置权重时需要指定分组,即Weight属性的第一个参数,为分组名,不同group单独计算权重,所以可以配置多个组。权重值为第二个参数,同一组里的权重值的总和 没有要求,最终计算会转换成小于1的double类型。
源码:
-
WeightCalculatorWebFilter
//项目启动时执行,解析配置的权重,并将整型的权重转换成小于1的double权重,按照从小到大的顺序放入到List中。
//将最终解析的结果存储在变量groupWeights中
void addWeightConfig(WeightConfig weightConfig) {
String group = weightConfig.getGroup();
GroupWeightConfig config;
if (groupWeights.containsKey(group)) {
config = new GroupWeightConfig(groupWeights.get(group));
}else {
config = new GroupWeightConfig(group);
}
config.weights.put(weightConfig.getRouteId(), weightConfig.getWeight());
//将权重的int类型转换成double类型
int weightsSum = 0;
for (Integer weight : config.weights.values()) {
weightsSum += weight;
}
final AtomicInteger index = new AtomicInteger(0);
for (Map.Entry<String, Integer> entry : config.weights.entrySet()) {
String routeId = entry.getKey();
Integer weight = entry.getValue();
Double nomalizedWeight = weight / (double) weightsSum;
config.normalizedWeights.put(routeId, nomalizedWeight);
config.rangeIndexes.put(index.getAndIncrement(), routeId);
}
config.ranges.clear()
config.ranges.add(0.0);
List<Double> values = new ArrayList<>(config.normalizedWeights.values());
for (int i = 0; i < values.size(); i++) {
Double currentWeight = values.get(i);
Double previousRange = config.ranges.get(i);
Double range = previousRange + currentWeight;
config.ranges.add(range);
}
// only update after all calculations
groupWeights.put(group, config);
}
//当接收到请求时执行,从变量groupWeights中读取配置,根据生成的随机值,选择routeId,
//再放入到ServerWebExchange的Attributes中,key为:{pre}.routeWeight
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
Map<String, String> weights = getWeights(exchange);
for (String group : groupWeights.keySet()) {
GroupWeightConfig config = groupWeights.get(group);
if (config == null) {
continue; // nothing we can do, but this is odd
}
//生成随机变量
double r = this.random.nextDouble();
List<Double> ranges = config.ranges;
//根据随机值选择RouteId
for (int i = 0; i < ranges.size() - 1; i++) {
if (r >= ranges.get(i) && r < ranges.get(i + 1)) {
String routeId = config.rangeIndexes.get(i);
weights.put(group, routeId);
break;
}
}
}
return chain.filter(exchange);
}
-
WeightRoutePredicateFactory
//从ServerWebExchange中读取{pre}.routeWeight属性,该属性读取到的就是根据权重计算的routeId
public boolean test(ServerWebExchange exchange) {
Map<String, String> weights = exchange.getAttributeOrDefault(WEIGHT_ATTR,
Collections.emptyMap());
//该值是在RoutePredicateHandlerMapping的lookupRoute方法中,遍历所有配置的路由
//每次遍历,将值放入到GATEWAY_PREDICATE_ROUTE_ATTR属性中,有所有的RoutePredicateFactory来执行
String routeId = exchange.getAttribute(GATEWAY_PREDICATE_ROUTE_ATTR);
String group = config.getGroup();
if (weights.containsKey(group)) {
String chosenRoute = weights.get(group);
return routeId.equals(chosenRoute);
}
return false;
}
12、ReadBodyPredicateFactory
-
缓存请求体,这样可以在后续多次读取请求体
-
该Predicate在官方文档上没有说明。
-
参考网上资料:
https://www.cnblogs.com/hyf-huangyongfei/p/12849406.html
最后:
-
不同的Predicate可以组合配置,但是需要同时满足才匹配成功。
-
如果一个请求能满足多个配置,则请求会被转发到第一个匹配的URI。
###更多文章请关注公众号,不定期更新笔记###
更多推荐
所有评论(0)