Soul API网关源码解析之SoulPlugin详解程序员

前言

上一篇文章中,我们提到了SoulWebHandler和WebHandler。WebHandler是WeFlux编程的核心接口,而SoulWebHandler是自定义实现的一个Handler。也简单的介绍了SoulWebHandler的作用与构建时机等等。当然在SoulWebHandler里最核心的是handle方法,关于这个方法的调用是在DefaultWebFilterChain.filter中,代码如下:

@Override 
public Mono<Void> filter(ServerWebExchange exchange) {
    
   return Mono.defer(() -> 
         this.currentFilter != null && this.chain != null ? 
               invokeFilter(this.currentFilter, this.chain, exchange) : 
               this.handler.handle(exchange)); 
} 

上面的代码中,首先判断当前的filter和chain是否为null,如果不为null,便执行invokeFilter方法,反之则调用handle方法,这里的this.handler便是WebHandler。后面有机会再介绍一下DefaultWebFilterChain。今天这篇是继续上一篇所说的,继续介绍plugin。

关于SoulPlugin

1、SoulPlugin的定义

这里的SoulPlugin是第一个接口,主要定义了execute方法,方法中的参数:ServerWebExchange、SoulPluginChain;然后就是getOrder方法,这个方法是为了获得plugin的顺序;接下来named方法,这个方法是为了获得插件名称;最后就是skip方法,这个方法是判断插件是否可以执行。代码如下:

public interface SoulPlugin {
    
    /** 
     * Process the Web request and (optionally) delegate to the next 
     * [email protected] WebFilter} through the given [email protected] SoulPluginChain}. 
     * 
     * @param exchange the current server exchange 
     * @param chain    provides a way to delegate to the next filter 
     * @return [email protected] Mono<Void>} to indicate when request processing is complete 
     */ 
    Mono<Void> execute(ServerWebExchange exchange, SoulPluginChain chain); 
    /** 
     * return plugin order . 
     * This attribute To determine the plugin execution order in the same type plugin. 
     * 
     * @return int order 
     */ 
    int getOrder(); 
    /** 
     * acquire plugin name. 
     * this is plugin name define you must Provide the right name. 
     * if you impl AbstractSoulPlugin this attribute not use. 
     * 
     * @return plugin name. 
     */ 
    default String named() {
    
        return ""; 
    } 
    /** 
     * plugin is execute. 
     * if return true this plugin can not execute. 
     * 
     * @param exchange the current server exchange 
     * @return default false. 
     */ 
    default Boolean skip(ServerWebExchange exchange) {
    
        return false; 
    } 
} 

2、SoulPlugin的实现

