Spring Cloud Gateway 源码剖析之Predicate谓词详解

简介: Spring Cloud Gateway 源码剖析之Predicate谓词详解

一、前言

我们上一篇 Spring Cloud Gateway 源码剖析之Route数据模型 中讲到了 Route 数据模型,其中有 Predicate 属性。


这一篇我们就来讲一讲 Predicate 谓词相关源码。Predicate 对象是由 RoutePredicateFactory 工厂类创建,那我们就来看下 RoutePredicateFactory 是如何创建 Predicate 的。

二、RoutePredicateFactory

从上图可知

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

可以直接看到处理器类与相关谓词工厂类如下


这里再对相关谓词工厂进行分类:


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(AfterRoutePredicateFactory.Config config) {
    ZonedDateTime datetime = config.getDatetime();
    return (exchange) -> {
        ZonedDateTime now = ZonedDateTime.now();
        return now.isAfter(datetime);
    };
}

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(BeforeRoutePredicateFactory.Config config) {
    ZonedDateTime datetime = config.getDatetime();
    return (exchange) -> {
        ZonedDateTime now = ZonedDateTime.now();
        return now.isBefore(datetime);
    };
}

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(BetweenRoutePredicateFactory.Config config) {
    ZonedDateTime datetime1 = config.datetime1;
    ZonedDateTime datetime2 = config.datetime2;
    Assert.isTrue(datetime1.isBefore(datetime2), config.datetime1 + " must be before " + config.datetime2);
    return (exchange) -> {
        ZonedDateTime now = ZonedDateTime.now();
        return now.isAfter(datetime1) && now.isBefore(datetime2);
    };
}

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(CookieRoutePredicateFactory.Config config) {
    return (exchange) -> {
        List<HttpCookie> cookies = (List)exchange.getRequest().getCookies().get(config.name);
        if (cookies == null) {
            return false;
        } else {
            Iterator var3 = cookies.iterator();
            HttpCookie cookie;
            do {
                if (!var3.hasNext()) {
                    return false;
                }
                cookie = (HttpCookie)var3.next();
            } while(!cookie.getValue().matches(config.regexp));
            return true;
        }
    };
}

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(HeaderRoutePredicateFactory.Config config) {
    boolean hasRegex = !StringUtils.isEmpty(config.regexp);
    return (exchange) -> {
        List<String> values = (List)exchange.getRequest().getHeaders().getOrDefault(config.header, Collections.emptyList());
        if (values.isEmpty()) {
            return false;
        } else {
            return hasRegex ? values.stream().anyMatch((value) -> {
                return value.matches(config.regexp);
            }) : true;
        }
    };
}

2.6 HostRoutePredicateFactory

  • Route 匹配 :请求 Host 匹配指定值
  • 配置:
spring:
  cloud:
    gateway:
      routes:
      - id: host_route
        uri: http://example.org
        predicates:
        - Host=**.somehost.org
  • 代码:
public Predicate<ServerWebExchange> apply(HostRoutePredicateFactory.Config config) {
    return (exchange) -> {
        String host = exchange.getRequest().getHeaders().getFirst("Host");
        Optional<String> optionalPattern = config.getPatterns().stream().filter((pattern) -> {
            return this.pathMatcher.match(pattern, host);
        }).findFirst();
        if (optionalPattern.isPresent()) {
            Map<String, String> variables = this.pathMatcher.extractUriTemplateVariables((String)optionalPattern.get(), host);
            ServerWebExchangeUtils.putUriTemplateVariables(exchange, variables);
            return true;
        } else {
            return false;
        }
    };
}

2.7 MethodRoutePredicateFactory

  • Route 匹配 :请求 Method 匹配指定值
  • 配置:
spring:
  cloud:
    gateway:
      routes:
      - id: method_route
        uri: http://example.org
        predicates:
        - Method=GET
  • 代码:
public Predicate<ServerWebExchange> apply(MethodRoutePredicateFactory.Config config) {
    return (exchange) -> {
        HttpMethod requestMethod = exchange.getRequest().getMethod();
        return requestMethod == config.getMethod();
    };
}

2.8 PathRoutePredicateFactory

  • Route 匹配 :请求 Path 匹配指定值
  • 配置:
spring:
  cloud:
    gateway:
      routes:
      - id: host_route
        uri: http://example.org
        predicates:
        - Path=/foo/{segment}
  • 代码:
public Predicate<ServerWebExchange> apply(PathRoutePredicateFactory.Config config) {
    ArrayList<PathPattern> pathPatterns = new ArrayList();
    synchronized(this.pathPatternParser) {
        this.pathPatternParser.setMatchOptionalTrailingSeparator(config.isMatchOptionalTrailingSeparator());
        config.getPatterns().forEach((pattern) -> {
            PathPattern pathPattern = this.pathPatternParser.parse(pattern);
            pathPatterns.add(pathPattern);
        });
    }
    return (exchange) -> {
        PathContainer path = PathContainer.parsePath(exchange.getRequest().getURI().getPath());
        Optional<PathPattern> optionalPathPattern = pathPatterns.stream().filter((pattern) -> {
            return pattern.matches(path);
        }).findFirst();
        if (optionalPathPattern.isPresent()) {
            PathPattern pathPattern = (PathPattern)optionalPathPattern.get();
            traceMatch("Pattern", pathPattern.getPatternString(), path, true);
            PathMatchInfo pathMatchInfo = pathPattern.matchAndExtract(path);
            ServerWebExchangeUtils.putUriTemplateVariables(exchange, pathMatchInfo.getUriVariables());
            return true;
        } else {
            traceMatch("Pattern", config.getPatterns(), path, false);
            return false;
        }
    };
}

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(QueryRoutePredicateFactory.Config config) {
    return (exchange) -> {
        if (!StringUtils.hasText(config.regexp)) {
            return exchange.getRequest().getQueryParams().containsKey(config.param);
        } else {
            List<String> values = (List)exchange.getRequest().getQueryParams().get(config.param);
            if (values == null) {
                return false;
            } else {
                Iterator var3 = values.iterator();
                String value;
                do {
                    if (!var3.hasNext()) {
                        return false;
                    }
                    value = (String)var3.next();
                } while(value == null || !value.matches(config.regexp));
                return true;
            }
        }
    };
}

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(RemoteAddrRoutePredicateFactory.Config config) {
    List<IpSubnetFilterRule> sources = this.convert(config.sources);
    return (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);
            }
            Iterator var6 = sources.iterator();
            while(var6.hasNext()) {
                IpSubnetFilterRule source = (IpSubnetFilterRule)var6.next();
                if (source.matches(remoteAddress)) {
                    return true;
                }
            }
        }
        return false;
    };
}

三、RoutePredicateHandlerMapping

我们先来看下 Spring Cloud Gateway 官网提供的架构图:

上一节讲完了常见分类的 Predicate 匹配规则,客户端发送请求过来,通过 HandlerMapping 进行 predicate 的匹配,匹配成功再进行下面的处理。

3.1 org.springframework.web.reactive.DispatcherHandler

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

public class DispatcherHandler implements WebHandler, ApplicationContextAware {
    private static final Exception HANDLER_NOT_FOUND_EXCEPTION;
    @Nullable
    private List<HandlerMapping> handlerMappings;
    @Nullable
    private List<HandlerAdapter> handlerAdapters;
    @Nullable
    private List<HandlerResultHandler> resultHandlers;
    public Mono<Void> handle(ServerWebExchange exchange) {
        return this.handlerMappings == null ? this.createNotFoundError() : 
        // 顺序使用 handlerMappings 获得对应的 WebHandler 
        Flux.fromIterable(this.handlerMappings).concatMap((mapping) -> {
            // 获得 Handler 
            return mapping.getHandler(exchange);
        // 如果匹配不到 WebHandler ,返回 HANDLER_NOT_FOUND_EXCEPTION 。
        }).next().switchIfEmpty(this.createNotFoundError()).flatMap((handler) -> {
            // 调用 invokeHandler() 方法,执行 Handler 。
            return this.invokeHandler(exchange, handler);
        }).flatMap((result) -> {
            // 调用 handleResult() 方法,处理结果
            return this.handleResult(exchange, result);
        });
    }
    ...
}

继续跟一下 invokeHandler() 方法:

private Mono<HandlerResult> invokeHandler(ServerWebExchange exchange, Object handler) {
    if (this.handlerAdapters != null) {
        // 获取Adapters, WebHandler 的处理器适配器。
        Iterator var3 = this.handlerAdapters.iterator();
        while(var3.hasNext()) {
            HandlerAdapter handlerAdapter = (HandlerAdapter)var3.next();
            // 调用support方法 ,是否支持 WebHandler
            if (handlerAdapter.supports(handler)) {
                // 调用handle 方法,执行处理器
                return handlerAdapter.handle(exchange, handler);
            }
        }
    }
    return Mono.error(new IllegalStateException("No HandlerAdapter: " + handler));
}
public boolean supports(Object handler) {
    return WebHandler.class.isAssignableFrom(handler.getClass());
}
public Mono<HandlerResult> handle(ServerWebExchange exchange, Object handler) {
    WebHandler webHandler = (WebHandler)handler;
    // 执行处理器。例如,WebHandler 为 FilteringWebHandler 时,获得 Route 的 GatewayFilter 数组,创建 GatewayFilterChain 处理请求。
    Mono<Void> mono = webHandler.handle(exchange);
    // 在 WebHandler 执行完后 #then(Mongo),然后返回 Mono.empty() 。
    return mono.then(Mono.empty());
}

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

