Path 路由断言工厂需要两个参数:一个 Spring PathMatcher 模式列表和一个名为 matchTrailingSlash 的可选标记(默认为 true)。语法如下:
Pth=uri,matchTrailingSlash
注意,matchTrailingSlash 标记用于指示网关是否应该匹配结尾斜杠。当设置为 true 时,网关将匹配包含结尾斜杠和不包含结尾斜杠的 URL,并将它们视为相同的资源。当设置为 false 时,网关将只匹配精确的 URL 路径,不考虑结尾斜杠。这个标记通常用于配置网关的路由规则,以确定是否应该考虑结尾斜杠的影响。
例如:
spring: cloud: gateway: routes: - id: path_route uri: https://example.org predicates: - Path=/red/{segment},/blue/{segment}
如果请求路径为:/red/1 或 /red/1/ 或 /red/blue 或 /blue/green,则此路由匹配。
如果 matchTrailingSlash 设置为 false,则不会匹配请求路径 /red/1/,不匹配末尾的斜杠 /。
该断言将 URI 模板变量(如上例中定义的 segment)提取为名称和值的映射,并将其放入 ServerWebExchange.getAttributes() 中,其关键字定义在ServerWebExchangeUtils.URI_TEMPLATE_VARIABLES_ATTRIBUTE 中,这些值随后可供 GatewayFilter 工厂使用。
为了更方便地访问这些变量,我们提供了一个实用方法(名为 get)。下面的示例展示了如何使用 get 方法:
Map<String, String> uriVariables = ServerWebExchangeUtils.getUriTemplateVariables(exchange); String segment = uriVariables.get("segment");
将“Gateway 搭建网关服务”项目的配置文件 bootstrap.yml 内容使用如下配置替换:
server: # 网关端口 port: 9000 spring: application: # 服务名称 name: gateway-demo01 cloud: # nacos nacos: discovery: server-addr: localhost:8848 username: nacos password: nacos group: DEFAULT_GROUP # 网关路由配置 gateway: # 网关路由配置 routes: # 路由id,自定义,只要唯一集合 - id: service-order # 路由的目标地址,lb 就是负载均衡,后面跟服务名 # 需要集成 nacos 等服务注册中心 uri: lb://service-order # 路由断言,也就是判断请求是否符合路由规则的条件 predicates: # 按照路径匹配,只要以 /order/ 开头就符合要求 - Path=/order/**
修改好配置后,重启网关服务,使用 Postman 访问订单服务。如下图:
从上图可知,访问接口成功。如果将 Path 断言的地址修改为“/order2/**”,然后重启网关服务,继续访问接口,如下图:
地址不匹配,导致访问失败。
下面是 Path 路由断言工厂源码分析:
package org.springframework.cloud.gateway.handler.predicate; //... public class PathRoutePredicateFactory extends AbstractRoutePredicateFactory<PathRoutePredicateFactory.Config> { private static final Log log = LogFactory.getLog(RoutePredicateFactory.class); private static final String MATCH_OPTIONAL_TRAILING_SEPARATOR_KEY = "matchOptionalTrailingSeparator"; private PathPatternParser pathPatternParser = new PathPatternParser(); public PathRoutePredicateFactory() { super(Config.class); } public void setPathPatternParser(PathPatternParser pathPatternParser) { this.pathPatternParser = pathPatternParser; } @Override public List<String> shortcutFieldOrder() { return Arrays.asList("patterns", MATCH_OPTIONAL_TRAILING_SEPARATOR_KEY); } @Override public ShortcutType shortcutType() { return ShortcutType.GATHER_LIST_TAIL_FLAG; } // 主要代码逻辑 @Override 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 exchange -> { // 获取路径信息 PathContainer path = parsePath(exchange.getRequest().getURI().getPath()); // 使用配置的模式匹配路径 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; } }; } private static void traceMatch(String prefix, Object desired, Object actual, boolean match) { if (log.isTraceEnabled()) { String message = String.format("%s \"%s\" %s against value \"%s\"", prefix, desired, match ? "matches" : "does not match", actual); log.trace(message); } } @Validated public static class Config { private List<String> patterns = new ArrayList<>(); private boolean matchOptionalTrailingSeparator = true; // 配置信息 } }