前面说了soulPlugin是一个接口,然后我们就可以看看关于这个接口的实现,可以发现这里的实现比较多,居然有31个之多。如图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HtiBe1fu-1611142903843)(https://uploader.shimo.im/f/aXtkOyd9LhRLYriZ.png!thumbnail?fileGuid=KR6RKQwVtrvKkTwd)]

这里笔者启动的是http插件,然后再DefaultSoulPluginChain的excute方法中打上断点,然后调用程序(见第二篇文章),便可以看到调用栈,如图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-87fWKSve-1611142903849)(https://uploader.shimo.im/f/QkIVH0kK8VLuCzzF.png!thumbnail?fileGuid=KR6RKQwVtrvKkTwd)]

这里的调用实现方是GlobalPlugin,那我们就以这个为例子,来看看里面的代码:

@Override 
public Mono<Void> execute(final ServerWebExchange exchange, final SoulPluginChain chain) {
    
    final ServerHttpRequest request = exchange.getRequest(); 
    final HttpHeaders headers = request.getHeaders(); 
    final String upgrade = headers.getFirst("Upgrade"); 
    SoulContext soulContext; 
    if (StringUtils.isBlank(upgrade) || !"websocket".equals(upgrade)) {
    
        soulContext = builder.build(exchange); 
    } else {
    
        final MultiValueMap<String, String> queryParams = request.getQueryParams(); 
        soulContext = transformMap(queryParams); 
    } 
    exchange.getAttributes().put(Constants.CONTEXT, soulContext); 
    return chain.execute(exchange); 
} 

上面这代代码的逻辑还是很清晰的,意思也很明确,但是这里有一个SoulContext的创建,这里的创建是基于SoulContextBuilder类的。那我们就来看看这个build方法的具体实现。

DefaultSoulContextBuilder的作用

1、DefaultSoulContextBuilder的实现

这是SoulContextBuilder接口的默认实现构建器,我们先来看看SoulContextBuilder:

public interface SoulContextBuilder {
    
    SoulContext build(ServerWebExchange exchange); 
} 

这个接口只是定义一个build方法,那就DefaultSoulContextBuilder的实现,代码如下:

public class DefaultSoulContextBuilder implements SoulContextBuilder {
 
@Override 
public SoulContext build(final ServerWebExchange exchange) {
 
final ServerHttpRequest request = exchange.getRequest(); 
String path = request.getURI().getPath(); 
MetaData metaData = MetaDataCache.getInstance().obtain(path); 
if (Objects.nonNull(metaData) && metaData.getEnabled()) {
 
exchange.getAttributes().put(Constants.META_DATA, metaData); 
} 
return transform(request, metaData); 
} 
/** 
* ServerHttpRequest transform RequestDTO . 
* 
* @param request [email protected] ServerHttpRequest} 
* @return RequestDTO request dto 
*/ 
private SoulContext transform(final ServerHttpRequest request, final MetaData metaData) {
 
final String appKey = request.getHeaders().getFirst(Constants.APP_KEY); 
final String sign = request.getHeaders().getFirst(Constants.SIGN); 
final String timestamp = request.getHeaders().getFirst(Constants.TIMESTAMP); 
SoulContext soulContext = new SoulContext(); 
String path = request.getURI().getPath(); 
soulContext.setPath(path); 
if (Objects.nonNull(metaData) && metaData.getEnabled()) {
 
if (RpcTypeEnum.SPRING_CLOUD.getName().equals(metaData.getRpcType())) {
 
setSoulContextByHttp(soulContext, path); 
soulContext.setRpcType(metaData.getRpcType()); 
} else if (RpcTypeEnum.DUBBO.getName().equals(metaData.getRpcType())) {
 
setSoulContextByDubbo(soulContext, metaData); 
} else if (RpcTypeEnum.SOFA.getName().equals(metaData.getRpcType())) {
 
setSoulContextBySofa(soulContext, metaData); 
} else if (RpcTypeEnum.TARS.getName().equals(metaData.getRpcType())) {
 
setSoulContextByTars(soulContext, metaData); 
} else {
 
setSoulContextByHttp(soulContext, path); 
soulContext.setRpcType(RpcTypeEnum.HTTP.getName()); 
} 
} else {
 
setSoulContextByHttp(soulContext, path); 
soulContext.setRpcType(RpcTypeEnum.HTTP.getName()); 
} 
soulContext.setAppKey(appKey); 
soulContext.setSign(sign); 
soulContext.setTimestamp(timestamp); 
soulContext.setStartDateTime(LocalDateTime.now()); 
Optional.ofNullable(request.getMethod()).ifPresent(httpMethod -> soulContext.setHttpMethod(httpMethod.name())); 
return soulContext; 
} 
private void setSoulContextByDubbo(final SoulContext soulContext, final MetaData metaData) {
 
soulContext.setModule(metaData.getAppName()); 
soulContext.setMethod(metaData.getServiceName()); 
soulContext.setRpcType(metaData.getRpcType()); 
soulContext.setContextPath(metaData.getContextPath()); 
} 
private void setSoulContextBySofa(final SoulContext soulContext, final MetaData metaData) {
 
soulContext.setModule(metaData.getAppName()); 
soulContext.setMethod(metaData.getServiceName()); 
soulContext.setRpcType(metaData.getRpcType()); 
soulContext.setContextPath(metaData.getContextPath()); 
} 
private void setSoulContextByTars(final SoulContext soulContext, final MetaData metaData) {
 
soulContext.setModule(metaData.getServiceName()); 
soulContext.setMethod(metaData.getMethodName()); 
soulContext.setRpcType(metaData.getRpcType()); 
soulContext.setContextPath(metaData.getContextPath()); 
} 
private void setSoulContextByHttp(final SoulContext soulContext, final String path) {
 
String contextPath = "/"; 
String[] splitList = StringUtils.split(path, "/"); 
if (splitList.length != 0) {
 
contextPath = contextPath.concat(splitList[0]); 
} 
String realUrl = path.substring(contextPath.length()); 
soulContext.setContextPath(contextPath); 
soulContext.setModule(contextPath); 
soulContext.setMethod(realUrl); 
soulContext.setRealUrl(realUrl); 
} 
} 

这个类比较长,但是因为主入口是build方法,那就从build入口,首先是获取ServerHttpRequest,然后是获取path,然后就是构建元数据,最后就是转换数据。在transform方法中主要是根据元数据的类型来调用相关了类,这里有spring cloud、dubbo、sofa、tras、http,不过这几个上下文设置的方法都大体类似。

2、DefaultSoulContextBuilder的构建方法

不过在执行完DefaultSoulContextBuilder中过的build方法后,便会进入到AbstractSoulPlugin中的execute方法。代码如下:

@Override 
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 last 
rule = 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); 
} 

