详解springcloud gateway工作原理、断言、filter、uri、id、全局跨域、globalfilter等以及关键源码实现

1.gateway概念
  1. 网关就是当前微服务项目的"统一入口"
  2. 程序中的网关就是当前微服务项目对外界开放的统一入口
  3. 所有外界的请求都需要先经过网关才能访问到我们的程序
  4. 提供了统一入口之后,方便对所有请求进行统一的检查和管理
2. 网关的主要功能
  • 将所有请求统一经过网关
  • 网关可以对这些请求进行检查
  • 网关方便记录所有请求的日志
  • 网关可以统一将所有请求路由到正确的模块\服务上

“路由"的近义词就是"分配”
在这里插入图片描述

3. 工作原理

在这里插入图片描述
在这里插入图片描述
Spring Gateway的工作原理基于路由、断言(Predicates)和过滤器(Filters)三大核心概念:

  • ‌路由(Route):定义了请求的转发规则,包括目标URL和匹配条件。
  • 断言(Predicates):用于匹配HTTP请求的各种条件,如路径、头信息、参数等。只有匹配成功的请求才会被路由处理。
  • 过滤器(Filters):在请求处理前后执行特定的逻辑,例如权限校验、日志记录
4. 配置和使用示例
spring:
  cloud:
    gateway:
      routes:
        - id: myroute
          uri: http://example.com
          predicates:
            - Path=/api/**
          filters:
            - AddRequestHeader=X-Request-ID, \${requestId}

这个配置定义了一个路由,所有路径以/api/开头的请求都会被转发到http://example.com,并且在请求头中添加一个X-Request-ID字段‌

springcloud gateway中配置uri有3种方式:

  • ws(websocket)方式: uri: ws://localhost:9000
  • http方式: uri: http://localhost:8130/
  • lb(注册中心中服务名字)方式: uri: lb://brilliance-consumer

springcloud gatetway命名规范

能被gateway的lb方式识别到的命名规则为:

   "[a-zA-Z]([a-zA-Z]|\\d|\\+|\\.|-)*:.*"

如果名字中有非*“a-zA-Z:.”*规则字符或者使用“_”,则会报错

5.网关gateway routes的组成

1. id:必须唯一

2. predicates(断言)
在这里插入图片描述
关键类源码分析:

package org.springframework.cloud.gateway.handler.predicate;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.style.ToStringCreator;
import org.springframework.http.server.PathContainer;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.pattern.PathPattern;
import org.springframework.web.util.pattern.PathPattern.PathMatchInfo;
import org.springframework.web.util.pattern.PathPatternParser;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_PREDICATE_MATCHED_PATH_ATTR;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_PREDICATE_MATCHED_PATH_ROUTE_ID_ATTR;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_PREDICATE_PATH_CONTAINER_ATTR;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_PREDICATE_ROUTE_ATTR;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.putUriTemplateVariables;
import static org.springframework.http.server.PathContainer.parsePath;

/**
 * @author Spencer Gibb
 * @author Dhawal Kapil
 */
public class PathRoutePredicateFactory extends AbstractRoutePredicateFactory<PathRoutePredicateFactory.Config> {

    private static final Log log = LogFactory.getLog(PathRoutePredicateFactory.class);

    private static final String MATCH_TRAILING_SLASH = "matchTrailingSlash";

    private PathPatternParser pathPatternParser = new PathPatternParser();