private Mono<Void> handleResult(ServerWebExchange exchange, HandlerResult result) {
    return this.getResultHandler(result).handleResult(exchange, result).onErrorResume((ex) -> {
        return result.applyExceptionHandler(ex).flatMap((exceptionResult) -> {
            return this.getResultHandler(exceptionResult).handleResult(exchange, exceptionResult);
        });
    });
}

3.2 org.springframework.cloud.gateway.handler.RoutePredicateHandlerMapping

接收到请求,匹配 Route ,并返回处理 Route 的 FilteringWebHandler。

SimpleHandlerAdapter#handle(ServerWebExchange, Object) 调用 FilteringWebHandler#handle(ServerWebExchange) 方法,处理请求。

public class RoutePredicateHandlerMapping extends AbstractHandlerMapping {
    private final FilteringWebHandler webHandler;
    private final RouteLocator routeLocator;
    private final Integer managmentPort;
    public RoutePredicateHandlerMapping(FilteringWebHandler webHandler, RouteLocator routeLocator, GlobalCorsProperties globalCorsProperties, Environment environment) {
        this.webHandler = webHandler;
        this.routeLocator = routeLocator;
        if (environment.containsProperty("management.server.port")) {
            this.managmentPort = new Integer(environment.getProperty("management.server.port"));
        } else {
            this.managmentPort = null;
        }
        // RequestMappingHandlerMapping 之后
        this.setOrder(1);
        this.setCorsConfigurations(globalCorsProperties.getCorsConfigurations());
    }
    protected Mono<?> getHandlerInternal(ServerWebExchange exchange) {
        if (this.managmentPort != null && exchange.getRequest().getURI().getPort() == this.managmentPort) {
            return Mono.empty();
        } else {
            // 设置 GATEWAY_HANDLER_MAPPER_ATTR 为 
            RoutePredicateHandlerMappingexchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_HANDLER_MAPPER_ATTR, this.getSimpleName());
            // 匹配路由
            return this.lookupRoute(exchange).flatMap((r) -> {
                exchange.getAttributes().remove(ServerWebExchangeUtils.GATEWAY_PREDICATE_ROUTE_ATTR);
                if (this.logger.isDebugEnabled()) {
                    this.logger.debug("Mapping [" + this.getExchangeDesc(exchange) + "] to " + r);
                }
                // 设置 GATEWAY_ROUTE_ATTR 为 匹配的 Route
                exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR, r);
                return Mono.just(this.webHandler);
            }).switchIfEmpty(Mono.empty().then(Mono.fromRunnable(() -> { //匹配不到返回
                exchange.getAttributes().remove(ServerWebExchangeUtils.GATEWAY_PREDICATE_ROUTE_ATTR);
                if (this.logger.isTraceEnabled()) {
                    this.logger.trace("No RouteDefinition found for [" + this.getExchangeDesc(exchange) + "]");
                }
            })));
        }
    }
}

跟一下 lookupRoute 匹配路由,这个方法是网关的核心,像我们自研的网关,如果你刚接手公司中的网关项目,找到匹配路由再展开,能帮你省很多时间,快速熟悉公司中网关的项目。

protected Mono<Route> lookupRoute(ServerWebExchange exchange) {
    // 获取所有路由
    return this.routeLocator.getRoutes().concatMap((route) -> {
        return Mono.just(route).filterWhen((r) -> {
            exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_PREDICATE_ROUTE_ATTR, r.getId());
            // 并调用 Predicate#apply(ServerWebExchange) 方法,顺序匹配一个 Route。
            return (Publisher)r.getPredicate().apply(exchange);
        // 未来会增加匹配过程中发生异常的处理。目前,任何一个 Predicate#test(ServerWebExchange) 的方法调用发生异常时,都会导致匹配不到 Route。一定要注意。      
        }).doOnError((e) -> {
            this.logger.error("Error applying predicate for route: " + route.getId(), e);
        }).onErrorResume((e) -> {
            return Mono.empty();
        });
    }).next().map((route) -> {
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Route matched: " + route.getId());
        }
        this.validateRoute(route, exchange);
        return route;
    });
}

3.3 org.springframework.cloud.gateway.handler.FilteringWebHandler

获得 Route 的 GatewayFilter 数组,创建 GatewayFilterChain 处理请求。这里我们放到下一篇来讲,下一篇也很重要,从原理来说也不是很难理解,就是一个过滤器链。但从 Gateway 的两大核心:路由+过滤链来说,这又很重要。



欢迎大家关注我的公众号【老周聊架构】,Java后端主流技术栈的原理、源码分析、架构以及各种互联网高并发、高性能、高可用的解决方案。


喜欢的话,点赞、再看、分享三连。

