soul 源码分析 —— 插件解析之选择器

本文详细解读了Soul网关中的选择器和规则功能,解释了它们在路由控制中的作用,如粗粒度路由断言(选择器)和细化路由过滤(规则)。理解选择器和规则的不同之处有助于优化流量管理和精确控制请求流程。

选择器定义

选择器是什么?

soul选择器就是网关的过滤器,所有的网关请求都会经过选择器功能进行路由断言,所有选择器匹配规则都通过后,才能进行下一步的请求。

soul网关还有一个规则的概念,规则也是实施网关请求的过滤。规则与选择器的条件设置基本一样。

那选择器和规则有什么不同?

选择器可以看成是粗粒度的路由断言,比如:/http/**

规则可以看成是进一步的路由断言,比如:/http/order/findById

可以理解为:选择器相当于是对流量的第一次筛选,规则就是最终的筛选。

选择器和规则都必须满足,网关请求才能进一步往下走。

选择器详解

在这里插入图片描述

选择器详解:

  • 名称:为你的选择器起一个容易分辨的名字
  • 类型:custom flow 是自定义流量。full flow 是全流量。自定义流量就是请求会走你下面的匹配方式与条件。全流量则不走。
  • 匹配方式:and 或者or 是指下面多个条件是按照and 还是or的方式来组合。
  • 条件:
    • uri:是指你根据uri的方式来筛选流量,match的方式支持模糊匹配(/**)
    • header:是指根据请求头里面的字段来筛选流量。
    • query:是指根据uri的查询条件来进行筛选流量。
    • ip:是指根据你请求的真实ip,来筛选流量。
    • host:是指根据你请求的真实host,来筛选流量。
    • post:建议不要使用。
    • 条件匹配:
      • match:模糊匹配,建议和uri条件搭配,支持 restful风格的匹配。(/test/**)
      • =:前后值相等,才能匹配。
      • regEx:正则匹配,表示前面一个值去匹配后面的正则表达式。
      • like:字符串模糊匹配。
  • 是否开启:打开才会生效
  • 打印日志:打开的时候,当匹配上的时候,会打印匹配日志。
  • 执行顺序:当多个选择器的时候,执行顺序小的优先执行。

选择器分析

选择器处理逻辑流程图:
在这里插入图片描述

1.选择器匹配开始处理

在AbstractSoulPlugin 类的 matchSelector(exchange, selectors),开始选择器匹配处理:

public abstract class AbstractSoulPlugin implements SoulPlugin {

    protected abstract Mono<Void> doExecute(ServerWebExchange exchange, SoulPluginChain chain, SelectorData selector, RuleData rule);

    @Override
    public Mono<Void> execute(final ServerWebExchange exchange, final SoulPluginChain chain) {
        // 获取当前插件的名称
        String pluginName = named();
        // 根据插件名获取插件数据
        final PluginData pluginData = BaseDataCache.getInstance().obtainPluginData(pluginName);
        // 如果插件数据信息不为空 并且 该插件启用
        if (pluginData != null && pluginData.getEnabled()) {
            // 根据插件名获取路由选择器数据
            final Collection<SelectorData> selectors = BaseDataCache.getInstance().obtainSelectorData(pluginName);
            // 如果不存在插件对应的选择器,并且插件名称是:divide、dubbo、springCloud;返回:can not match selector data: {插件}
            if (CollectionUtils.isEmpty(selectors)) {
                return handleSelectorIsNull(pluginName, exchange, chain);
            }
            // 匹配路由选择器条件
            final SelectorData selectorData = matchSelector(exchange, selectors);
            // 如果匹配不到对应的选择器,并且插件名称是:divide、dubbo、springCloud;返回:can not match selector data: {插件}
            if (Objects.isNull(selectorData)) {
                return handleSelectorIsNull(pluginName, exchange, chain);
            }
            // 路由对应选择器日志输出:{插件} selector success match , selector name :{选择器}
            selectorLog(selectorData, pluginName);
            // 根据插件id,获取路由选择器对应的规则
            final List<RuleData> rules = BaseDataCache.getInstance().obtainRuleData(selectorData.getId());
            // 如果获取路由选择器对应的规则为空,返回:can not match rule data: {插件}
            if (CollectionUtils.isEmpty(rules)) {
                return handleRuleIsNull(pluginName, exchange, chain);
            }
            RuleData rule;
            // 如果是全流控,不需要进一步的选择器限制规则
            if (selectorData.getType() == SelectorTypeEnum.FULL_FLOW.getCode()) {
                //get last
                rule = rules.get(rules.size() - 1);
            } else {
                rule = matchRule(exchange, rules);
            }
            // 如果匹配不到路由规则,返回:can not match rule data: {插件}
            if (Objects.isNull(rule)) {
                return handleRuleIsNull(pluginName, exchange, chain);
            }
            // 输出:{} selector success match , selector name :{}
            ruleLog(rule, pluginName);
            // 执行当前插件的doExecute方法
            return doExecute(exchange, chain, selectorData, rule);
        }
        // 插件不存在或者没有启用,调用下一个插件
        return chain.execute(exchange);
    }
    ...
}

通过方法matchSelector(exchange, selectors);,开始匹配选择器处理。

selectors: 为要过滤的选择器数组

matchSelector的具体代码如下:

private SelectorData matchSelector(final ServerWebExchange exchange, final Collection<SelectorData> selectors) {
        return selectors.stream()
                .filter(selector -> selector.getEnabled() && filterSelector(selector, exchange))
                .findFirst().orElse(null);
    }

    private Boolean filterSelector(final SelectorData selector, final ServerWebExchange exchange) {
        // 如果选择器是自定义流量
        if (selector.getType() == SelectorTypeEnum.CUSTOM_FLOW.getCode()) {
            // 如果不存在选择器条件,返回false
            if (CollectionUtils.isEmpty(selector.getConditionList())) {
                return false;
            }
            return MatchStrategyUtils.match(selector.getMatchMode(), selector.getConditionList(), exchange);
        }
        // 如果选择器是全流量,不做匹配控制,
        return true;
    }

遍历选择器数组,进行如下判断:

FULL_FLOW: 如果是全流量,即不做任何匹配条件限制
CUSTOM_FLOW:自定义流量,根据配置的条件匹配
2.选择器匹配方式选择
    public static boolean match(final Integer strategy, final List<ConditionData> conditionDataList, final ServerWebExchange exchange) {
        // 获取匹配方式:0:and;1:or
        String matchMode = MatchModeEnum.getMatchModeByCode(strategy);
        // 如果匹配方式为and,则加载AndMatchStrategy
        // 如果匹配方式为or,则加载OrMatchStrategy
        MatchStrategy matchStrategy = ExtensionLoader.getExtensionLoader(MatchStrategy.class).getJoin(matchMode);
        // 调用匹配方式的match方法,进行选择器的匹配
        return matchStrategy.match(conditionDataList, exchange);
    }
}

匹配方式,支持两种:

and: 所有的匹配条件都成立,选择器匹配才算成功

or: 只要一个匹配条件成立,选择器匹配就算成功

  • 通过spi的方式,进行匹配方式选择
  • 如果匹配方式为and,则加载AndMatchStrategy
  • 如果匹配方式为or,则加载OrMatchStrategy

3.匹配方式处理

    @Override
    public Boolean match(final List<ConditionData> conditionDataList, final ServerWebExchange exchange) {
        return conditionDataList
                .stream()
                .allMatch(condition -> OperatorJudgeFactory.judge(condition, buildRealData(condition, exchange)));
    }

如果匹配方式为:AndMatchStrategy

需要做两件事:

1)遍历每个匹配条件

2)获取请求的真实匹配条件值,接着进行操作符类的处理

4.获取真实的匹配条件值
String buildRealData(final ConditionData condition, final ServerWebExchange exchange) {
        // 假设当前condition为:ConditionData(paramType=uri, operator=match, paramName=/, paramValue=/test-http/**)
        String realData = "";
        // 获取请求类型
        ParamTypeEnum paramTypeEnum = ParamTypeEnum.getParamTypeEnumByName(condition.getParamType());
        switch (paramTypeEnum) {
            case HEADER:
                final HttpHeaders headers = exchange.getRequest().getHeaders();
                final List<String> list = headers.get(condition.getParamName());
                if (CollectionUtils.isEmpty(list)) {
                    return realData;
                }
                realData =                         Objects.requireNonNull(headers.get(condition.getParamName())).stream().findFirst().orElse("");
                break;
            case URI:
                // 如果请求:http://localhost:9195/http/order/findById?id=9
                // realData:/http/order/findById
                realData = exchange.getRequest().getURI().getPath();
                break;
            case QUERY:
                final MultiValueMap<String, String> queryParams = exchange.getRequest().getQueryParams();
                realData = queryParams.getFirst(condition.getParamName());
                break;
            case HOST:
                realData = HostAddressUtils.acquireHost(exchange);
                break;
            case IP:
                realData = HostAddressUtils.acquireIp(exchange);
                break;
            case POST:
                final SoulContext soulContext = exchange.getAttribute(Constants.CONTEXT);
                realData = (String) ReflectUtils.getFieldValue(soulContext, condition.getParamName());
                break;
            default:
                break;
        }
        return realData;
    }

匹配的条件包括:post、uri、query、host、ip、header

所有值的获取都是通过设置的匹配条件的key,去获取真实请求的值。

5.操作符类型的获取
public class OperatorJudgeFactory {

    private static final Map<String, OperatorJudge> OPERATOR_JUDGE_MAP = Maps.newHashMapWithExpectedSize(4);

    // 初始化四种判断规则:"="、"match"、"like"、"regEx"
    static {
        OPERATOR_JUDGE_MAP.put(OperatorEnum.EQ.getAlias(), new EqOperatorJudge());
        OPERATOR_JUDGE_MAP.put(OperatorEnum.MATCH.getAlias(), new MatchOperatorJudge());
        OPERATOR_JUDGE_MAP.put(OperatorEnum.LIKE.getAlias(), new LikeOperatorJudge());
        OPERATOR_JUDGE_MAP.put(OperatorEnum.REGEX.getAlias(), new RegExOperatorJudge());
    }

    /**
     * judge request realData has by pass.
     * @param conditionData condition data
     * @param realData       realData
     * @return is true pass   is false not pass
     */
    public static Boolean judge(final ConditionData conditionData, final String realData) {
        // eg:ConditionData(paramType=uri, operator=match, paramName=/, paramValue=/test-http/**)
        // eg:realData(/http/order/findById)
        if (Objects.isNull(conditionData) || StringUtils.isBlank(realData)) {
            return false;
        }
        // OPERATOR_JUDGE_MAP.get(conditionData.getOperator()): 生成操作符判断类
        // 操作符判断类为:EqOperatorJudge、MatchOperatorJudge、LikeOperatorJudge、RegExOperatorJudge
        return OPERATOR_JUDGE_MAP.get(conditionData.getOperator()).judge(conditionData, realData);
    }
}

