本文共 46486 字,大约阅读时间需要 154 分钟。
转载自:https://blog.csdn.net/weixin_44100910/article/details/106439122
本篇介绍SpringCloud的网关模块的RoutePredicateFactory,通过提供的RoutePredicateFactory来实现配置不同的路由规则。目前支持12种RoutePredicateFactory,简介如下:
QueryRoutePredicateFactory:根据query参数路由
PathRoutePredicateFactory:根据Path路由
AfterRoutePredicateFactory:根据请求的时间路由,在指定时间之后
BeforeRoutePredicateFactory:根据请求的时间路由,在指定时间之前
BetweenRoutePredicateFactory:根据请求的时间路由,在指定时间范围内
CookieRoutePredicateFactory:根据请求头中的Cookie路由
HeaderRoutePredicateFactory:根据请求头中的值路由
HostRoutePredicateFactory:根据请求头中的Host路由
MethodRoutePredicateFactory:根据请求Method路由
RemoteAddrRoutePredicateFactory:根据请求的远程IP路由
WeightRoutePredicateFactory:根据权重路由
ReadBodyPredicateFactory :暂未官方文档说明
根据查询的参数路由,配置关键词: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}是否匹配 Listvalues = 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; }
根据请求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 OptionaloptionalPathPattern = 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; } }
根据请求时间路由,配置关键词: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()); }
根据请求时间路由,配置关键词: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()); }
根据请求时间路由,配置关键词: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()); }
根据请求头中的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) { Listcookies = 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; }
根据请求头中的值路由,配置关键词: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) { Listvalues = 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 OptionaloptionalPattern = config.getPatterns().stream() .filter(pattern -> pathMatcher.match(pattern, host)).findFirst(); if (optionalPattern.isPresent()) { //将URI中的模板变量添加到exchange中,供其他GatewayFilter使用 Map 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}变量。
根据请求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); }
根据请求的远程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; }
根据权重路由,配置关键词: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.Entryentry : 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 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 filter(ServerWebExchange exchange, WebFilterChain chain) { Map 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 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) { Mapweights = 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; }
缓存请求体,这样可以在后续多次读取请求体
该Predicate在官方文档上没有说明。
参考网上资料:
https://www.cnblogs.com/hyf-huangyongfei/p/12849406.html
不同的Predicate可以组合配置,但是需要同时满足才匹配成功。
如果一个请求能满足多个配置,则请求会被转发到第一个匹配的URI。