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
     * {@code WebFilter} through the given {@link SoulPluginChain}.
     *
     * @param exchange the current server exchange
     * @param chain    provides a way to delegate to the next filter
     * @return {@code 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 {@linkplain 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的实现,最后讲了一下选择器的匹配,但是只讲了部分,后面继续深入讲一下匹配的代码。

猜你喜欢

转载自blog.csdn.net/zfy163520/article/details/112909108