Gateway(三)Predicate

2020年4月18日 | 作者 Siran | 2400字 | 阅读大约需要5分钟
归档于 gateway | 标签 #gateway

1.概述

Gateway 创建 Route 对象时,使用 RoutePredicateFactory 创建 Predicate 对象。Predicate 对象可以赋值给 Route.predicate 属性,用于匹配请求对应的 Route 。

Gateway(二)Route一文中构建Route的时候,通过List<RoutePredicateFactory> 来给 Route.predicate 属性赋值。

private void initFactories(List<RoutePredicateFactory> predicates) {
		predicates.forEach(factory -> {
			String key = factory.name();
			if (this.predicates.containsKey(key)) {
				this.logger.warn("A RoutePredicateFactory named " + key
						+ " already exists, class: " + this.predicates.get(key)
						+ ". It will be overwritten.");
			}
			this.predicates.put(key, factory);
			if (logger.isInfoEnabled()) {
				logger.info("Loaded RoutePredicateFactory [" + key + "]");
			}
		});
	}

2.RoutePredicateFactory

RoutePredicateFactory 是路由谓语工厂接口,代码如下:

@FunctionalInterface
public interface RoutePredicateFactory extends ArgumentHints {
  String PATTERN_KEY = "pattern";

	Predicate<ServerWebExchange> apply(Tuple args);

	default String name() {
		return NameUtils.normalizePredicateName(getClass());
	}

}
  • #name() 默认方法,调用 NameUtils#normalizePredicateName(Class) 方法,获得 RoutePredicateFactory 的名字。该方法截取类名前半段,例如 QueryRoutePredicateFactory 的结果为 Query
  • #apply() 接口方法,创建 Predicate 。

RoutePredicateFactory 的实现类:


2.1 AfterRoutePredicateFactory

  • Route 匹配 :请求时间满足在配置时间之后

  • 配置:

    spring:
      cloud:
        gateway:
          routes:
          - id: after_route
            uri: http://example.org
            predicates:
            - After=2017-01-20T17:42:47.789-07:00[America/Denver]
    
  • 代码:

    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 String toString() {
                  return String.format("After: %s", config.getDatetime());
              }
          };
      }
    

2.2 BeforeRoutePredicateFactory

  • Route 匹配 :请求时间满足在配置时间之前

  • 配置:

    spring:
      cloud:
        gateway:
          routes:
          - id: before_route
            uri: http://example.org
            predicates:
            - Before=2017-01-20T17:42:47.789-07:00[America/Denver]
      
    
  • 代码

    public Predicate<ServerWebExchange> apply(Config config) {
          return new GatewayPredicate() {
              @Override
              public boolean test(ServerWebExchange serverWebExchange) {
                  final ZonedDateTime now = ZonedDateTime.now();
                  return now.isBefore(config.getDatetime());
              }
      
              @Override
              public String toString() {
                  return String.format("Before: %s", config.getDatetime());
              }
          };
      }
    

2.3 BetweenRoutePredicateFactory

  • Route 匹配 :请求时间满足在配置时间之间

  • 配置:

    spring:
      cloud:
        gateway:
          routes:
          - id: between_route
            uri: http://example.org
            predicates:
            - Betweeen=2017-01-20T17:42:47.789-07:00[America/Denver], 2017-01-21T17:42:47.789-07:00[America/Denver]
    
  • 代码

    public Predicate<ServerWebExchange> apply(Config config) {
          Assert.isTrue(config.getDatetime1().isBefore(config.getDatetime2()),
                  config.getDatetime1() + " must be before " + config.getDatetime2());
      
          return new GatewayPredicate() {
              @Override
              public boolean test(ServerWebExchange serverWebExchange) {
                  final ZonedDateTime now = ZonedDateTime.now();
                  return now.isAfter(config.getDatetime1())
                          && now.isBefore(config.getDatetime2());
              }
      
              @Override
              public String toString() {
                  return String.format("Between: %s and %s", config.getDatetime1(),
                          config.getDatetime2());
              }
          };
      }
    

