⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 http://www.iocoder.cn/Spring-Cloud-Gateway/handler-route-predicate-factory/ 「芋道源码」欢迎转载,保留摘要,谢谢!

本文主要基于 Spring-Cloud-Gateway 2.0.X M4


🙂🙂🙂关注**微信公众号:【芋道源码】**有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言将得到认真回复。甚至不知道如何读源码也可以请教噢
  4. 新的源码解析文章实时收到通知。每周更新一篇左右
  5. 认真的源码交流微信群。

阅读源码最好的方式,是使用 IDEA 进行调试 Spring Cloud Gateway 源码,不然会一脸懵逼。

胖友可以点击「芋道源码」扫码关注,回复 git019 关键字
获得艿艿添加了中文注释的 Spring Cloud Gateway 源码地址。

阅读源码很孤单,加入源码交流群,一起坚持!

1. 概述

本文主要分享 RoutePredicateFactory 路由谓语工厂

RoutePredicateFactory 涉及到的类在 org.springframework.cloud.gateway.handler.predicate 包下,如下图 :

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


推荐 Spring Cloud 书籍

2. RoutePredicateFactory

org.springframework.cloud.gateway.handler.predicate.RoutePredicateFactory , 路由谓语工厂接口。代码如下 :

@FunctionalInterface
public interface RoutePredicateFactory extends ArgumentHints {

String PATTERN_KEY = "pattern";

Predicate<ServerWebExchange> apply(Tuple args);

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

}


RoutePredicateFactory 实现类如下图 :

下面我们一个一个 RoutePredicateFactory 实现类理解。代码比较多,实际也比较简单。

另外,org.springframework.cloud.gateway.handler.predicate.RoutePredicates ,RoutePredicates 工厂,其调用 RoutePredicateFactory 接口的实现类,创建各种 Predicate 。

3. 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]

  • RoutePredicates 方法 :#after(ZonedDateTime)

  • 代码 :

     1: public class AfterRoutePredicateFactory implements RoutePredicateFactory {
    2:
    3: public static final String DATETIME_KEY = "datetime";
    4:
    5: @Override
    6: public List<String> argNames() {
    7: return Collections.singletonList(DATETIME_KEY);
    8: }
    9:
    10: @Override
    11: public Predicate<ServerWebExchange> apply(Tuple args) {
    12: Object value = args.getValue(DATETIME_KEY);
    13: final ZonedDateTime dateTime = BetweenRoutePredicateFactory.getZonedDateTime(value);
    14:
    15: return exchange -> {
    16: final ZonedDateTime now = ZonedDateTime.now();
    17: return now.isAfter(dateTime);
    18: };
    19: }
    20:
    21: }

    • Tulpe 参数 :datetime
    • 第 13 行 :调用 BetweenRoutePredicateFactory#getZonedDateTime(value) 方法,解析配置的时间值,在 「5. BetweenRoutePredicateFactory」 详细解析。

4. BeforeRoutePredicateFactory

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

  • RoutePredicates 方法 :#before(ZonedDateTime)

  • 配置 :

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

  • 代码 :

     1: public class BeforeRoutePredicateFactory implements RoutePredicateFactory {
    2:
    3: public static final String DATETIME_KEY = "datetime";
    4:
    5: @Override
    6: public List<String> argNames() {
    7: return Collections.singletonList(DATETIME_KEY);
    8: }
    9:
    10: @Override
    11: public Predicate<ServerWebExchange> apply(Tuple args) {
    12: Object value = args.getValue(DATETIME_KEY);
    13: final ZonedDateTime dateTime = BetweenRoutePredicateFactory.getZonedDateTime(value);
    14:
    15: return exchange -> {
    16: final ZonedDateTime now = ZonedDateTime.now();
    17: return now.isBefore(dateTime);
    18: };
    19: }
    20:
    21: }

    • Tulpe 参数 :datetime
    • 第 13 行 :调用 BetweenRoutePredicateFactory#getZonedDateTime(value) 方法,解析配置的时间值,在 「5. BetweenRoutePredicateFactory」 详细解析。

