当前位置: 代码迷 >> 综合 >> Soul 源码阅读之选择器
  详细解决方案

Soul 源码阅读之选择器

热度:75   发布时间:2023-12-15 21:34:27.0

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

	@Overrideprotected 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 插件看到了这种设计带来的好处,一个合适的抽象层级可以使得代码更简洁,可复用性更高。