apache-shenyu 之plugin之match/condition資源匹配邏輯

(apache-shenyu 2.4.3版本)apache shenyu前身soul網(wǎng)關(guān)畔裕,是一款java中spring5新引入的project-reactor的webflux铣鹏,reactor-netty等為基礎(chǔ)實(shí)現(xiàn)的高性能網(wǎng)關(guān)挣跋,現(xiàn)已進(jìn)入apache孵化器汗唱,作者yu199195 (xiaoyu) (github.com)

從當(dāng)前本章開始垢油,后續(xù)開始擼一遍shenyu的plugin邏輯臊恋,plugin是shenyu將網(wǎng)關(guān)的所有業(yè)務(wù)以及框架提供能力的抽象怖侦,將所有可以使用的能力都抽象為plugin,并提供了熱插拔距帅,自由擴(kuò)展的功能右锨,包括自定義代碼擴(kuò)展的熱插拔(通過實(shí)現(xiàn)指定接口的jar放到指定路徑)

本文來學(xué)習(xí)apache-shenyu的資源匹配邏輯

我們可以將指定的一個(gè)資源進(jìn)行邏輯上的匹配


圖2

圖2

我們使用springCloud的后端服務(wù),以及nacos注冊(cè)中心锥债,以及websocket同步數(shù)據(jù)陡蝇,可以看apache-shenyu之啟動(dòng)項(xiàng)目老年人教程 - 簡(jiǎn)書 (jianshu.com)
可以看上圖2,matchType 可以使用and/or代表下面conditions的組合方式

condition作用域

我們可以看到condition可以作用到資源上的很多選項(xiàng)
condition作用方式

作用方式也很多哮肚,然后后面就是我們可以填入對(duì)應(yīng)值即可登夫,就可以根據(jù)這些條件組合起來判斷網(wǎng)關(guān)是否會(huì)轉(zhuǎn)發(fā)到后端接口

下面來看看apache-shenyu 如何抽象這些邏輯
所在類包

先來看看 matchType MatchStrategy接口

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

來看看策略工廠

public final class MatchStrategyFactory {
    private MatchStrategyFactory() {
    }
// 通過當(dāng)前工廠,參數(shù)為策略選擇參數(shù)允趟,通過SPI選擇一個(gè)子類
    public static MatchStrategy newInstance(final Integer strategy) {
        String matchMode = MatchModeEnum.getMatchModeByCode(strategy);
        return ExtensionLoader.getExtensionLoader(MatchStrategy.class).getJoin(matchMode);
    }
    // 為了使用方便恼策,直接使用靜態(tài)方法暴露 machType的業(yè)務(wù)方法,那么只需要傳入策略參數(shù)內(nèi)部選擇子類執(zhí)行
    public static boolean match(final Integer strategy, final List<ConditionData> conditionDataList, final ServerWebExchange exchange) {
        return newInstance(strategy).match(conditionDataList, exchange);
    }
}

就看一個(gè) or 即可

@Join
public class OrMatchStrategy extends AbstractMatchStrategy implements MatchStrategy {

    @Override
    public Boolean match(final List<ConditionData> conditionDataList, final ServerWebExchange exchange) {
        return conditionDataList
                .stream()
// or其實(shí)就是  anyMatch   all就是 allMatch 哈哈哈
// 這里有兩個(gè) 和他其邏輯組合的玩意潮剪,judge方法其實(shí)就是匹配方式涣楷,buildRealData就是要匹配的選項(xiàng)
                .anyMatch(condition -> PredicateJudgeFactory.judge(condition, buildRealData(condition, exchange)));
    }
}

再來看看匹配選項(xiàng) ParameterData 接口

匹配選項(xiàng)
@SPI
public interface ParameterData {
// 還是一個(gè) spi接口 ,默認(rèn)返回空字符串
    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);
    }
// 還是通過靜態(tài)方法直接暴露業(yè)務(wù)方法
    public static String builderData(final String paramType, final String paramName, final ServerWebExchange exchange) {
        return newInstance(paramType).builder(paramName, exchange);
    }
}

下面我分別看看 子類實(shí)現(xiàn)

@Join
public class CookieParameterData implements ParameterData {
    @Override
    public String builder(final String paramName, final ServerWebExchange exchange) {
// 獲取cookie的某一個(gè)值 然后后續(xù)的 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 一個(gè)是當(dāng)前host一個(gè)是 遠(yuǎn)程調(diào)用的 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 {
    // 這個(gè)比較特殊碳褒,是獲取 shenyu封裝的參數(shù)
    @Override
    public String builder(final String paramName, final ServerWebExchange exchange) {
        ShenyuContext shenyuContext = exchange.getAttribute(Constants.CONTEXT);
        return (String) ReflectUtils.getFieldValue(shenyuContext, paramName);
    }
}

然后我們?cè)賮砜纯?PredicateJudge 匹配方式