2.4 CookieRoutePredicateFactory

  • Route 匹配 :请求指定 Cookie 正则匹配指定值

  • 配置:

    spring:
      cloud:
        gateway:
          routes:
          - id: cookie_route
            uri: http://example.org
            predicates:
            - Cookie=chocolate, ch.p
      
    
  • 代码

    public Predicate<ServerWebExchange> apply(Config config) {
          return new GatewayPredicate() {
              @Override
              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;
              }
      
              @Override
              public String toString() {
                  return String.format("Cookie: name=%s regexp=%s", config.name,
                          config.regexp);
              }
          };
      }
    

2.5 HeaderRoutePredicateFactory

  • Route 匹配 :请求头满足匹配

  • 配置:

    spring:
      cloud:
        gateway:
          routes:
          - id: header_route
            uri: http://example.org
            predicates:
            - Header=X-Request-Id, \d+
    
  • 代码

    public Predicate<ServerWebExchange> apply(Config config) {
          boolean hasRegex = !StringUtils.isEmpty(config.regexp);
      
          return new GatewayPredicate() {
              @Override
              public boolean test(ServerWebExchange exchange) {
                  List<String> values = exchange.getRequest().getHeaders()
                          .getOrDefault(config.header, Collections.emptyList());
                  if (values.isEmpty()) {
                      return false;
                  }
                  // values is now guaranteed to not be empty
                  if (hasRegex) {
                      // check if a header value matches
                      return values.stream()
                              .anyMatch(value -> value.matches(config.regexp));
                  }
      
                  // there is a value and since regexp is empty, we only check existence.
                  return true;
              }
      
              @Override
              public String toString() {
                  return String.format("Header: %s regexp=%s", config.header,
                          config.regexp);
              }
          };
      }
    

2.6 HostRoutePredicateFactory

  • Route 匹配 :请求 Host 匹配指定值。

  • 配置:

    spring:
      cloud:
        gateway:
          routes:
          - id: host_route
            uri: http://example.org
            predicates:
            - Host=**.somehost.org
    
  • 代码

    public Predicate<ServerWebExchange> apply(Config config) {
          return new GatewayPredicate() {
              @Override
              public boolean test(ServerWebExchange exchange) {
                  String host = exchange.getRequest().getHeaders().getFirst("Host");
                  Optional<String> optionalPattern = config.getPatterns().stream()
                          .filter(pattern -> pathMatcher.match(pattern, host)).findFirst();
      
                  if (optionalPattern.isPresent()) {
                      Map<String, String> variables = pathMatcher
                              .extractUriTemplateVariables(optionalPattern.get(), host);
                      ServerWebExchangeUtils.putUriTemplateVariables(exchange, variables);
                      return true;
                  }
      
                  return false;
              }
      
              @Override
              public String toString() {
                  return String.format("Hosts: %s", config.getPatterns());
              }
          };
      }
    

2.7 MethodRoutePredicateFactory

  • Route 匹配 :请求 Method 匹配指定值

  • 配置:

    spring:
      cloud:
        gateway:
          routes:
          - id: method_route
            uri: http://example.org
            predicates:
            - Method=GET
    
  • 代码

    public Predicate<ServerWebExchange> apply(Config config) {
          return new GatewayPredicate() {
              @Override
              public boolean test(ServerWebExchange exchange) {
                  HttpMethod requestMethod = exchange.getRequest().getMethod();
                  return stream(config.getMethods())
                          .anyMatch(httpMethod -> httpMethod == requestMethod);
              }
      
              @Override
              public String toString() {
                  return String.format("Methods: %s", Arrays.toString(config.getMethods()));
              }
          };
      }
    

