Soul API网关源码解析之MatchStrategy

前言

前面一篇文章讲到了SoulPlugin和插件选择器,主要是讲了SoulPlugin的定义与实现,以及选择器的匹配,但是并没有把选择器的匹配给写完。那么本篇文章就去这里继续进行解析。和大家进一步进行深入的分析。先看下代码:

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);
    }
}

选择器匹配

  • 获取匹配模式

从上面的代码中,我们可以看到首先是根据策略来获取匹配模式,那说到这里就要看看代码了,如下:

@RequiredArgsConstructor
@Getter
public enum MatchModeEnum {
    /**
     * And match mode enum.
     */
    AND(0, "and"),
    /**
     * Or match mode enum.
     */
    OR(1, "or");
    private final int code;
    private final String name;
    /**
     * get match mode name by code.
     *
     * @param code match mode code.
     * @return match mode name.
     */
    public static String getMatchModeByCode(final int code) {
        return Arrays.stream(MatchModeEnum.values())
                .filter(e -> e.code == code).findFirst()
                .orElse(MatchModeEnum.AND)
                .getName();
    }
}

其实这就是一个枚举类,这里的匹配模式是根据or、and的策略来进行匹配。所以这块代码还是很简单的。那我们匹配到了自己的模式后,那就进入到下一步了。

  • 获取ExtensionLoader

从最上面的代码中可以看到,先根据MatchStrategy.class来ExtensionLoader,然后根据获得到的ExtensionLoader来获取具体的匹配策略。说这么多,直接上代码:

ExtensionLoader.java

public static <T> ExtensionLoader<T> getExtensionLoader(final Class<T> clazz) {
    if (clazz == null) {
        throw new NullPointerException("extension clazz is null");
    }
    if (!clazz.isInterface()) {
        throw new IllegalArgumentException("extension clazz (" + clazz + ") is not interface!");
    }
    if (!clazz.isAnnotationPresent(SPI.class)) {
        throw new IllegalArgumentException("extension clazz (" + clazz + ") without @" + SPI.class + " Annotation");
    }
    ExtensionLoader<T> extensionLoader = (ExtensionLoader<T>) LOADERS.get(clazz);
    if (extensionLoader != null) {
        return extensionLoader;
    }
    LOADERS.putIfAbsent(clazz, new ExtensionLoader<>(clazz));
    return (ExtensionLoader<T>) LOADERS.get(clazz);
}

这段代码很简单,根据传入的class来到缓存中获取ExtensionLoader,如果能获取到则直接返回,没有就直接new 一个,然后返回去。

  • 正式匹配
@Join
public class AndMatchStrategy extends AbstractMatchStrategy implements MatchStrategy {
@Override
public Boolean match(final List<ConditionData> conditionDataList, final ServerWebExchange exchange) {
return conditionDataList
.stream()
.allMatch(condition -> OperatorJudgeFactory.judge(condition, buildRealData(condition, exchange)));
}
}
@Join
public class OrMatchStrategy extends AbstractMatchStrategy implements MatchStrategy {
    @Override
    public Boolean match(final List<ConditionData> conditionDataList, final ServerWebExchange exchange) {
        return conditionDataList
                .stream()
                .anyMatch(condition -> OperatorJudgeFactory.judge(condition, buildRealData(condition, exchange)));
    }
}

上面的代码就是两种策略的实现类,但是我们发现这两个都是继承了AndMatchStrategy,并且实现了MatchStrategy,我们还是要来看看MatchStrategy代码:

@SPI
public interface MatchStrategy {
    /**
     * this is condition match.
     *
     * @param conditionDataList condition list.
     * @param exchange          {@linkplain ServerWebExchange}
     * @return true is match , false is not match.
     */
    Boolean match(List<ConditionData> conditionDataList, ServerWebExchange exchange);
}

MatchStrategy接口类中只是定义了一个方法,但是你会发现这个接口上标注了@SPI 注解,这个先放一放,后面再关注。接着继续看看AbstractMatchStrategy抽象类的代码实现:

