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 请求的发送。