Gateway Path 路由断言工厂

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 访问订单服务。如下图:

Gateway Path 路由断言工厂

从上图可知,访问接口成功。如果将 Path 断言的地址修改为“/order2/**”,然后重启网关服务,继续访问接口,如下图:

Gateway Path 路由断言工厂

地址不匹配,导致访问失败。

源码分析

下面是 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;
       // 配置信息
    }


}
说说我的看法
全部评论(
没有评论
关于
本网站专注于 Java、数据库(MySQL、Oracle)、Linux、软件架构及大数据等多领域技术知识分享。涵盖丰富的原创与精选技术文章,助力技术传播与交流。无论是技术新手渴望入门,还是资深开发者寻求进阶,这里都能为您提供深度见解与实用经验,让复杂编码变得轻松易懂,携手共赴技术提升新高度。如有侵权,请来信告知:hxstrive@outlook.com
公众号