5. BetweenRoutePredicateFactory

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

  • RoutePredicates 方法 :#between(ZonedDateTime, ZonedDateTime)

  • 配置 :

    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]

  • 代码 :

     1: public class BetweenRoutePredicateFactory implements RoutePredicateFactory {
    2:
    3: public static final String DATETIME1_KEY = "datetime1";
    4: public static final String DATETIME2_KEY = "datetime2";
    5:
    6: @Override
    7: public Predicate<ServerWebExchange> apply(Tuple args) {
    8: //TODO: is ZonedDateTime the right thing to use?
    9: final ZonedDateTime dateTime1 = getZonedDateTime(args.getValue(DATETIME1_KEY));
    10: final ZonedDateTime dateTime2 = getZonedDateTime(args.getValue(DATETIME2_KEY));
    11: Assert.isTrue(dateTime1.isBefore(dateTime2), args.getValue(DATETIME1_KEY) +
    12: " must be before " + args.getValue(DATETIME2_KEY));
    13:
    14: return exchange -> {
    15: final ZonedDateTime now = ZonedDateTime.now();
    16: return now.isAfter(dateTime1) && now.isBefore(dateTime2);
    17: };
    18: }
    19:
    20: public static ZonedDateTime getZonedDateTime(Object value) {
    21: ZonedDateTime dateTime;
    22: if (value instanceof ZonedDateTime) {
    23: dateTime = ZonedDateTime.class.cast(value);
    24: } else {
    25: dateTime = parseZonedDateTime(value.toString());
    26: }
    27: return dateTime;
    28: }
    29:
    30: public static ZonedDateTime parseZonedDateTime(String dateString) {
    31: ZonedDateTime dateTime;
    32: try {
    33: // 数字
    34: long epoch = Long.parseLong(dateString);
    35: dateTime = Instant.ofEpochMilli(epoch).atOffset(ZoneOffset.ofTotalSeconds(0))
    36: .toZonedDateTime();
    37: } catch (NumberFormatException e) {
    38: // 字符串
    39: // try ZonedDateTime instead
    40: dateTime = ZonedDateTime.parse(dateString);
    41: }
    42:
    43: return dateTime;
    44: }
    45:
    46: }

    • Tulpe 参数 :datetime1 / datetime2
    • 第 20 至 44 行 :解析配置的时间值 。
      • 第 22 至 23 行 :当值类型为 ZonedDateTime 。主要使用 Java / Kotlin 配置 Route 时,例如 RoutePredicates#between(ZonedDateTime, ZonedDateTime)
      • 第 33 至 36 行 :当值类型为 Long 。例如配置文件 1511795602765
      • 当 38 至 41 行 :当值类型为 String 。例如配置文件里 2017-01-20T17:42:47.789-07:00[America/Denver]

6. CookieRoutePredicateFactory

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

  • RoutePredicates 方法 :#cookie(String, String)

  • 配置 :

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

  • 代码 :

     1: public class CookieRoutePredicateFactory implements RoutePredicateFactory {
    2:
    3: public static final String NAME_KEY = "name";
    4: public static final String REGEXP_KEY = "regexp";
    5:
    6: @Override
    7: public List<String> argNames() {
    8: return Arrays.asList(NAME_KEY, REGEXP_KEY);
    9: }
    10:
    11: @Override
    12: public Predicate<ServerWebExchange> apply(Tuple args) {
    13: String name = args.getString(NAME_KEY);
    14: String regexp = args.getString(REGEXP_KEY);
    15:
    16: return exchange -> {
    17: List<HttpCookie> cookies = exchange.getRequest().getCookies().get(name);
    18: for (HttpCookie cookie : cookies) {
    19: // 正则匹配
    20: if (cookie.getValue().matches(regexp)) {
    21: return true;
    22: }
    23: }
    24: return false;
    25: };
    26: }
    27: }

    • Tulpe 参数 :name / regexp
    • 第 20 行 :指定 Cookie 正则匹配指定值