相关文章
|
6天前
|
前端开发 Java 物联网
智慧班牌源码,采用Java + Spring Boot后端框架,搭配Vue2前端技术,支持SaaS云部署
智慧班牌系统是一款基于信息化与物联网技术的校园管理工具,集成电子屏显示、人脸识别及数据交互功能,实现班级信息展示、智能考勤与家校互通。系统采用Java + Spring Boot后端框架,搭配Vue2前端技术,支持SaaS云部署与私有化定制。核心功能涵盖信息发布、考勤管理、教务处理及数据分析,助力校园文化建设与教学优化。其综合性和可扩展性有效打破数据孤岛,提升交互体验并降低管理成本,适用于日常教学、考试管理和应急场景,为智慧校园建设提供全面解决方案。
68 14
|
5月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
161 2
|
4月前
|
JSON Java API
利用Spring Cloud Gateway Predicate优化微服务路由策略
Spring Cloud Gateway 的路由配置中,`predicates`​(断言)用于定义哪些请求应该匹配特定的路由规则。 断言是Gateway在进行路由时,根据具体的请求信息如请求路径、请求方法、请求参数等进行匹配的规则。当一个请求的信息符合断言设置的条件时,Gateway就会将该请求路由到对应的服务上。
281 69
利用Spring Cloud Gateway Predicate优化微服务路由策略
|
2月前
|
前端开发 Java Nacos
🛡️Spring Boot 3 整合 Spring Cloud Gateway 工程实践
本文介绍了如何使用Spring Cloud Alibaba 2023.0.0.0技术栈构建微服务网关,以应对微服务架构中流量治理与安全管控的复杂性。通过一个包含鉴权服务、文件服务和主服务的项目,详细讲解了网关的整合与功能开发。首先,通过统一路由配置,将所有请求集中到网关进行管理;其次,实现了限流防刷功能,防止恶意刷接口;最后,添加了登录鉴权机制,确保用户身份验证。整个过程结合Nacos注册中心,确保服务注册与配置管理的高效性。通过这些实践,帮助开发者更好地理解和应用微服务网关。
205 0
🛡️Spring Boot 3 整合 Spring Cloud Gateway 工程实践
|
1月前
|
存储 监控 数据可视化
SaaS云计算技术的智慧工地源码,基于Java+Spring Cloud框架开发
智慧工地源码基于微服务+Java+Spring Cloud +UniApp +MySql架构,利用传感器、监控摄像头、AI、大数据等技术,实现施工现场的实时监测、数据分析与智能决策。平台涵盖人员、车辆、视频监控、施工质量、设备、环境和能耗管理七大维度,提供可视化管理、智能化报警、移动智能办公及分布计算存储等功能,全面提升工地的安全性、效率和质量。
|
3月前
|
监控 JavaScript 数据可视化
建筑施工一体化信息管理平台源码,支持微服务架构,采用Java、Spring Cloud、Vue等技术开发。
智慧工地云平台是专为建筑施工领域打造的一体化信息管理平台,利用大数据、云计算、物联网等技术,实现施工区域各系统数据汇总与可视化管理。平台涵盖人员、设备、物料、环境等关键因素的实时监控与数据分析,提供远程指挥、决策支持等功能,提升工作效率,促进产业信息化发展。系统由PC端、APP移动端及项目、监管、数据屏三大平台组成,支持微服务架构,采用Java、Spring Cloud、Vue等技术开发。
155 7
|
4月前
|
消息中间件 监控 Java
如何将Spring Boot + RabbitMQ应用程序部署到Pivotal Cloud Foundry (PCF)
如何将Spring Boot + RabbitMQ应用程序部署到Pivotal Cloud Foundry (PCF)
74 6
|
4月前
|
存储 缓存 Java
Spring面试必问:手写Spring IoC 循环依赖底层源码剖析
在Spring框架中,IoC(Inversion of Control,控制反转)是一个核心概念,它允许容器管理对象的生命周期和依赖关系。然而,在实际应用中,我们可能会遇到对象间的循环依赖问题。本文将深入探讨Spring如何解决IoC中的循环依赖问题,并通过手写源码的方式,让你对其底层原理有一个全新的认识。
108 2
|
4月前
|
JavaScript Java Kotlin
深入 Spring Cloud Gateway 过滤器
Spring Cloud Gateway 是新一代微服务网关框架,支持多种过滤器实现。本文详解了 `GlobalFilter`、`GatewayFilter` 和 `AbstractGatewayFilterFactory` 三种过滤器的实现方式及其应用场景,帮助开发者高效利用这些工具进行网关开发。
630 1
|
4月前
|
Java 关系型数据库 MySQL
如何将Spring Boot + MySQL应用程序部署到Pivotal Cloud Foundry (PCF)
如何将Spring Boot + MySQL应用程序部署到Pivotal Cloud Foundry (PCF)
109 5