Soul 源码阅读之选择器

org.dromara.soul.plugin.base.AbstractSoulPlugin

所有的插件入口类都继承了此抽象类,此类实现了 SoulPlugin 接口的 execute 方法并为其子类定义了 doExecute 方法来提供接入入口。而 execute 方法中包含了选择器和选择器规则的重要代码。

事实上,我们可以看到 soul 的每一款插件都有相应的选择器和规则的配置,以此来提供每一个插件的入口筛选,所以,soul 就把选择器前置到抽象类中,组装到整个插件调用链的上下文中。

execute 方法如下,在选择器相关的代码上做了注释:

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);
            if (CollectionUtils.isEmpty(selectors)) {
                return handleSelectorIsNull(pluginName, exchange, chain);
            }
            // 匹配选择器
            final SelectorData selectorData = matchSelector(exchange, selectors);
            if (Objects.isNull(selectorData)) {
                return handleSelectorIsNull(pluginName, exchange, chain);
            }
            selectorLog(selectorData, pluginName);
            final List<RuleData> rules = BaseDataCache.getInstance().obtainRuleData(selectorData.getId());
            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);
            }
            if (Objects.isNull(rule)) {
                return handleRuleIsNull(pluginName, exchange, chain);
            }
            ruleLog(rule, pluginName);
            return doExecute(exchange, chain, selectorData, rule);
        }
        return chain.execute(exchange);
    }

可以看到,在调用链的每一个节点执行之前,都先进行了选择器和其规则的匹配,而当我们继续查看 matchSelector,matchRule 这两个方法时,会发现他们最终都指向了同一个方法,即 MatchStrategyUtils.match(final Integer strategy, final List<ConditionData> conditionDataList, final ServerWebExchange exchange);

org.dromara.soul.plugin.base.utils.MatchStrategyUtils

这个类仅有一个方法,即 match 方法,其源码如下:

public static boolean match(final Integer strategy, final List<ConditionData> conditionDataList, final ServerWebExchange exchange) {
		// 获取用户配置的匹配方式
        String matchMode = MatchModeEnum.getMatchModeByCode(strategy);
        // 根据匹配方式获取相应的 MatchStrategy 实例
        MatchStrategy matchStrategy = ExtensionLoader.getExtensionLoader(MatchStrategy.class).getJoin(matchMode);
        // 进行下一层的匹配
        return matchStrategy.match(conditionDataList, exchange);
    }

其中 MatchStrategy 接口对应了下图红框框出的内容:
在这里插入图片描述
打开此接口所在的包 org.dromara.soul.plugin.base.condition.strategy 就能够看到相对应的两个实现类:AndMatchStrategy 和 OrMatchStrategy。
此外还有一个 AbstractMatchStrategy ,这个类提供了一个方法 buildRealData,为下一层匹配组装了上下文,代码如下:

String buildRealData(final ConditionData condition, final ServerWebExchange exchange) {
        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:
                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;
    }

这段代码对应了下图中红框部分:
在这里插入图片描述
也就是说,这个方法根据用户定义的条件种类,在上下文中寻找相应的信息,并处理成方便下一层匹配工具校验的形式。
接下来,我们深入到 AndMatchStrategy 和 OrMatchStrategy 类中,就可以看到它们都调用了同一个方法:OperatorJudgeFactory.judge(final ConditionData conditionData, final String realData);

org.dromara.soul.plugin.base.condition.judge.OperatorJudgeFactory

这个类维护了一个 Map,存放了四种匹配规则的实例,分别对应了 OperatorJudge 接口的四个实现,即下图红框部分的符号:
在这里插入图片描述

public static Boolean judge(final ConditionData conditionData, final String realData) {
        if (Objects.isNull(conditionData) || StringUtils.isBlank(realData)) {
            return false;
        }
        // 每次调用都会从 Map 中找到相应的匹配符号的实例,并进行校验
        return OPERATOR_JUDGE_MAP.get(conditionData.getOperator()).judge(conditionData, realData);
    }

至此,当前请求是否命中了某一插件的选择器规则就已经得到了结果。

waf 插件

waf 时类似防火墙的功能,可以做到屏蔽特定特征的流量的目的。
有了上文中的选择器匹配结果,实际上此插件的实现非常简单:

	@Override
    protected Mono<Void> doExecute(final ServerWebExchange exchange, final SoulPluginChain chain, final SelectorData selector, final RuleData rule) {
        WafConfig wafConfig = Singleton.INST.get(WafConfig.class);
        if (Objects.isNull(selector) && Objects.isNull(rule)) {
            if (WafModelEnum.BLACK.getName().equals(wafConfig.getModel())) {
                return chain.execute(exchange);
            }
            exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
            Object error = SoulResultWrap.error(403, Constants.REJECT_MSG, null);
            return WebFluxResultUtils.result(exchange, error);
        }
        String handle = rule.getHandle();
        WafHandle wafHandle = GsonUtils.getInstance().fromJson(handle, WafHandle.class);
        if (Objects.isNull(wafHandle) || StringUtils.isBlank(wafHandle.getPermission())) {
            log.error("waf handler can not configuration:{}", handle);
            return chain.execute(exchange);
        }
        // 如果选择器和选择器规则没有命中,都会在前两个 if 条件中过滤掉,也就是说能走到这一行的进程,都命中了规则,此时,只需要判断用户是否选择了拒绝此流量,再将用户配置的响应信息返回即可
        if (WafEnum.REJECT.getName().equals(wafHandle.getPermission())) {
            exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
            Object error = SoulResultWrap.error(Integer.parseInt(wafHandle.getStatusCode()), Constants.REJECT_MSG, null);
            return WebFluxResultUtils.result(exchange, error);
        }
        return chain.execute(exchange);
    }

小结

至此,我们看到了 soul 选择器和选择器规则的整体设计,并通过 waf 插件看到了这种设计带来的好处,一个合适的抽象层级可以使得代码更简洁,可复用性更高。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值