这个方法首先是获取插件名称,然后获取插件数据,紧接着判断plugindata是否为null且是否已启用,两个条件有一个不符合便执行chain.execute(exchange)方法,否则进入判断。

3、匹配选择器

在if代码块中,首先根据插件名获取选择器集合,再根据选择器集合获取选择器数据,这里所调用的是matchSelector方法,代码如下:

private SelectorData matchSelector(final ServerWebExchange exchange, final Collection<SelectorData> selectors) {
 
return selectors.stream() 
.filter(selector -> selector.getEnabled() && filterSelector(selector, exchange)) 
.findFirst().orElse(null); 
} 
private Boolean filterSelector(final SelectorData selector, final ServerWebExchange exchange) {
 
if (selector.getType() == SelectorTypeEnum.CUSTOM_FLOW.getCode()) {
 
if (CollectionUtils.isEmpty(selector.getConditionList())) {
 
return false; 
} 
return MatchStrategyUtils.match(selector.getMatchMode(), selector.getConditionList(), exchange); 
} 
return true; 
} 

这段代码在过滤的时候,是根据选择器是否开启来进行过滤的,然后filterSelector方法,在这个方法中主要是调用MatchStrategyUtils的match方法是。那就来看看这个类。

4、MatchStrategyUtils.match

public class MatchStrategyUtils {
 
/** 
* Match boolean. 
* 
* @param strategy          the strategy 
* @param conditionDataList the condition data list 
* @param exchange          the exchange 
* @return the boolean 
*/ 
public static boolean match(final Integer strategy, final List<ConditionData> conditionDataList, final ServerWebExchange exchange) {
 
String matchMode = MatchModeEnum.getMatchModeByCode(strategy); 
MatchStrategy matchStrategy = ExtensionLoader.getExtensionLoader(MatchStrategy.class).getJoin(matchMode); 
return matchStrategy.match(conditionDataList, exchange); 
} 
} 

上面的代码意思是很明显的,这里根据策略来进行选择调用的。

总结

后面的代码调用还是比较复杂的,就先进行到这里,下一篇文章还会继续接着这个来进行进一步的剖析。

本篇文章首先从SoulPlugin的定义与实现来讲解的,然后简单的分析了一下DefaultSoulContextBuilder的实现,最后讲了一下选择器的匹配,但是只讲了部分,后面继续深入讲一下匹配的代码。

原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/1289.html

(0)
上一篇 2021年7月15日
下一篇 2021年7月15日

相关推荐

发表回复

登录后才能评论