本次将结合divde插件,发起http请求soul网关,体验http代理
1、启动服务
- soul-admin:启动网关管理后台,打开divde插件配置
- soul-bootstrap:启动网关入口
- soul-examples->soul-examples-http:启动http请求测试用例
2、通过soul网关进行访问:
- http://127.0.0.1:9195/http/order/findById?id=1
可以看到 soul bootstrap 日志打印如下信息。当前开启的插件是divide ,可以看到请求是通过 o.d.s.plugin.httpclient.WebClientPlugin 进行处理 “ you request,The resulting urlPath is :http://192.168.2.1:8188/order/findById?id=1”。
2021-01-14 18:18:07.155 INFO 24406 --- [-work-threads-1] o.d.soul.plugin.base.AbstractSoulPlugin : divide selector success match , selector name :/http
2021-01-14 18:18:07.156 INFO 24406 --- [-work-threads-1] o.d.soul.plugin.base.AbstractSoulPlugin : divide selector success match , selector name :/http/order/findById
2021-01-14 18:18:07.172 INFO 24406 --- [-work-threads-1] o.d.s.plugin.httpclient.WebClientPlugin : you request,The resulting urlPath is :http://192.168.2.1:8188/order/findById?id=1, retryTimes: 0
2021-01-14 18:18:07.404 WARN 24406 --- [-work-threads-1] io.netty.bootstrap.Bootstrap : Unknown channel option 'SO_TIMEOUT' for channel '[id: 0x941669fe]'
3、去源代码中找到类WebClientPlugin,尝试在excute()方法上打个断点,重新请求接口。请求确实由 soul-plugin-httpclient 插件进行处理。
由 excute() 方法的 SoulPluginChain 参数可以知道,Soul网关插件是通过链式调用,接下来将会执行插件链中的下一个插件。
4、源码具体执行过程
- 1)soul-web
org.dromara.soul.web.configuration.SoulConfiguration----> new SoulWebHandler()
// 处理请求的启动器
// org.dromara.soul.web.handler.SoulWebHandler
// 所有插件在此完成链式调用,这里的 Mono 用法没有看明白
@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();
});
}
- 2)soul-plugin-divide
// 进入 divide 插件进行执行
// org.dromara.soul.plugin.divide.DividePlugin.doExecute
@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);
}
- 3)soul-plugin-httpclient
// 方法执行后,将会进入 soul-examples-http 接口方法,说明在此处发起实际的接口调用。
// org.dromara.soul.plugin.httpclient.WebClientPlugin.execute
@Override
public Mono<Void> execute(final ServerWebExchange exchange, final SoulPluginChain chain) {
final SoulContext soulContext = exchange.getAttribute(Constants.CONTEXT);
assert soulContext != null;
String urlPath = exchange.getAttribute(Constants.HTTP_URL);
if (StringUtils.isEmpty(urlPath)) {
Object error = SoulResultWrap.error(SoulResultEnum.CANNOT_FIND_URL.getCode(), SoulResultEnum.CANNOT_FIND_URL.getMsg(), null);
return WebFluxResultUtils.result(exchange, error);
}
long timeout = (long) Optional.ofNullable(exchange.getAttribute(Constants.HTTP_TIME_OUT)).orElse(3000L);
int retryTimes = (int) Optional.ofNullable(exchange.getAttribute(Constants.HTTP_RETRY)).orElse(0);
log.info("you request,The resulting urlPath is :{}, retryTimes: {}", urlPath, retryTimes);
HttpMethod method = HttpMethod.valueOf(exchange.getRequest().getMethodValue());
WebClient.RequestBodySpec requestBodySpec = webClient.method(method).uri(urlPath);
return handleRequestBody(requestBodySpec, exchange, timeout, retryTimes, chain);
}
// 处理执行结果
// org.dromara.soul.plugin.httpclient.response.WebClientResponsePlugin.execute
@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()));
}));
}
通过断点调试注意到,不管是执行前的请求处理,还是执行后对执行结果的处理都会执行 org.dromara.soul.web.handler.SoulWebHandler.execute 方法。
5、记录一下debug执行过程:
总结:
通过代码执行过程了解到所有插件都是链式调用,通过插件开关进行过滤执行,不仅有请求的处理链还有请求结果的处理链。源码中使用了Reactor 框架,对这个不了解,比如源码中的Mono.defer 方法。后续需要补一下Java响应式编程相关知识,对理解代码执行会有帮助。debug一遍后感觉只是对整个执行流程有所了解,还有很多比较模糊地方没看明白,插件之间调来调去搞晕了。还有就是要结合Soul文档去看,对理解一些逻辑处理会有帮助,比如:数据配置原理,元数据概念等。
需要深入研究的一些知识点:
- 1)soul-admin 后台更新插件后,是如何通知 soul-bootstrap进行更新的?
- 2)soul-bootstrap 启动后,插件集合是如何加载到内存的?
- 3)各种插件之间的调用关系,请求执行前后还有filter,还有Handler?
- 4)发起请求到拿到请求结果进行处理之间是如何转换的?
- 5)理解响应式?