2.8 PathRoutePredicateFactory

  • Route 匹配 :请求 Path 匹配指定值。

  • 配置:

    spring:
      cloud:
        gateway:
          routes:
          - id: host_route
            uri: http://example.org
            predicates:
            - Path=/foo/{segment}
    
  • 代码

    public Predicate<ServerWebExchange> apply(Config config) {
          final ArrayList<PathPattern> pathPatterns = new ArrayList<>();
          synchronized (this.pathPatternParser) {
              pathPatternParser.setMatchOptionalTrailingSeparator(
                      config.isMatchOptionalTrailingSeparator());
              config.getPatterns().forEach(pattern -> {
                  PathPattern pathPattern = this.pathPatternParser.parse(pattern);
                  pathPatterns.add(pathPattern);
              });
          }
          return new GatewayPredicate() {
              @Override
              public boolean test(ServerWebExchange exchange) {
                  PathContainer path = parsePath(
                          exchange.getRequest().getURI().getRawPath());
      
                  Optional<PathPattern> optionalPathPattern = pathPatterns.stream()
                          .filter(pattern -> pattern.matches(path)).findFirst();
      
                  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;
                  }
              }
      
              @Override
              public String toString() {
                  return String.format("Paths: %s, match trailing slash: %b",
                          config.getPatterns(), config.isMatchOptionalTrailingSeparator());
              }
          };
      }
    

2.9 QueryRoutePredicateFactory

  • Route 匹配 :请求 QueryParam 匹配指定值。

  • 配置:

    spring:
      cloud:
        gateway:
          routes:
          - id: query_route
            uri: http://example.org
            predicates:
            - Query=baz
            - Query=foo, ba.
    
  • 代码

    public Predicate<ServerWebExchange> apply(Config config) {
          return new GatewayPredicate() {
              @Override
              public boolean test(ServerWebExchange exchange) {
                  if (!StringUtils.hasText(config.regexp)) {
                      // check existence of header
                      return exchange.getRequest().getQueryParams()
                              .containsKey(config.param);
                  }
      
                  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;
              }
      
              @Override
              public String toString() {
                  return String.format("Query: param=%s regexp=%s", config.getParam(),
                          config.getRegexp());
              }
          };
      }
    

2.10 RemoteAddrRoutePredicateFactory

  • Route 匹配 :请求来源 IP 在指定范围内。

  • 配置:

    spring:
      cloud:
        gateway:
          routes:
          - id: remoteaddr_route
            uri: http://example.org
            predicates:
            - RemoteAddr=192.168.1.1/24
    
  • 代码

    public Predicate<ServerWebExchange> apply(Config config) {
          List<IpSubnetFilterRule> sources = convert(config.sources);
      
          return new GatewayPredicate() {
              @Override
              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();
      
                      if (log.isDebugEnabled() && !hostAddress.equals(host)) {
                          log.debug("Remote addresses didn't match " + hostAddress + " != "
                                  + host);
                      }
      
                      for (IpSubnetFilterRule source : sources) {
                          if (source.matches(remoteAddress)) {
                              return true;
                          }
                      }
                  }
      
                  return false;
              }
      
              @Override
              public String toString() {
                  return String.format("RemoteAddrs: %s", config.getSources());
              }
          };
      }
    

3.RoutePredicateHandlerMapping

