文章背景图

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

2026-01-30
2
-
- 分钟
|

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

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

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

1. 概述

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

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

01-mQvMsPwrkbzShaJMNEKaZVHOmQQKCXsI.png

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 实现类如下图 :

02-RecBTkBrjAzdBzreahUHOVnhWlvGxpvC.png

另外,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)

  • 代码 :

    public class AfterRoutePredicateFactory implements RoutePredicateFactory {
    
    	public static final String DATETIME_KEY = "datetime";
    
    	@Override
    	public List<String> argNames() {
    		return Collections.singletonList(DATETIME_KEY);
    	}
    
    	@Override
    	public Predicate<ServerWebExchange> apply(Tuple args) {
    		Object value = args.getValue(DATETIME_KEY);
    		final ZonedDateTime dateTime = BetweenRoutePredicateFactory.getZonedDateTime(value);
    
    		return exchange -> {
    			final ZonedDateTime now = ZonedDateTime.now();
    			return now.isAfter(dateTime);
    		};
    	}
    
    }

    • 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]

  • 代码 :

    public class BeforeRoutePredicateFactory implements RoutePredicateFactory {
    
    	public static final String DATETIME_KEY = "datetime";
    
    	@Override
    	public List<String> argNames() {
    		return Collections.singletonList(DATETIME_KEY);
    	}
    
    	@Override
    	public Predicate<ServerWebExchange> apply(Tuple args) {
    		Object value = args.getValue(DATETIME_KEY);
    		final ZonedDateTime dateTime = BetweenRoutePredicateFactory.getZonedDateTime(value);
    
    		return exchange -> {
    			final ZonedDateTime now = ZonedDateTime.now();
    			return now.isBefore(dateTime);
    		};
    	}
    
    }

    • 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]

  • 代码 :

    public class BetweenRoutePredicateFactory implements RoutePredicateFactory {
    
    	public static final String DATETIME1_KEY = "datetime1";
    	public static final String DATETIME2_KEY = "datetime2";
    
    	@Override
    	public Predicate<ServerWebExchange> apply(Tuple args) {
    		//TODO: is ZonedDateTime the right thing to use?
    		final ZonedDateTime dateTime1 = getZonedDateTime(args.getValue(DATETIME1_KEY));
    		final ZonedDateTime dateTime2 = getZonedDateTime(args.getValue(DATETIME2_KEY));
    		Assert.isTrue(dateTime1.isBefore(dateTime2), args.getValue(DATETIME1_KEY) +
    				" must be before " + args.getValue(DATETIME2_KEY));
    
    		return exchange -> {
    			final ZonedDateTime now = ZonedDateTime.now();
    			return now.isAfter(dateTime1) && now.isBefore(dateTime2);
    		};
    	}
    
    	public static ZonedDateTime getZonedDateTime(Object value) {
    		ZonedDateTime dateTime;
    		if (value instanceof ZonedDateTime) {
    			dateTime = ZonedDateTime.class.cast(value);
    		} else {
    			dateTime = parseZonedDateTime(value.toString());
    		}
    		return dateTime;
    	}
    
    	public static ZonedDateTime parseZonedDateTime(String dateString) {
    		ZonedDateTime dateTime;
    		try {
    			// 数字
    			long epoch = Long.parseLong(dateString);
    			dateTime = Instant.ofEpochMilli(epoch).atOffset(ZoneOffset.ofTotalSeconds(0))
    					.toZonedDateTime();
    		} catch (NumberFormatException e) {
    			// 字符串
    			// try ZonedDateTime instead
    			dateTime = ZonedDateTime.parse(dateString);
    		}
    
    		return dateTime;
    	}
    
    }

    • 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

  • 代码 :

    public class CookieRoutePredicateFactory implements RoutePredicateFactory {
    
    	public static final String NAME_KEY = "name";
    	public static final String REGEXP_KEY = "regexp";
    
    	@Override
    	public List<String> argNames() {
    		return Arrays.asList(NAME_KEY, REGEXP_KEY);
    	}
    
    	@Override
    	public Predicate<ServerWebExchange> apply(Tuple args) {
    		String name = args.getString(NAME_KEY);
    		String regexp = args.getString(REGEXP_KEY);
    
    		return exchange -> {
    			List<HttpCookie> cookies = exchange.getRequest().getCookies().get(name);
    			for (HttpCookie cookie : cookies) {
    				// 正则匹配
    				if (cookie.getValue().matches(regexp)) {
    					return true;
    				}
    			}
    			return false;
    		};
    	}
    }

    • 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+

  • 代码 :

    public class HeaderRoutePredicateFactory implements RoutePredicateFactory {
    
    	public static final String HEADER_KEY = "header";
    	public static final String REGEXP_KEY = "regexp";
    
    	@Override
    	public List<String> argNames() {
    		return Arrays.asList(HEADER_KEY, REGEXP_KEY);
    	}
    
    	@Override
    	public Predicate<ServerWebExchange> apply(Tuple args) {
    		String header = args.getString(HEADER_KEY);
    		String regexp = args.getString(REGEXP_KEY);
    
    		return exchange -> {
    			List<String> values = exchange.getRequest().getHeaders().get(header);
    			for (String value : values) {
    				// 正则匹配
    				if (value.matches(regexp)) {
    					return true;
    				}
    			}
    			return false;
    		};
    	}
    }

    • 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

  • 代码 :

    public class HostRoutePredicateFactory implements RoutePredicateFactory {
    
    	private PathMatcher pathMatcher = new AntPathMatcher(".");
    
    	public void setPathMatcher(PathMatcher pathMatcher) {
    		this.pathMatcher = pathMatcher;
    	}
    
    	@Override
    	public List<String> argNames() {
    		return Collections.singletonList(PATTERN_KEY);
    	}
    
    	@Override
    	public Predicate<ServerWebExchange> apply(Tuple args) {
    		String pattern = args.getString(PATTERN_KEY);
    
    		return exchange -> {
    			String host = exchange.getRequest().getHeaders().getFirst("Host");
    			// 匹配
    			return this.pathMatcher.match(pattern, host);
    		};
    	}
    }

    • 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

  • 代码 :

    public class MethodRoutePredicateFactory implements RoutePredicateFactory {
    
    	public static final String METHOD_KEY = "method";
    
    	@Override
    	public List<String> argNames() {
    		return Arrays.asList(METHOD_KEY);
    	}
    
    	@Override
    	public Predicate<ServerWebExchange> apply(Tuple args) {
    		String method = args.getString(METHOD_KEY);
    		return exchange -> {
    			HttpMethod requestMethod = exchange.getRequest().getMethod();
    			// 正则匹配
    			return requestMethod.matches(method);
    		};
    	}
    }

    • 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}

  • 代码 :

    public class PathRoutePredicateFactory implements RoutePredicateFactory {
    
    	private PathPatternParser pathPatternParser = new PathPatternParser();
    
    	public void setPathPatternParser(PathPatternParser pathPatternParser) {
    		this.pathPatternParser = pathPatternParser;
    	}
    
    	@Override
    	public List<String> argNames() {
    		return Collections.singletonList(PATTERN_KEY);
    	}
    
    	@Override
    	public Predicate<ServerWebExchange> apply(Tuple args) {
    	    // 解析 Path ,创建对应的 PathPattern
    		String unparsedPattern = args.getString(PATTERN_KEY);
    		PathPattern pattern;
    		synchronized (this.pathPatternParser) {
    			pattern = this.pathPatternParser.parse(unparsedPattern);
    		}
    
    		return exchange -> {
    			PathContainer path = parsePath(exchange.getRequest().getURI().getPath());
    
    			// 匹配
    			boolean match = pattern.matches(path);
    			traceMatch("Pattern", pattern.getPatternString(), path, match);
    			if (match) {
    				// 解析 路径参数,例如 path=/foo/123 <=> /foo/{segment}
    				PathMatchInfo uriTemplateVariables = pattern.matchAndExtract(path);
    				exchange.getAttributes().put(URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriTemplateVariables);
    				return true;
    			}
    			else {
    				return false;
    			}
    		};
    	}
    }

    • 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 ,在此处打断点,结果如下图 :

      03-HCJyIQnqIooKMokEgHEKSkhZfVGEuJrx.png

      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.

  • 代码 :

    public class QueryRoutePredicateFactory implements RoutePredicateFactory {
    
    	public static final String PARAM_KEY = "param";
    	public static final String REGEXP_KEY = "regexp";
    
    	@Override
    	public List<String> argNames() {
    		return Arrays.asList(PARAM_KEY, REGEXP_KEY);
    	}
    
    	@Override
    	public boolean validateArgs() {
    		return false;
    	}
    
    	@Override
    	public Predicate<ServerWebExchange> apply(Tuple args) {
    		validateMin(1, args);
    		String param = args.getString(PARAM_KEY);
    
    		return exchange -> {
    		    // 包含 参数
    			if (!args.hasFieldName(REGEXP_KEY)) {
    				// check existence of header
    				return exchange.getRequest().getQueryParams().containsKey(param);
    			}
    
    			// 正则匹配 参数
    			String regexp = args.getString(REGEXP_KEY);
    			List<String> values = exchange.getRequest().getQueryParams().get(param);
    			for (String value : values) {
    				if (value.matches(regexp)) {
    					return true;
    				}
    			}
    			return false;
    		};
    	}
    }

    • 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

  • 代码 :

    public class RemoteAddrRoutePredicateFactory implements RoutePredicateFactory {
    
    	private static final Log log = LogFactory.getLog(RemoteAddrRoutePredicateFactory.class);
    
    	@Override
    	public Predicate<ServerWebExchange> apply(Tuple args) {
    		validate(1, args);
    
    		//
    		List<SubnetUtils> sources = new ArrayList<>();
    		if (args != null) {
    			for (Object arg : args.getValues()) {
    				addSource(sources, (String) arg);
    			}
    		}
    
    		return exchange -> {
    			InetSocketAddress remoteAddress = exchange.getRequest().getRemoteAddress();
    			if (remoteAddress != null) {
    				// 来源 IP
    				String hostAddress = remoteAddress.getAddress().getHostAddress();
    				String host = exchange.getRequest().getURI().getHost();
    				if (!hostAddress.equals(host)) {
    					log.warn("Remote addresses didn't match " + hostAddress + " != " + host);
    				}
    
    				//
    				for (SubnetUtils source : sources) {
    					if (source.getInfo().isInRange(hostAddress)) {
    						return true;
    					}
    				}
    			}
    
    			return false;
    		};
    	}
    
    	private void addSource(List<SubnetUtils> sources, String source) {
    		boolean inclusiveHostCount = false;
    		if (!source.contains("/")) { // no netmask, add default
    			source = source + "/32";
    		}
    		if (source.endsWith("/32")) {
    			//http://stackoverflow.com/questions/2942299/converting-cidr-address-to-subnet-mask-and-network-address#answer-6858429
    			inclusiveHostCount = true;
    		}
    		//TODO: howto support ipv6 as well?
    		SubnetUtils subnetUtils = new SubnetUtils(source);
    		subnetUtils.setInclusiveHostCount(inclusiveHostCount);
    		sources.add(subnetUtils);
    	}
    }

    • Tulpe 参数 :字符串数组

    • 第 7 行 :调用 #validateMin(...) 方法,校验参数数量至少为 1 ,字符串数组非空

    • 第 10 至 15 行 :使用 SubnetUtils 工具类,解析配置的值。

    • 第 21 至 25 行 :获得请求来源 IP 。

    • 第 28 至 32 行 :请求来源 IP指定范围内

评论交流

文章目录