Soul网关源码探秘《六》 - 插件链

Soul网关源码探秘《六》 - 插件链

插件抽象类模版方法

今天主要探究某一个插件的处理流程。首先来看一下AbstractPlugin提供的模版方法execute中做了什么。

public Mono<Void> execute(final ServerWebExchange exchange, final SoulPluginChain chain) {
        /** 1. 获取插件名称 */
        String pluginName = named();
        /** 2. 从缓存中获取该插件的信息 */
        final PluginData pluginData = BaseDataCache.getInstance().obtainPluginData(pluginName);
        /** 3. 如果缓存中没有该插件信息或者该插件没有被开启,将流程转交至插件链中下一个插件 */
        if (pluginData != null && pluginData.getEnabled()) {
            /** 4. 从缓存中获取插件的所有选择器信息 */
            final Collection<SelectorData> selectors = BaseDataCache.getInstance().obtainSelectorData(pluginName);
            if (CollectionUtils.isEmpty(selectors)) {
                return handleSelectorIsNull(pluginName, exchange, chain);
            }
            /** 5. 为当前请求匹配相应的选择器 */
            final SelectorData selectorData = matchSelector(exchange, selectors);
            if (Objects.isNull(selectorData)) {
                return handleSelectorIsNull(pluginName, exchange, chain);
            }
            /** 6. 输出匹配成功的选择器日志信息 */
            selectorLog(selectorData, pluginName);
            /** 7. 从缓存中获取插件的所有规则 */
            final List<RuleData> rules = BaseDataCache.getInstance().obtainRuleData(selectorData.getId());
            if (CollectionUtils.isEmpty(rules)) {
                return handleRuleIsNull(pluginName, exchange, chain);
            }
            RuleData rule;
            /** 8. 为当前请求匹配相应的规则 */
            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);
            }
            /** 9. 输出匹配成功的规则日志信息 */
            ruleLog(rule, pluginName);
            /** 10. 执行插件中相应选择器和规则的具体逻辑 */
            return doExecute(exchange, chain, selectorData, rule);
        }
        return chain.execute(exchange);
    }

可以看到,在抽象类提供的模版方法中,为当前请求匹配了选择器和规则,并转交给插件实现类执行后面的逻辑。

插件的特有流程

AbstractPlugin中提供抽象方法doExecute给子类去实现各自特有的处理逻辑。

此次以DividePlugin为例来查看后续流程。

@Override
    protected Mono<Void> doExecute(final ServerWebExchange exchange, final SoulPluginChain chain, final SelectorData selector, final RuleData rule) {
        final SoulContext soulContext = exchange.getAttribute(Constants.CONTEXT);
        assert soulContext != null;
        final DivideRuleHandle ruleHandle = GsonUtils.getInstance().fromJson(rule.getHandle(), DivideRuleHandle.class);
        /** 1. 通过选择器ID获取所有提供服务的列表信息 */
        final List<DivideUpstream> upstreamList = UpstreamCacheManager.getInstance().findUpstreamListBySelectorId(selector.getId());
        /** 2. 如果提供服务的列表为空,则输出错误日志,并规范化 response 输出。PS:这段错误输出逻辑可以提取成新的方法 */
        if (CollectionUtils.isEmpty(upstreamList)) {
            log.error("divide upstream configuration error: {}", rule.toString());
            Object error = SoulResultWrap.error(SoulResultEnum.CANNOT_FIND_URL.getCode(), SoulResultEnum.CANNOT_FIND_URL.getMsg(), null);
            return WebFluxResultUtils.result(exchange, error);
        }
        /** 3. 获取请求的IP */
        final String ip = Objects.requireNonNull(exchange.getRequest().getRemoteAddress()).getAddress().getHostAddress();
        /** 4. 从规则中设置的负载均衡算法来计算真实去转发的服务 */
        DivideUpstream divideUpstream = LoadBalanceUtils.selector(upstreamList, ruleHandle.getLoadBalance(), ip);
        /** 5. 如果经过负载均衡后转发的服务为空,则输出错误日志,并规范化 response 输出。PS:这段错误输出逻辑依然可以提取成新的方法 */
        if (Objects.isNull(divideUpstream)) {
            log.error("divide has no upstream");
            Object error = SoulResultWrap.error(SoulResultEnum.CANNOT_FIND_URL.getCode(), SoulResultEnum.CANNOT_FIND_URL.getMsg(), null);
            return WebFluxResultUtils.result(exchange, error);
        }
        // set the http url
        /** 6. 生成真实请求的信息并设置到 exchange 中 */
        String domain = buildDomain(divideUpstream);
        String realURL = buildRealURL(domain, soulContext, exchange);
        exchange.getAttributes().put(Constants.HTTP_URL, realURL);
        // set the http timeout
        exchange.getAttributes().put(Constants.HTTP_TIME_OUT, ruleHandle.getTimeout());
        exchange.getAttributes().put(Constants.HTTP_RETRY, ruleHandle.getRetry());
        /** 7. 转发给插件链上的下一个插件继续进行处理 */
        return chain.execute(exchange);
    }

DividePlugin中通过负载均衡算法计算出实际要转发的服务,并生成了转发的服务信息设置到设置到 exchange 中。然后转发给插件链上的下一个插件继续进行处理。

exchange 中设置好真实转发的服务后,最终总得发送出去。通过假设性原则,查找soul-plugin-httpclient/src/main/java/org/dromara/soul/plugin/httpClient/查看,果然发现在WebClientPlugin里定义了它的 order

/** 意味着 WebClientPlugin 插件总会在 DividePlugin 之后执行 */
public int getOrder() {
        return PluginEnum.DIVIDE.getCode() + 1;
    }

WebClientPlugin 中的 execute 中,实际转发了请求。

public Mono<Void> execute(final ServerWebExchange exchange, final SoulPluginChain chain) {
        final SoulContext soulContext = exchange.getAttribute(Constants.CONTEXT);
        assert soulContext != null;
        /** 1. 获取请求地址 */
        String urlPath = exchange.getAttribute(Constants.HTTP_URL);
        // some other code
        }
        /** 2. 获取超市时间 */
        long timeout = (long) Optional.ofNullable(exchange.getAttribute(Constants.HTTP_TIME_OUT)).orElse(3000L);
        /** 3. 获取重试次数 */
        int retryTimes = (int) Optional.ofNullable(exchange.getAttribute(Constants.HTTP_RETRY)).orElse(0);
        log.info("The request urlPath is {}, retryTimes is {}", urlPath, retryTimes);
        // some other code
        /** 4. 实际发起请求 */
        return handleRequestBody(requestBodySpec, exchange, timeout, retryTimes, chain);
    }

可以看到,WebClientPlugin插件从 exchange 里取出实际请求地址、超时时间以及重试次数,并完成了 HTTP 请求的发送。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

rughru

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值