    public PathRoutePredicateFactory() {
       super(Config.class);
    }

    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);
       }
    }

    public void setPathPatternParser(PathPatternParser pathPatternParser) {
       this.pathPatternParser = pathPatternParser;
    }

    @Override
    public List<String> shortcutFieldOrder() {
       return Arrays.asList("patterns", MATCH_TRAILING_SLASH);
    }

    @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.isMatchTrailingSlash());
          config.getPatterns().forEach(pattern -> {
             PathPattern pathPattern = this.pathPatternParser.parse(pattern);
             pathPatterns.add(pathPattern);
          });
       }
       return new GatewayPredicate() {
          @Override
          public boolean test(ServerWebExchange exchange) {
             PathContainer path = (PathContainer) exchange.getAttributes().computeIfAbsent(
                   GATEWAY_PREDICATE_PATH_CONTAINER_ATTR,
                   s -> parsePath(exchange.getRequest().getURI().getRawPath()));

             PathPattern match = null;
             for (int i = 0; i < pathPatterns.size(); i++) {
                PathPattern pathPattern = pathPatterns.get(i);
                if (pathPattern.matches(path)) {
                   match = pathPattern;
                   break;
                }
             }

             if (match != null) {
                traceMatch("Pattern", match.getPatternString(), path, true);
                PathMatchInfo pathMatchInfo = match.matchAndExtract(path);
                putUriTemplateVariables(exchange, pathMatchInfo.getUriVariables());
                exchange.getAttributes().put(GATEWAY_PREDICATE_MATCHED_PATH_ATTR, match.getPatternString());
                String routeId = (String) exchange.getAttributes().get(GATEWAY_PREDICATE_ROUTE_ATTR);
                if (routeId != null) {
                   // populated in RoutePredicateHandlerMapping
                   exchange.getAttributes().put(GATEWAY_PREDICATE_MATCHED_PATH_ROUTE_ID_ATTR, routeId);
                }
                return true;
             }
             else {
                traceMatch("Pattern", config.getPatterns(), path, false);
                return false;
             }
          }

          @Override
          public Object getConfig() {
             return config;
          }

          @Override
          public String toString() {
             return String.format("Paths: %s, match trailing slash: %b", config.getPatterns(),
                   config.isMatchTrailingSlash());
          }
       };
    }

    @Validated
    public static class Config {

       private List<String> patterns = new ArrayList<>();

       private boolean matchTrailingSlash = true;

       public List<String> getPatterns() {
          return patterns;
       }

       public Config setPatterns(List<String> patterns) {
          this.patterns = patterns;
          return this;
       }

       /**
        * @deprecated use {@link #isMatchTrailingSlash()}
        */
       @Deprecated
       public boolean isMatchOptionalTrailingSeparator() {
          return isMatchTrailingSlash();
       }

       /**
        * @deprecated use {@link #setMatchTrailingSlash(boolean)}
        */
       @Deprecated
       public Config setMatchOptionalTrailingSeparator(boolean matchOptionalTrailingSeparator) {
          setMatchTrailingSlash(matchOptionalTrailingSeparator);
          return this;
       }

       public boolean isMatchTrailingSlash() {
          return matchTrailingSlash;
       }

       public Config setMatchTrailingSlash(boolean matchTrailingSlash) {
          this.matchTrailingSlash = matchTrailingSlash;
          return this;
       }

       @Override
       public String toString() {
          return new ToStringCreator(this).append("patterns", patterns)
                .append(MATCH_TRAILING_SLASH, matchTrailingSlash).toString();
       }

    }

}
package org.springframework.cloud.gateway.handler.predicate;

import java.util.function.Consumer;
import java.util.function.Predicate;
import org.springframework.cloud.gateway.handler.AsyncPredicate;
import org.springframework.cloud.gateway.support.Configurable;
import org.springframework.cloud.gateway.support.NameUtils;
import org.springframework.cloud.gateway.support.ShortcutConfigurable;
import org.springframework.web.server.ServerWebExchange;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.toAsyncPredicate;

/**
 * @author Spencer Gibb
 */
@FunctionalInterface
public interface RoutePredicateFactory<C> extends ShortcutConfigurable, Configurable<C> {

    /**
     * Pattern key.
     */
    String PATTERN_KEY = "pattern";

    // useful for javadsl
    default Predicate<ServerWebExchange> apply(Consumer<C> consumer) {
       C config = newConfig();
       consumer.accept(config);
       beforeApply(config);
       return apply(config);
    }

    default AsyncPredicate<ServerWebExchange> applyAsync(Consumer<C> consumer) {
       C config = newConfig();
       consumer.accept(config);
       beforeApply(config);
       return applyAsync(config);
    }

    default Class<C> getConfigClass() {
       throw new UnsupportedOperationException("getConfigClass() not implemented");
    }

    @Override
    default C newConfig() {
       throw new UnsupportedOperationException("newConfig() not implemented");
    }

    default void beforeApply(C config) {
    }

    Predicate<ServerWebExchange> apply(C config);

    default AsyncPredicate<ServerWebExchange> applyAsync(C config) {
       return toAsyncPredicate(apply(config));
    }

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

}

自定义Vip路由断言工厂实现

package com.wemedia.gateway.config;

import jakarta.validation.constraints.NotEmpty;
import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
import org.springframework.cloud.gateway.handler.predicate.CookieRoutePredicateFactory;
import org.springframework.cloud.gateway.handler.predicate.GatewayPredicate;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.server.ServerWebExchange;

import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;

/**
 * 自定义Vip路由断言工厂
 */
@Component
public class VipRoutePredicateFactory extends AbstractRoutePredicateFactory<VipRoutePredicateFactory.Config> {

    public VipRoutePredicateFactory(){
        super(Config.class);
    }

    @Override
    public List<String> shortcutFieldOrder() {
        return Arrays.asList("param","value");
    }

    @Override
    public Predicate<ServerWebExchange> apply(Config config) {
        return new GatewayPredicate() {
            @Override
            public boolean test(ServerWebExchange serverWebExchange) {
                //localhost/search?q=hhh&user=jackma
                ServerHttpRequest request = serverWebExchange.getRequest();
                String first = request.getQueryParams().getFirst(config.param);
                return StringUtils.hasText(first)&&first.equals(config.value);
            }
        };
    }


    @Validated
    public static class Config {
        @NotEmpty
        private String value;
        @NotEmpty
        private String param;

        public String getParam() {
            return param;
        }

        public void setParam(String param) {
            this.param = param;
        }