7. HeaderRoutePredicateFactory

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

  • RoutePredicates 方法 :#header(String, String)

  • 配置 :

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

  • 代码 :

     1: public class HeaderRoutePredicateFactory implements RoutePredicateFactory {
    2:
    3: public static final String HEADER_KEY = "header";
    4: public static final String REGEXP_KEY = "regexp";
    5:
    6: @Override
    7: public List<String> argNames() {
    8: return Arrays.asList(HEADER_KEY, REGEXP_KEY);
    9: }
    10:
    11: @Override
    12: public Predicate<ServerWebExchange> apply(Tuple args) {
    13: String header = args.getString(HEADER_KEY);
    14: String regexp = args.getString(REGEXP_KEY);
    15:
    16: return exchange -> {
    17: List<String> values = exchange.getRequest().getHeaders().get(header);
    18: for (String value : values) {
    19: // 正则匹配
    20: if (value.matches(regexp)) {
    21: return true;
    22: }
    23: }
    24: return false;
    25: };
    26: }
    27: }

    • Tulpe 参数 :header / regexp
    • 第 20 行 :指定 Header 正则匹配指定值

8. HostRoutePredicateFactory

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

  • RoutePredicates 方法 :#host(String)

  • 配置 :

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

  • 代码 :

     1: public class HostRoutePredicateFactory implements RoutePredicateFactory {
    2:
    3: private PathMatcher pathMatcher = new AntPathMatcher(".");
    4:
    5: public void setPathMatcher(PathMatcher pathMatcher) {
    6: this.pathMatcher = pathMatcher;
    7: }
    8:
    9: @Override
    10: public List<String> argNames() {
    11: return Collections.singletonList(PATTERN_KEY);
    12: }
    13:
    14: @Override
    15: public Predicate<ServerWebExchange> apply(Tuple args) {
    16: String pattern = args.getString(PATTERN_KEY);
    17:
    18: return exchange -> {
    19: String host = exchange.getRequest().getHeaders().getFirst("Host");
    20: // 匹配
    21: return this.pathMatcher.match(pattern, host);
    22: };
    23: }
    24: }

    • Tulpe 参数 :pattern
    • pathMatcher 属性,路径匹配器,默认使用 org.springframework.util.AntPathMatcher 。通过 #setPathMatcher(PathMatcher) 方法,可以重新设置。
    • 第 21 行 :请求路径 匹配指定值

9. MethodRoutePredicateFactory

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

  • RoutePredicates 方法 :#method(String)

  • 配置 :

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

  • 代码 :

     1: public class MethodRoutePredicateFactory implements RoutePredicateFactory {
    2:
    3: public static final String METHOD_KEY = "method";
    4:
    5: @Override
    6: public List<String> argNames() {
    7: return Arrays.asList(METHOD_KEY);
    8: }
    9:
    10: @Override
    11: public Predicate<ServerWebExchange> apply(Tuple args) {
    12: String method = args.getString(METHOD_KEY);
    13: return exchange -> {
    14: HttpMethod requestMethod = exchange.getRequest().getMethod();
    15: // 正则匹配
    16: return requestMethod.matches(method);
    17: };
    18: }
    19: }

    • Tulpe 参数 :method
    • 第 16 行 :请求 Method 匹配指定值

