首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

SpringCloud Gateway 路由断言

SpringCloud Gateway 系列文章共五篇,由我行开发工程师 @Aaron 提供,带大家深入剖析 Gateway 工作原理,及如何基于 Gateway 进行定制化开发以适应企业特定环境需求。

第一篇:SpringCloud Gateway 动态路由

第二篇:SpringCloud Gateway 路由数量对性能的影响研究

第三篇:SpringCloud Gateway 路由转发性能优化

第四篇:SpringCloud Gateway 断言

第五篇:SpringCloud Gateway 过滤器。

断言即判断一个命题的真伪,路由断言则用于判断该路由是否可应用于当前请求。

路由定义

见上图,Route 各属性分别为:

  • id,路由的唯一编号可用于定义一个路由,gateway 中用,一些的过滤器会根据 id 是否属于某个集合做特定的操作;
  • order,路由的优先级,值越小,优先级越高(路由表按此排序);
  • metadata,可以定义连接超时、响应超时等参数,将来可能会拓展用途;
  • uri,上游地址,即 upstream,可以是一个确定的服务地址,也可以是一个 lb 开头的地址,表示可通过服务发现,发现上游节点;
  • filters,过滤器列表,请求根据断言命中一个路由后,会依次执行过滤列表的过滤器(《SpringCloud Gateway 过滤器》将详细介绍);
  • predicate,路由断言,本文重点,用于判断路由是否匹配当前路由。

接下来我们看 AsyncPredicate 是如何定义的:

代码语言:javascript
复制
public interface AsyncPredicate<T> extends Function<T, Publisher<Boolean>> {//...}

复制代码

我们对比下 jdk 中的内置 FunctionalInterface:

代码语言:javascript
复制
public interface Predicate<T> {//...}
public interface Function<T, R> {//...}

复制代码

如果你能认识到Predicate<T>其实是一个特殊的Function<T, R>,它的返回类型R规定为Boolean,你就能轻松的理解 AsyncPredicate<T>:它是一个响应式编程模型下的Predicate<T>,返回值为Mono<Boolean>

工作原理

在前文动态路由章节,我们已经在路由查找过程中,看到过断言时如何工作的,见RoutePredicateHandlerMapping 源码 126-134 行

代码语言:javascript
复制
protected Mono<Route> lookupRoute(ServerWebExchange exchange) {  // routeLocator.getRoutes() 获取的路由已经排序  return this.routeLocator.getRoutes()      // concatMap 与 flatmap的区别就是能保持执行顺序不变      .concatMap(route ->              // filterWhen 发生订阅时执行,就相当于按序遍历符合条件的记录              Mono.just(route).filterWhen(r -> {                exchange.getAttributes().put(GATEWAY_PREDICATE_ROUTE_ATTR, r.getId());                // 从route中获取断言,并判断是否为真,并返回                return r.getPredicate().apply(exchange);              })  //...}

复制代码

filterWhen 接收的是一个Function<T, Publisher<Boolean>> 的函数式接口,也就是上面我们介绍的 AsyncPredicate<T>

前文动态路由章节,我们介绍过Route是有RouteDefinition转换而来,执行转换的类为

代码语言:javascript
复制
private Route convertToRoute(RouteDefinition routeDefinition) {  // combinePredicates 根据路由定义中的断言定义组合为路由断言  AsyncPredicate<ServerWebExchange> predicate = combinePredicates(routeDefinition);  List<GatewayFilter> gatewayFilters = getFilters(routeDefinition);
  return Route.async(routeDefinition).asyncPredicate(predicate)      .replaceFilters(gatewayFilters).build();}

复制代码

源码 241-286 行

代码语言:javascript
复制
// 该方法使用 and 关系 组合了所有的断言定义private AsyncPredicate<ServerWebExchange> combinePredicates(     RouteDefinition routeDefinition) {   List<PredicateDefinition> predicates = routeDefinition.getPredicates();   if (predicates == null || predicates.isEmpty()) {     // this is a very rare case, but possible, just match all     return AsyncPredicate.from(exchange -> true);   }   AsyncPredicate<ServerWebExchange> predicate = lookup(routeDefinition,       predicates.get(0));
   for (PredicateDefinition andPredicate : predicates.subList(1,       predicates.size())) {     AsyncPredicate<ServerWebExchange> found = lookup(routeDefinition,         andPredicate);     // and 表明多个断言的关系,也就是必须所有条件都符合才能命中一个路由     predicate = predicate.and(found);   }
   return predicate; }
 // 根据路由定义中的断言定义,从指定 断言工厂 获取一个实例化 断言 private AsyncPredicate<ServerWebExchange> lookup(RouteDefinition route,     PredicateDefinition predicate) {   RoutePredicateFactory<Object> factory = this.predicates.get(predicate.getName());   // 省略非关键代码   return factory.applyAsync(config); }

复制代码

断言工厂

上文我们已经解析清楚,一个请求是否命中某个路由,最终是由路由定义中的断言定义对应的断言工厂的applyAsync 方法决定。

见上图,RoutePredicateFactory 是一个FunctionalInterface 其唯一抽象方法为:

代码语言:javascript
复制
Predicate<ServerWebExchange> apply(C config);

复制代码

具体的RoutePredicateFactory的实现工厂类,需实现上述方法。

Gateway 内置的路由工厂有:

以上每个工厂的apply都实现了如何根据配置及ServerWebExchange来判断当前请求是否匹配断言(如果需要自定义断言工厂,也需要实现此逻辑)。

每个路由工厂如何配置,官方文档均有示例。

我们看如下示例:

代码语言:javascript
复制
spring:  cloud:    gateway:      routes:      - id: path_route        uri: https://example.org        predicates:        - Path=/red/**,/blue/**

复制代码

关键字 Path 表示采用 PathRoutePredicateFactory 工厂创建该断言;

Path 后是逗号分隔的有序字符串数据,表示只要匹配任意一个 path,该断言即为真(见apply方法),路由命中。

下面方法可用于创建动态路由时,生成断言定义:

代码语言:javascript
复制
  /**   * 生成断言定义   *   * @param type 断言名称   * @param orderedArgs 有序的参数数组   * @return 路由断言定义   */  public static PredicateDefinition createPredicate(@NotNull PredicateType type,      @NotNull String... orderedArgs) {    PredicateDefinition definition = new PredicateDefinition();    definition.setName(type.name());    int order = 0;    for (String arg : orderedArgs) {      if (StringUtils.isNotBlank(arg)) {        definition.addArg(NameUtils.generateName(order++), arg);      }    }    return definition;  }

复制代码

断言定义的 Name 即为断言工厂的前缀字符串。

总结

本文较为详细的分析了 Gateway 是如何判断一个请求应采用哪个路由进行处理的原理,并进一步分析了断言工厂的实现,以及配置文件及断言定义如何与断言工厂相对应。

希望通过本文,你能帮助你加深对 Gateway 的理解。

  • 发表于:
  • 本文为 InfoQ 中文站特供稿件
  • 首发地址https://www.infoq.cn/article/acd9463cfbc6f0858396b6740
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券
http://www.vxiaotou.com