        public String getValue() {
            return value;
        }

        public void setValue(String value) {
            this.value = value;
        }

    }
}

在这里插入代码片

配置Vip断言
在这里插入图片描述
3. Filter(过滤器)
在这里插入图片描述
3.1 rewritePath(路径重写)
在这里插入图片描述
在这里插入图片描述

  • 添加RewritePath过滤器,重写原先路径/readDb,在访问路径前面追加/api/order/readDb,不然网关无法直接访问/readDb;
  • 添加AddReponseHeader过滤器,给响应头增加参数X-Response-ABC,值为123

3.2 默认过滤器filter:
在这里插入图片描述
增加默认过滤器default-filters, 参数Add-ReponseHeader=X-Reponse-Abc ,值为123 给所有服务的相应头中

3.3 全局过滤器GlobalFilter

package com.wemedia.gateway.filter;

import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

/**
 * 实现响应时间全局过滤器
 */
@Component
@Slf4j
public class RTGlobalFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();

        String uri = request.getURI().toString();
        long start=System.currentTimeMillis();
        log.info("请求【{}】开始时间:{}",uri,start );
        //================以上是前置逻辑==============

        Mono<Void> filter = chain.filter(exchange).doFinally((result) -> {
            //================后置逻辑
            long end = System.currentTimeMillis();
            log.info("请求【{}】结束时间:{},耗时:{}ms", uri, end, end - start);
        });
        return filter;
    }

    @Override
    public int getOrder() {
        return 0;
    }
}

过滤器filter 列表:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
3.4 自定义过滤器工厂

关键源码分析

package org.springframework.cloud.gateway.filter.factory;
import reactor.core.publisher.Mono;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.web.server.ServerWebExchange;

import static org.springframework.cloud.gateway.support.GatewayToStringStyler.filterToStringCreator;

/**
 * @author Spencer Gibb
 */
public class AddResponseHeaderGatewayFilterFactory extends AbstractNameValueGatewayFilterFactory {

    @Override
    public GatewayFilter apply(NameValueConfig config) {
       return new GatewayFilter() {
          @Override
          public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
             return chain.filter(exchange).then(Mono.fromRunnable(() -> addHeader(exchange, config)));
          }

          @Override
          public String toString() {
             return filterToStringCreator(AddResponseHeaderGatewayFilterFactory.this)
                   .append(config.getName(), config.getValue()).toString();
          }
       };
    }

    void addHeader(ServerWebExchange exchange, NameValueConfig config) {
       final String value = ServerWebExchangeUtils.expand(exchange, config.getValue());
       HttpHeaders headers = exchange.getResponse().getHeaders();
       // if response has been commited, no more response headers will bee added.
       if (!exchange.getResponse().isCommitted()) {
          headers.add(config.getName(), value);
       }
    }

}

1.实现一次性token自定义过滤器工厂

package com.wemedia.gateway.filter;

import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.factory.AbstractNameValueGatewayFilterFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.UUID;

/**
 * 实现一次性令牌的自定义过滤器工场
 */
@Component
@Slf4j
public class OnceTokenGatewayFilterFactory extends AbstractNameValueGatewayFilterFactory {
    @Override
    public GatewayFilter apply(NameValueConfig config) {
        return new GatewayFilter() {
            @Override
            public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
                //每次响应之前,添加一个一次性令牌,支持uuid,jwt等格式

                return chain.filter(exchange).then(Mono.fromRunnable(()->{
                    ServerHttpResponse response = exchange.getResponse();
                    HttpHeaders headers = response.getHeaders();
                    String value = config.getValue();
                    if("uuid".equalsIgnoreCase(value)){
                        value = UUID.randomUUID().toString();
                    }

                    if("jwt".equalsIgnoreCase(value)){
                        value="";
                    }


                    headers.add(config.getName(),value);
                }));
            }
        };
    }
}

2.配置一次性token过滤器
在这里插入图片描述
3.访问api响应结果如下:
在这里插入图片描述
3.5 实现全局跨域
在这里插入图片描述

  • 解决单机跨域方法:直接在每个controller上增加注解@CrossOrigin

在这里插入图片描述

  • 在分布式系统上解决跨域问题,在gateway上统一处理跨域问题
    配置全局跨域:
    在这里插入图片描述
    运行结果如下,增加了跨域处理:
    在这里插入图片描述
6.参考资料
  • 网关 APISIX
  • 网关:shenyu https://shenyu.apache.org/zh/ java实现
  • springcloud gateway https://blog.csdn.net/weixin_44807758/article/details/130273943
  • Sentinel
    https://sentinelguard.io/zh-cn/
    https://sentinelguard.io/zh-cn/docs/introduction.html
    https://sentinelguard.io/zh-cn/docs/dashboard.html
    https://github.com/alibaba/Sentinel
  • 开源微服务框架:
    若依:https://doc.ruoyi.vip/
    https://github.com/zhoutaoo/SpringCloud
    https://cloud.tencent.com/developer/article/2391291
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值