通过用户设置的匹配条件操作符,获取对应的操作符类。

操作符支持:"="、“match”、“like”、“regEx”

通过OPERATOR_JUDGE_MAP数组,获取操作符实例

6.操作符类进行判断处理
public class MatchOperatorJudge implements OperatorJudge {

    // eg:ConditionData(paramType=uri, operator=match, paramName=/, paramValue=/test-http/**)
    // eg:realData(/http/order/findById)
    @Override
    public Boolean judge(final ConditionData conditionData, final String realData) {
        // 如果请求过滤类型为:uri,走下面的处理方式
        if (Objects.equals(ParamTypeEnum.URI.getName(), conditionData.getParamType())) {
            return PathMatchUtils.match(conditionData.getParamValue().trim(), realData);
        }
        return realData.contains(conditionData.getParamValue().trim());
    }
}

如果获取的操作符是"match",则进行匹配条件值与真实请求值的比较

  • 如果请求条件是uri,则进行特殊处理

    public static boolean match(final String matchUrls, final String path) {
        return Splitter.on(",").omitEmptyStrings().trimResults().splitToList(matchUrls).stream().anyMatch(url -> reg(url, path));
    }
    

    ​matchUrls:用户在后台配置的uri条件:比如:/test-http/**
    uri可以是多个,以逗号隔开,只要其中一个匹配上,即返回true

  • 如果是其它请求条件,即:post、query、host、ip、header,则进行contains方法处理即可

操作符处理完之后,匹配条件的结果也就完成了。

如果匹配方式是and:需要多个匹配的结果成立,才能返回匹配条件;

如果匹配方式是or:只需要一个匹配的结果成立,就能返回匹配条件;

至此,选择器匹配就完成了。

评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值