Soul网关源码阅读(五)—— Soul网关初探

Soul网关源码阅读(五)—— Soul网关初探

概述

之前我们大概分析了数据同步的逻辑,知道了后端服务是怎么将api信息注册到admin,再由admin同步到soul网关的。

这一篇我们分析一下soul网关的基本结构,并大概介绍一下soul网关如何接受请求并且转发的。

因为我们的soul网关是并没有将提供的接口信息写死在配置文件的,所有提供的接口api都是通过admin同步过来,并动态加载到服务的。

所以这里我们主要需要思考一个问题:

  1. 从admin同步过来的后端服务的接口api,是如何注册到soul网关并形成api接口,使其服务化的?(类似Servlet的功能)

源码分析

接着上一篇的思路,我们分析了从admin同步过来的数据,存放在BaseDataCache类的一个Hashmap的缓存里面,那么soul网关必定需要加载这些缓存数据到它的服务框架中,使其具有接口服务的能力。

查看网关的启动项目Soul-bootstrap的代码,发现只有一个简单的启动了类,并没有web相关的业务逻辑。查看pom依赖,可以看到其依赖了一个soul-spring-boot-starter-gateway模块,该模块是一个spring-boot-starter类型的模块使其具有自动配置的功能。查看soul-spring-boot-starter-gateway的依赖,发现其依赖了soul-web模块,soul-web模块才是soul网关处理核心逻辑的地方。

soul-web模块才是soul网关的核心,包括处理插件、请求路由和转发等逻辑。

继续查看soul-web模块的依赖,发现其果然依赖了BaseDataCache所在的soul-plugin-base模块。

搜索BaseDataCache的使用引用,发现一个抽象类AbstractSoulPlugin中的execute方法对其进行了使用。

查看AbstractSoulPlugin的实现,会发现所有插件都实现了这个类,且都实现了doExecute这个抽象方法,我们先看一下这个方法的定义:

    /**
     * this is Template Method child has Implement your own logic.
     *
     * @param exchange exchange the current server exchange {@linkplain ServerWebExchange}
     * @param chain    chain the current chain  {@linkplain ServerWebExchange}
     * @param selector selector    {@linkplain SelectorData}
     * @param rule     rule    {@linkplain RuleData}
     * @return {@code Mono<Void>} to indicate when request handling is complete
     */
    protected abstract Mono<Void> doExecute(ServerWebExchange exchange, SoulPluginChain chain, SelectorData selector, RuleData rule);

这个接口说:这是一个模版方法,子类需要实现自己的逻辑,它几个参数也值得分析一下:

  • ServerWebExchange:SpringWebFlux中的类。

    官方的定义是:

    Contract for an HTTP request-response interaction. Provides access to the HTTP request and response and also exposes additional server-side processing related properties and features such as request attributes.

    HTTP请求-响应交互的协议。提供对HTTP请求和响应的访问。

  • SoulPluginChain:插件链表,只有一个execute方法,用于执行一个插件的逻辑,并流转到下一个插件,类似于WebFilter。

  • SelectorData:选择器数据

  • RuleData:规则数据

这里又学习了一个新的设计模式,是一个责任链模式,类似SpringMvc中的FilterChain,每个插件执行自己的业务逻辑,以此往下执行

我们选择Divide插件的实现来看一下:

@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);
    final List<DivideUpstream> upstreamList = UpstreamCacheManager.getInstance().findUpstreamListBySelectorId(selector.getId());
    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);
    }
    final String ip = Objects.requireNonNull(exchange.getRequest().getRemoteAddress()).getAddress().getHostAddress();
    DivideUpstream divideUpstream = LoadBalanceUtils.selector(upstreamList, ruleHandle.getLoadBalance(), ip);
    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
    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());
    return chain.execute(exchange);
}

通过postman调用对http的请求,通过打断点进行分许:
在这里插入图片描述

可以看到doExecute的核心逻辑是:

从缓存中获取上下文信息,包括http地址、端口号、路由、参数等一切信息,并封装到ServerWebExchange中(前面了解到他是一个协议,现在看来它就是一个请求交换器),然后执行execute方法,并传给下一个插件执行进行处理。

查看SoulWebHandler的execute方法:

 @Override
        public Mono<Void> execute(final ServerWebExchange exchange) {
    
    
            return Mono.defer(() -> {
    
    
                if (this.index < plugins.size()) {
    
    
                    SoulPlugin plugin = plugins.get(this.index++);
                    Boolean skip = plugin.skip(exchange);
                    if (skip) {
    
    
                        return this.execute(exchange);
                    }
                    return plugin.execute(exchange, this);
                }
                return Mono.empty();
            });
        }

他将当前执行的plugin与要执行的plugin类型做了比对,如果类型不同则跳过,如果相同则执行对应plugin实现的execute方法。逐步调试发现,最终执行的Plugin逻辑是WebClientResponsePlugin。

@Override
public Mono<Void> execute(final ServerWebExchange exchange, final SoulPluginChain chain) {
    
    
    return chain.execute(exchange).then(Mono.defer(() -> {
    
    
        ServerHttpResponse response = exchange.getResponse();
        ClientResponse clientResponse = exchange.getAttribute(Constants.CLIENT_RESPONSE_ATTR);
        if (Objects.isNull(clientResponse)
                || response.getStatusCode() == HttpStatus.BAD_GATEWAY
                || response.getStatusCode() == HttpStatus.INTERNAL_SERVER_ERROR) {
    
    
            Object error = SoulResultWrap.error(SoulResultEnum.SERVICE_RESULT_ERROR.getCode(), SoulResultEnum.SERVICE_RESULT_ERROR.getMsg(), null);
            return WebFluxResultUtils.result(exchange, error);
        }
        if (response.getStatusCode() == HttpStatus.GATEWAY_TIMEOUT) {
    
    
            Object error = SoulResultWrap.error(SoulResultEnum.SERVICE_TIMEOUT.getCode(), SoulResultEnum.SERVICE_TIMEOUT.getMsg(), null);
            return WebFluxResultUtils.result(exchange, error);
        }
        response.setStatusCode(clientResponse.statusCode());
        response.getCookies().putAll(clientResponse.cookies());
        response.getHeaders().putAll(clientResponse.headers().asHttpHeaders());
        return response.writeWith(clientResponse.body(BodyExtractors.toDataBuffers()));
    }));
}

从中获取到后端服务返回的数据结果。

由此我们有理由推测WebFlux的调用逻辑如下:

在这里插入图片描述

至此我们我们soul网关的调用逻辑大概试水了一下,还有很多地方不是很清楚的地方,后面再深入学习。

思考总结

今天接着初步分析了一下soul网关的转发调用逻辑,其核心是基于webFlux的web框架,这个技术框架是之前没有怎么接触过的,因此话费了很多时间去调试,还有很多概念不是很清楚。但是,经过今天的学习我们接触了WebFlux这种不同与Servlet的web框架,知道了其大致原理。明天我们将专门学习一下SpringWebFlux的原理,希望有所收获。

猜你喜欢

转载自blog.csdn.net/u010084384/article/details/112856520