abstract class AbstractMatchStrategy {
    /**
     * Build real data string.
     *
     * @param condition the condition
     * @param exchange  the exchange
     * @return the string
     */
    String buildRealData(final ConditionData condition, final ServerWebExchange exchange) {
        String realData = "";
        ParamTypeEnum paramTypeEnum = ParamTypeEnum.getParamTypeEnumByName(condition.getParamType());
        switch (paramTypeEnum) {
            case HEADER:
                final HttpHeaders headers = exchange.getRequest().getHeaders();
                final List<String> list = headers.get(condition.getParamName());
                if (CollectionUtils.isEmpty(list)) {
                    return realData;
                }
                realData = Objects.requireNonNull(headers.get(condition.getParamName())).stream().findFirst().orElse("");
                break;
            case URI:
                realData = exchange.getRequest().getURI().getPath();
                break;
            case QUERY:
                final MultiValueMap<String, String> queryParams = exchange.getRequest().getQueryParams();
                realData = queryParams.getFirst(condition.getParamName());
                break;
            case HOST:
                realData = HostAddressUtils.acquireHost(exchange);
                break;
            case IP:
                realData = HostAddressUtils.acquireIp(exchange);
                break;
            case POST:
                final SoulContext soulContext = exchange.getAttribute(Constants.CONTEXT);
                realData = (String) ReflectUtils.getFieldValue(soulContext, condition.getParamName());
                break;
            default:
                break;
        }
        return realData;
    }
}

在这个抽象类中,它首先会根据参数类型获取paramTypeEnum,然后根据paramTypeEnum来判断调用那个流程。这里有HEADER、URI等等。这里代码先不细说,我们回到两个策略类的juge方法,这个方法是OperatorJudgeFactory类中的,代码如下:

public class OperatorJudgeFactory {
    private static final Map<String, OperatorJudge> OPERATOR_JUDGE_MAP = Maps.newHashMapWithExpectedSize(4);
    static {
        OPERATOR_JUDGE_MAP.put(OperatorEnum.EQ.getAlias(), new EqOperatorJudge());
        OPERATOR_JUDGE_MAP.put(OperatorEnum.MATCH.getAlias(), new MatchOperatorJudge());
        OPERATOR_JUDGE_MAP.put(OperatorEnum.LIKE.getAlias(), new LikeOperatorJudge());
        OPERATOR_JUDGE_MAP.put(OperatorEnum.REGEX.getAlias(), new RegExOperatorJudge());
    }
    /**
     * judge request realData has by pass.
     * @param conditionData condition data
     * @param realData       realData
     * @return is true pass   is false not pass
     */
    public static Boolean judge(final ConditionData conditionData, final String realData) {
        if (Objects.isNull(conditionData) || StringUtils.isBlank(realData)) {
            return false;
        }
        return OPERATOR_JUDGE_MAP.get(conditionData.getOperator()).judge(conditionData, realData);
    }
}

从代码中可以看出类中在一个缓存,这个缓存存着几种操作类型,然后你如果去看OPERATOR_JUDGE_MAP.get(conditionData.getOperator()).judge(conditionData, realData)这行代码,会发现,这个juge方法也有匹配的几个类。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xFnMzir3-1611235899159)(https://uploader.shimo.im/f/Hq3s6RQKjeYs1IKr.png!thumbnail?fileGuid=hgKJCgVPG68kYqVq)]

在这里,我们以MatchOperatorJudge类为例:

public class MatchOperatorJudge implements OperatorJudge {
    @Override
    public Boolean judge(final ConditionData conditionData, final String realData) {
        if (Objects.equals(ParamTypeEnum.URI.getName(), conditionData.getParamType())) {
            return PathMatchUtils.match(conditionData.getParamValue().trim(), realData);
        }
        return realData.contains(conditionData.getParamValue().trim());
    }
}

在这个类的juge方法中,有一这么一行代码:PathMatchUtils.match(conditionData.getParamValue().trim(), realData),这里是通过PathMatchUtils工具类的match来进行匹配的。代码如下:

public class PathMatchUtils {
    private static final AntPathMatcher MATCHER = new AntPathMatcher();
    /**
     * Match boolean.
     *
     * @param matchUrls the ignore urls
     * @param path      the path
     * @return the boolean
     */
    public static boolean match(final String matchUrls, final String path) {
        return Splitter.on(",").omitEmptyStrings().trimResults().splitToList(matchUrls).stream().anyMatch(url -> reg(url, path));
    }
    private static boolean reg(final String pattern, final String path) {
        return MATCHER.match(pattern, path);
    }
}

其实到这里,匹配的代码就差不多,那今天就到此结束了,后面准备从数据同步的角度来看看功能是如何实现的。

总结

这篇文章只是给上篇文章扫了个尾,主要从:

  • 获取匹配模式
  • 获取ExtensionLoader
  • 正式匹配

这几个几个方面来进行解析的。后面将要从其他角度来继续进行代码分析。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值