Soul网关源码阅读(五)—— Soul网关初探
概述
之前我们大概分析了数据同步的逻辑,知道了后端服务是怎么将api信息注册到admin,再由admin同步到soul网关的。
这一篇我们分析一下soul网关的基本结构,并大概介绍一下soul网关如何接受请求并且转发的。
因为我们的soul网关是并没有将提供的接口信息写死在配置文件的,所有提供的接口api都是通过admin同步过来,并动态加载到服务的。
所以这里我们主要需要思考一个问题:
- 从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的原理,希望有所收获。