在上面介绍了所有的Predicate 匹配规则,那么在 Gateway(一)初始化 文章中的工作机制部分讲过,客户端发送请求过来,通过 HandlerMapping 进行predicate 的匹配,匹配成功在进行下面的处理。

  • 1. org.springframework.web.reactive.DispatcherHandler :接收到请求,匹配 HandlerMapping ,此处会匹配到 RoutePredicateHandlerMapping 。 由于Gateway是构建在 reactive 上的,所以这边的web类型就是reactive。

    public class DispatcherHandler implements WebHandler, ApplicationContextAware {	
      @Override
      public Mono<Void> handle(ServerWebExchange exchange) {
          if (this.handlerMappings == null) {
              return createNotFoundError();
          }
        //① 顺序使用 handlerMappings 获得对应的 WebHandler 
          return Flux.fromIterable(this.handlerMappings)
            //② 获得 Handler 
                  .concatMap(mapping -> mapping.getHandler(exchange))
                  .next()
            //③ 如果匹配不到 WebHandler ,返回 HANDLER_NOT_FOUND_EXCEPTION 。
                  .switchIfEmpty(createNotFoundError())
            //④ 调用 invokeHandler() 方法,执行 Handler 。
                  .flatMap(handler -> invokeHandler(exchange, handler))
            //⑤ 调用 #handleResult() 方法,处理结果
                  .flatMap(result -> handleResult(exchange, result));
      }
    }  
    
    • 顺序使用 handlerMappings 获得对应的 WebHandler

      可以看到有 RoutePredicateHandlerMapping

    • 获得 Handler

    • 如果匹配不到 WebHandler ,返回 HANDLER_NOT_FOUND_EXCEPTION

    • 调用 invokeHandler() 方法,执行 Handler 。

      private Mono<HandlerResult> invokeHandler(ServerWebExchange exchange, Object handler) {
              if (this.handlerAdapters != null) {
            //4.1 获取Adapters, WebHandler 的处理器适配器。
                  for (HandlerAdapter handlerAdapter : this.handlerAdapters) {
              //4.2 调用support方法 ,是否支持 WebHandler
                      if (handlerAdapter.supports(handler)) {
                //4.3 调用handle 方法,执行处理器
                          return handlerAdapter.handle(exchange, handler);
                      }
                  }
              }
              return Mono.error(new IllegalStateException("No HandlerAdapter: " + handler));
          }
      
      • 4.1 this.handlerAdapters :获取Adapters, WebHandler 的处理器适配器。

      • 4.2 supports :调用support方法 ,是否支持 WebHandler

        //org.springframework.web.reactive.result.SimpleHandlerAdapter
        @Override
        public boolean supports(Object handler) {
          return WebHandler.class.isAssignableFrom(handler.getClass());
        }
        
      • 4.3 handle : 调用handle 方法,执行处理器

        //org.springframework.web.reactive.result.SimpleHandlerAdapter
        public Mono<HandlerResult> handle(ServerWebExchange exchange, Object handler) {
              WebHandler webHandler = (WebHandler) handler;
            //4.3.1 执行处理器。例如,WebHandler 为 FilteringWebHandler 时,获得 Route 的 GatewayFilter 数组,创建 GatewayFilterChain 处理请求。
              Mono<Void> mono = webHandler.handle(exchange);
            //4.3.2 在 WebHandler 执行完后 ( #then(Mongo) ),然后返回 Mono.empty() 。
              return mono.then(Mono.empty());
          }
        
        • 4.3.1 执行处理器。例如,WebHandler 为 FilteringWebHandler 时,获得 Route 的 GatewayFilter 数组,创建 GatewayFilterChain 处理请求。
        • 4.3.2 在 WebHandler 执行完后 ( #then(Mongo) ),然后返回 Mono.empty() 。
    • 调用 #handleResult() 方法,处理结果。

      SimpleHandlerAdapter 返回的是 Mono.empty() ,所以不会触发该方法。

      private Mono<Void> handleResult(ServerWebExchange exchange, HandlerResult result) {
              return getResultHandler(result).handleResult(exchange, result)
                      .onErrorResume(ex -> result.applyExceptionHandler(ex).flatMap(exceptionResult ->
                              getResultHandler(exceptionResult).handleResult(exchange, exceptionResult)));
          }
      
  • 2. org.springframework.cloud.gateway.handler.RoutePredicateHandlerMapping :接收到请求,匹配 Route ,并返回处理 Route 的 FilteringWebHandler 。

    public class RoutePredicateHandlerMapping extends AbstractHandlerMapping {
      
      private final FilteringWebHandler webHandler;
      private final RouteLocator routeLocator;
      
      public RoutePredicateHandlerMapping(FilteringWebHandler webHandler, RouteLocator routeLocator) {
          this.webHandler = webHandler;
          this.routeLocator = routeLocator;
      
          setOrder(1); // RequestMappingHandlerMapping 之后
      }
        
      @Override
      protected Mono<?> getHandlerInternal(ServerWebExchange exchange) {
        // 设置 GATEWAY_HANDLER_MAPPER_ATTR 为 RoutePredicateHandlerMapping
          exchange.getAttributes().put(GATEWAY_HANDLER_MAPPER_ATTR, getSimpleName());
      
          return lookupRoute(exchange) //2.1 匹配路由
                  // .log("route-predicate-handler-mapping", Level.FINER) //name this
                  .flatMap((Function<Route, Mono<?>>) r -> {
                      exchange.getAttributes().remove(GATEWAY_PREDICATE_ROUTE_ATTR);
                      if (logger.isDebugEnabled()) {
                          logger.debug(
                                  "Mapping [" + getExchangeDesc(exchange) + "] to " + r);
                      }
      
              // 设置 GATEWAY_ROUTE_ATTR 为 匹配的 Route
                      exchange.getAttributes().put(GATEWAY_ROUTE_ATTR, r);
                      return Mono.just(webHandler);
                  }).switchIfEmpty(Mono.empty().then(Mono.fromRunnable(() -> {//匹配不到返回
                      exchange.getAttributes().remove(GATEWAY_PREDICATE_ROUTE_ATTR);
                      if (logger.isTraceEnabled()) {
                          logger.trace("No RouteDefinition found for ["
                                  + getExchangeDesc(exchange) + "]");
                      }
                  })));
      }
    }
    
    • 2.1 lookupRoute 匹配路由

      protected Mono<Route> lookupRoute(ServerWebExchange exchange) {
          //2.1.1 获取所有路由
              return this.routeLocator.getRoutes()
                      .concatMap(route -> Mono.just(route).filterWhen(r -> {
                          // add the current route we are testing
                          exchange.getAttributes().put(GATEWAY_PREDICATE_ROUTE_ATTR, r.getId());
                //2.1.2并调用 Predicate#apply(ServerWebExchange) 方法,顺序匹配一个 Route。
                          return r.getPredicate().apply(exchange);
                      })
                              // instead of immediately stopping main flux due to error, log and
                              // swallow it
                  //2.1.3 未来会增加匹配过程中发生异常的处理。目前,任何一个 Predicate#test(ServerWebExchange) 的方法调用发生异常时,都会导致匹配不到 Route 。一定要注意。      
                              .doOnError(e -> logger.error(
                                      "Error applying predicate for route: " + route.getId(),
                                      e))
                              .onErrorResume(e -> Mono.empty()))
                      // .defaultIfEmpty() put a static Route not found
                      // or .switchIfEmpty()
                      // .switchIfEmpty(Mono.<Route>empty().log("noroute"))
                      .next()
                      // TODO: error handling
                      .map(route -> {
                          if (logger.isDebugEnabled()) {
                              logger.debug("Route matched: " + route.getId());
                          }
                          validateRoute(route, exchange);
                          return route;
                      });
          }
      
      • 2.1.1 getRoutes() : 获取所有Route,在Gateway(二)Route一文中已经分析过了。
      • 2.1.2 Predicate#apply(ServerWebExchange) 方法,顺序匹配一个 Route。
      • 2.1.3:错误处理,如果在匹配Route 中发现错误,会导致一个Route都匹配不到
  • 3. org.springframework.cloud.gateway.handler.FilteringWebHandler :获得 Route 的 GatewayFilter 数组,创建 GatewayFilterChain 处理请求。在Gateway(四)Filter文中分析


参考自:Spring-Cloud-Gateway 源码解析 —— 处理器 (3.1) 之 RoutePredicateFactory 路由谓语工厂