@SPI
@FunctionalInterface
public interface PredicateJudge {
// 是一個(gè)spi接口,并且是函數(shù)式接口看疗,可以不用寫一個(gè)類噢
    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;
    }
}

實(shí)現(xiàn)類們不一一列舉了

@Join
public class ContainsPredicateJudge implements PredicateJudge {

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

通過上述代碼可以看出 shenyu通過三個(gè)spi接口 + 工廠將這三個(gè)業(yè)務(wù)參數(shù)的使用抽象出來,實(shí)際還通過spi指定了策略工廠key對(duì)應(yīng)的實(shí)現(xiàn)類


spi指定

因?yàn)槲覀傽ShenyuSpringCloudClient 一個(gè)注解是一個(gè)客戶端維度两芳,但是注到類上摔寨,我們可以通過不同的rule 1:1的映射到對(duì)應(yīng)接口上,如果注到方法上怖辆,我們多個(gè)rule的規(guī)則就是可以有多個(gè)uri可以映射到這一個(gè)方法上是复,使用上是非常靈活的,就看我們?nèi)绾问褂?/p>

主要是區(qū)分 注解到類和方法上的區(qū)別即可竖螃。

我們來看看調(diào)用方

// 獲取 一個(gè)@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 {
// 一般使用這個(gè)方法
                rule = matchRule(exchange, rules);
            }
    private RuleData matchRule(final ServerWebExchange exchange, final Collection<RuleData> rules) {
// 可以看到一個(gè)@ShenyuSpringCloudClient 的所有規(guī)則淑廊,只會(huì)選擇findFirst() 第一個(gè) true的返回
        return rules.stream().filter(rule -> filterRule(rule, exchange)).findFirst().orElse(null);
    }

    private Boolean filterRule(final RuleData ruleData, final ServerWebExchange exchange) {
// 規(guī)則開關(guān) && 上述我們的 MatchStrategy  PredicateJudge ParameterData 組合規(guī)則返回值
        return ruleData.getEnabled() && MatchStrategyFactory.match(ruleData.getMatchMode(), ruleData.getConditionDataList(), exchange);
    }

總結(jié)

  1. 多個(gè)維度的client注冊(cè)及使用,整類以及單個(gè)方法
  2. rule動(dòng)態(tài)開關(guān)
  3. 多個(gè)rule返回第一個(gè)true
  4. 抽象出MatchStrategy PredicateJudge ParameterData 以及SPI + 策略工廠 組合使用簡(jiǎn)化代碼以及提高擴(kuò)展性
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末斑鼻,一起剝皮案震驚了整個(gè)濱河市蒋纬,隨后出現(xiàn)的幾起案子猎荠,更是在濱河造成了極大的恐慌坚弱,老刑警劉巖蜀备,帶你破解...
    沈念sama閱讀 216,692評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異荒叶,居然都是意外死亡碾阁,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,482評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門些楣,熙熙樓的掌柜王于貴愁眉苦臉地迎上來脂凶,“玉大人,你說我怎么就攤上這事愁茁〔锨眨” “怎么了?”我有些...
    開封第一講書人閱讀 162,995評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵鹅很,是天一觀的道長(zhǎng)嘶居。 經(jīng)常有香客問我,道長(zhǎng)促煮,這世上最難降的妖魔是什么邮屁? 我笑而不...
    開封第一講書人閱讀 58,223評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮菠齿,結(jié)果婚禮上佑吝,老公的妹妹穿的比我還像新娘。我一直安慰自己绳匀,他們只是感情好芋忿,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,245評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著襟士,像睡著了一般盗飒。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上陋桂,一...
    開封第一講書人閱讀 51,208評(píng)論 1 299
  • 那天逆趣,我揣著相機(jī)與錄音,去河邊找鬼嗜历。 笑死宣渗,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的梨州。 我是一名探鬼主播痕囱,決...
    沈念sama閱讀 40,091評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼暴匠!你這毒婦竟也來了鞍恢?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,929評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎帮掉,沒想到半個(gè)月后弦悉,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體隆夯,經(jīng)...
    沈念sama閱讀 45,346評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡涮阔,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,570評(píng)論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了坛猪。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片涩搓。...
    茶點(diǎn)故事閱讀 39,739評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡污秆,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出昧甘,到底是詐尸還是另有隱情良拼,我是刑警寧澤,帶...
    沈念sama閱讀 35,437評(píng)論 5 344
  • 正文 年R本政府宣布充边,位于F島的核電站将饺,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏痛黎。R本人自食惡果不足惜予弧,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,037評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望湖饱。 院中可真熱鬧掖蛤,春花似錦、人聲如沸井厌。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,677評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽仅仆。三九已至器赞,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間墓拜,已是汗流浹背港柜。 一陣腳步聲響...
    開封第一講書人閱讀 32,833評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留咳榜,地道東北人夏醉。 一個(gè)月前我還...
    沈念sama閱讀 47,760評(píng)論 2 369
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像涌韩,于是被迫代替她去往敵國(guó)和親畔柔。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,647評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容