10. PathRoutePredicateFactory

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

  • RoutePredicates 方法 :#path(String, String)

  • 配置 :

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

  • 代码 :

     1: public class PathRoutePredicateFactory implements RoutePredicateFactory {
    2:
    3: private PathPatternParser pathPatternParser = new PathPatternParser();
    4:
    5: public void setPathPatternParser(PathPatternParser pathPatternParser) {
    6: this.pathPatternParser = pathPatternParser;
    7: }
    8:
    9: @Override
    10: public List<String> argNames() {
    11: return Collections.singletonList(PATTERN_KEY);
    12: }
    13:
    14: @Override
    15: public Predicate<ServerWebExchange> apply(Tuple args) {
    16: // 解析 Path ,创建对应的 PathPattern
    17: String unparsedPattern = args.getString(PATTERN_KEY);
    18: PathPattern pattern;
    19: synchronized (this.pathPatternParser) {
    20: pattern = this.pathPatternParser.parse(unparsedPattern);
    21: }
    22:
    23: return exchange -> {
    24: PathContainer path = parsePath(exchange.getRequest().getURI().getPath());
    25:
    26: // 匹配
    27: boolean match = pattern.matches(path);
    28: traceMatch("Pattern", pattern.getPatternString(), path, match);
    29: if (match) {
    30: // 解析 路径参数,例如 path=/foo/123 <=> /foo/{segment}
    31: PathMatchInfo uriTemplateVariables = pattern.matchAndExtract(path);
    32: exchange.getAttributes().put(URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriTemplateVariables);
    33: return true;
    34: }
    35: else {
    36: return false;
    37: }
    38: };
    39: }
    40: }

    • Tulpe 参数 :pattern

    • pathPatternParser 属性,路径模式解析器。

    • 第 17 至 21 行 :解析配置的 Path ,创建对应的 PathPattern 。考虑到解析过程中的线程安全,此处使用 synchronized 修饰符,详见 PathPatternParser#parse(String) 方法的注释。

    • 第 24 至 27 行 :解析请求的 Path ,匹配配置的 Path 。

    • 第 30 至 32 行 :解析路径参数,设置到 ServerWebExchange.attributes 属性中,提供给后续的 GatewayFilter 使用。举个例子,当配置的 Path 为 /foo/{segment} ,请求的 Path 为 /foo/123 ,在此处打断点,结果如下图 :

      FROM 《Spring Cloud Gateway》
      This predicate extracts the URI template variables (like segment defined in the example above) as a map of names and values and places it in the ServerWebExchange.getAttributes() with a key defined in PathRoutePredicate.URL_PREDICATE_VARS_ATTR. Those values are then available for use by GatewayFilter Factories

11. QueryRoutePredicateFactory

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

  • RoutePredicates 方法 :#query(String, String)

  • 配置 :

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

  • 代码 :

     1: public class QueryRoutePredicateFactory implements RoutePredicateFactory {
    2:
    3: public static final String PARAM_KEY = "param";
    4: public static final String REGEXP_KEY = "regexp";
    5:
    6: @Override
    7: public List<String> argNames() {
    8: return Arrays.asList(PARAM_KEY, REGEXP_KEY);
    9: }
    10:
    11: @Override
    12: public boolean validateArgs() {
    13: return false;
    14: }
    15:
    16: @Override
    17: public Predicate<ServerWebExchange> apply(Tuple args) {
    18: validateMin(1, args);
    19: String param = args.getString(PARAM_KEY);
    20:
    21: return exchange -> {
    22: // 包含 参数
    23: if (!args.hasFieldName(REGEXP_KEY)) {
    24: // check existence of header
    25: return exchange.getRequest().getQueryParams().containsKey(param);
    26: }
    27:
    28: // 正则匹配 参数
    29: String regexp = args.getString(REGEXP_KEY);
    30: List<String> values = exchange.getRequest().getQueryParams().get(param);
    31: for (String value : values) {
    32: if (value.matches(regexp)) {
    33: return true;
    34: }
    35: }
    36: return false;
    37: };
    38: }
    39: }

    • Tulpe 参数 :param ( 必填 ) / regexp ( 选填 ) 。
    • 第 18 行 :调用 #validateMin(...) 方法,校验参数数量至少为 1 ,即 param 非空 。
    • 第 22 至 26 行 :当 regexp 为空时,校验 param 对应的 QueryParam 存在。
    • 第 28 至 35 行 :当 regexp 非空时,请求 param 对应的 QueryParam 正则匹配指定值
      • QueryParams 为空时,会报空指针 BUG

