美文网首页
apache-shenyu 之plugin之match/cond

apache-shenyu 之plugin之match/cond

作者: 二哈_8fd0 | 来源:发表于2022-06-12 19:30 被阅读0次

(apache-shenyu 2.4.3版本)apache shenyu前身soul网关,是一款java中spring5新引入的project-reactor的webflux,reactor-netty等为基础实现的高性能网关,现已进入apache孵化器,作者yu199195 (xiaoyu) (github.com)

从当前本章开始,后续开始撸一遍shenyu的plugin逻辑,plugin是shenyu将网关的所有业务以及框架提供能力的抽象,将所有可以使用的能力都抽象为plugin,并提供了热插拔,自由扩展的功能,包括自定义代码扩展的热插拔(通过实现指定接口的jar放到指定路径)

本文来学习apache-shenyu的资源匹配逻辑

我们可以将指定的一个资源进行逻辑上的匹配


图2
图2

我们使用springCloud的后端服务,以及nacos注册中心,以及websocket同步数据,可以看apache-shenyu之启动项目老年人教程 - 简书 (jianshu.com)
可以看上图2,matchType 可以使用and/or代表下面conditions的组合方式

condition作用域
我们可以看到condition可以作用到资源上的很多选项
condition作用方式
作用方式也很多,然后后面就是我们可以填入对应值即可,就可以根据这些条件组合起来判断网关是否会转发到后端接口
下面来看看apache-shenyu 如何抽象这些逻辑
所在类包

先来看看 matchType MatchStrategy接口

matchType
@SPI
public interface MatchStrategy {
// 这是一个 spi 接口
    Boolean match(List<ConditionData> conditionDataList, ServerWebExchange exchange);
}

来看看策略工厂

public final class MatchStrategyFactory {
    private MatchStrategyFactory() {
    }
// 通过当前工厂,参数为策略选择参数,通过SPI选择一个子类
    public static MatchStrategy newInstance(final Integer strategy) {
        String matchMode = MatchModeEnum.getMatchModeByCode(strategy);
        return ExtensionLoader.getExtensionLoader(MatchStrategy.class).getJoin(matchMode);
    }
    // 为了使用方便,直接使用静态方法暴露 machType的业务方法,那么只需要传入策略参数内部选择子类执行
    public static boolean match(final Integer strategy, final List<ConditionData> conditionDataList, final ServerWebExchange exchange) {
        return newInstance(strategy).match(conditionDataList, exchange);
    }
}

就看一个 or 即可

@Join
public class OrMatchStrategy extends AbstractMatchStrategy implements MatchStrategy {

    @Override
    public Boolean match(final List<ConditionData> conditionDataList, final ServerWebExchange exchange) {
        return conditionDataList
                .stream()
// or其实就是  anyMatch   all就是 allMatch 哈哈哈
// 这里有两个 和他其逻辑组合的玩意,judge方法其实就是匹配方式,buildRealData就是要匹配的选项
                .anyMatch(condition -> PredicateJudgeFactory.judge(condition, buildRealData(condition, exchange)));
    }
}

再来看看匹配选项 ParameterData 接口

匹配选项
@SPI
public interface ParameterData {
// 还是一个 spi接口 ,默认返回空字符串
    default String builder(final String paramName, final ServerWebExchange exchange) {
        return "";
    }
}
public final class ParameterDataFactory {
    
    private ParameterDataFactory() {
    }
    // 还是通过策略工厂选择子类
    public static ParameterData newInstance(final String paramType) {
        return ExtensionLoader.getExtensionLoader(ParameterData.class).getJoin(paramType);
    }
// 还是通过静态方法直接暴露业务方法
    public static String builderData(final String paramType, final String paramName, final ServerWebExchange exchange) {
        return newInstance(paramType).builder(paramName, exchange);
    }
}

下面我分别看看 子类实现