12. RemoteAddrRoutePredicateFactory

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

  • RoutePredicates 方法 :#remoteAddr(String...)

  • 配置 :

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

  • 代码 :

     1: public class RemoteAddrRoutePredicateFactory implements RoutePredicateFactory {
    2:
    3: private static final Log log = LogFactory.getLog(RemoteAddrRoutePredicateFactory.class);
    4:
    5: @Override
    6: public Predicate<ServerWebExchange> apply(Tuple args) {
    7: validate(1, args);
    8:
    9: //
    10: List<SubnetUtils> sources = new ArrayList<>();
    11: if (args != null) {
    12: for (Object arg : args.getValues()) {
    13: addSource(sources, (String) arg);
    14: }
    15: }
    16:
    17: return exchange -> {
    18: InetSocketAddress remoteAddress = exchange.getRequest().getRemoteAddress();
    19: if (remoteAddress != null) {
    20: // 来源 IP
    21: String hostAddress = remoteAddress.getAddress().getHostAddress();
    22: String host = exchange.getRequest().getURI().getHost();
    23: if (!hostAddress.equals(host)) {
    24: log.warn("Remote addresses didn't match " + hostAddress + " != " + host);
    25: }
    26:
    27: //
    28: for (SubnetUtils source : sources) {
    29: if (source.getInfo().isInRange(hostAddress)) {
    30: return true;
    31: }
    32: }
    33: }
    34:
    35: return false;
    36: };
    37: }
    38:
    39: private void addSource(List<SubnetUtils> sources, String source) {
    40: boolean inclusiveHostCount = false;
    41: if (!source.contains("/")) { // no netmask, add default
    42: source = source + "/32";
    43: }
    44: if (source.endsWith("/32")) {
    45: //http://stackoverflow.com/questions/2942299/converting-cidr-address-to-subnet-mask-and-network-address#answer-6858429
    46: inclusiveHostCount = true;
    47: }
    48: //TODO: howto support ipv6 as well?
    49: SubnetUtils subnetUtils = new SubnetUtils(source);
    50: subnetUtils.setInclusiveHostCount(inclusiveHostCount);
    51: sources.add(subnetUtils);
    52: }
    53: }

    • Tulpe 参数 :字符串数组
    • 第 7 行 :调用 #validateMin(...) 方法,校验参数数量至少为 1 ,字符串数组非空
    • 第 10 至 15 行 :使用 SubnetUtils 工具类,解析配置的值。
    • 第 21 至 25 行 :获得请求来源 IP 。
    • 第 28 至 32 行 :请求来源 IP指定范围内

666. 彩蛋

知识星球

😈 代码好多,贴的手都抽了。嘿嘿,RemoteAddrRoutePredicateFactory 写的有点偷懒。

胖友,分享一波朋友圈可好!

文章目录
  1. 1. 1. 概述
  2. 2. 2. RoutePredicateFactory
  3. 3. 3. AfterRoutePredicateFactory
  4. 4. 4. BeforeRoutePredicateFactory
  5. 5. 5. BetweenRoutePredicateFactory
  6. 6. 6. CookieRoutePredicateFactory
  7. 7. 7. HeaderRoutePredicateFactory
  8. 8. 8. HostRoutePredicateFactory
  9. 9. 9. MethodRoutePredicateFactory
  10. 10. 10. PathRoutePredicateFactory
  11. 11. 11. QueryRoutePredicateFactory
  12. 12. 12. RemoteAddrRoutePredicateFactory
  13. 13. 666. 彩蛋