@Join
public class CookieParameterData implements ParameterData {
    @Override
    public String builder(final String paramName, final ServerWebExchange exchange) {
// 获取cookie的某一个值 然后后续的 matchType 和 匹配方式由MatchStrategy和PredicateJudge来做
        List<HttpCookie> cookies = exchange.getRequest().getCookies().get(paramName);
        if (CollectionUtils.isEmpty(cookies)) {
            return "";
        }
        return cookies.get(0).getValue();
    }
}
// --------- domain---------------
@Join
public class DomainParameterData implements ParameterData {
    @Override
    public String builder(final String paramName, final ServerWebExchange exchange) {
        return exchange.getRequest().getURI().getHost();
    }
}
//----------------- header -----------
@Join
public class HeaderParameterData implements ParameterData {   
    @Override
    public String builder(final String paramName, final ServerWebExchange exchange) {
        List<String> headers = exchange.getRequest().getHeaders().get(paramName);
        if (CollectionUtils.isEmpty(headers)) {
            return "";
        } 
        return headers.get(0);
    }
}
//------------ host ----------
@Join
public class HostParameterData implements ParameterData {
   // 不同于domain 一个是当前host一个是 远程调用的 host,具体不去了解了 
    @Override
    public String builder(final String paramName, final ServerWebExchange exchange) {
        return HostAddressUtils.acquireHost(exchange);
    }
}
// 还有  ip,param, uri,getMethodValue(post 还是 get等等),
// --------- post parameter--------
@Join
public class PostParameterData implements ParameterData {
    // 这个比较特殊,是获取 shenyu封装的参数
    @Override
    public String builder(final String paramName, final ServerWebExchange exchange) {
        ShenyuContext shenyuContext = exchange.getAttribute(Constants.CONTEXT);
        return (String) ReflectUtils.getFieldValue(shenyuContext, paramName);
    }
}

然后我们再来看看 PredicateJudge 匹配方式

@SPI
@FunctionalInterface
public interface PredicateJudge {
// 是一个spi接口,并且是函数式接口,可以不用写一个类噢
    Boolean judge(ConditionData conditionData, String realData);
}

工厂类

public final class PredicateJudgeFactory {
    
    private PredicateJudgeFactory() {
    }
    
    public static PredicateJudge newInstance(final String operator) {
        return ExtensionLoader.getExtensionLoader(PredicateJudge.class).getJoin(processSpecialOperator(operator));
    }

    public static Boolean judge(final ConditionData conditionData, final String realData) {
        if (Objects.isNull(conditionData) || StringUtils.isBlank(realData)) {
            return false;
        }
        return newInstance(conditionData.getOperator()).judge(conditionData, realData);
    }

// 虽然前端使用 = 表示相等,后端需要用 equals
    private static String processSpecialOperator(final String operator) {
        return "=".equals(operator) ? "equals" : operator;
    }
}

实现类们不一一列举了

@Join
public class ContainsPredicateJudge implements PredicateJudge {

    @Override
    public Boolean judge(final ConditionData conditionData, final String realData) {
        return realData.contains(conditionData.getParamValue().trim());
    }
}

通过上述代码可以看出 shenyu通过三个spi接口 + 工厂将这三个业务参数的使用抽象出来,实际还通过spi指定了策略工厂key对应的实现类


spi指定

因为我们@ShenyuSpringCloudClient 一个注解是一个客户端维度,但是注到类上,我们可以通过不同的rule 1:1的映射到对应接口上,如果注到方法上,我们多个rule的规则就是可以有多个uri可以映射到这一个方法上,使用上是非常灵活的,就看我们如何使用

主要是区分 注解到类和方法上的区别即可。

我们来看看调用方

// 获取 一个@ShenyuSpringCloudClient 的所有rule
        List<RuleData> rules = BaseDataCache.getInstance().obtainRuleData(selectorData.getId());
            if (CollectionUtils.isEmpty(rules)) {
                return handleRuleIfNull(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);
            }
    private RuleData matchRule(final ServerWebExchange exchange, final Collection<RuleData> rules) {
// 可以看到一个@ShenyuSpringCloudClient 的所有规则,只会选择findFirst() 第一个 true的返回
        return rules.stream().filter(rule -> filterRule(rule, exchange)).findFirst().orElse(null);
    }

    private Boolean filterRule(final RuleData ruleData, final ServerWebExchange exchange) {
// 规则开关 && 上述我们的 MatchStrategy  PredicateJudge ParameterData 组合规则返回值
        return ruleData.getEnabled() && MatchStrategyFactory.match(ruleData.getMatchMode(), ruleData.getConditionDataList(), exchange);
    }

总结

  1. 多个维度的client注册及使用,整类以及单个方法
  2. rule动态开关
  3. 多个rule返回第一个true
  4. 抽象出MatchStrategy PredicateJudge ParameterData 以及SPI + 策略工厂 组合使用简化代码以及提高扩展性

相关文章

网友评论

      本文标题:apache-shenyu 之plugin之match/cond

      本文链接:https://www.haomeiwen.com/subject/oqxdmrtx.html