下载编译
git clone git@github.com:dromara/soul.git
cd soul
mvn clean package install -Dmaven.test.skip=true -Dmaven.javadoc.skip=true -Drat.skip=true -Dcheckstyle.skip=true
运行
1. 启动一个127.0.0.1:3306,用户名为root,密码为空的mysql实例,用于保存soul元数据
2. 使用idea打开maven项目soul
3. 在【soul-admin】模块下,找到启动类 org.dromara.soul.admin.SoulAdminBootstrap 并运行,默认监听的是9095端口
4. 在【soul-bootstrap】模块下,找到启动类 org.dromara.soul.bootstrap.SoulBootstrapApplication 并运行,默认监听的是9195端口
5. 使用浏览器打开 soul管理界面
6. 默认的用户名/密码是 admin/123456
使用方法
1. 启动一个本地服务, 采用默认8080端口
curl http://localhost:8080/message -d '{"body": "1"}' -X POST -H 'Content-Type: application/json'
success%
2. 在本地服务(SpringBoot)上添加soul依赖
(1) 添加pom依赖
<dependency>
<groupId>org.dromara</groupId>
<artifactId>soul-spring-boot-starter-client-springmvc</artifactId>
<version>2.2.1</version>
</dependency>
(2)添加yml,注册本地服务到soul网关
soul:
http:
# soul 管理界面入口
adminUrl: http://127.0.0.1:9095
# 本地服务监听的端口
port: 8080
# soul 网关代理的uri前缀
contextPath: /http
# 本地服务的名称
appName: http
# 代理全部请求, 不需要配置 @org.dromara.soul.client.springmvc.annotation.SoulSpringMvcClient
full: true
(3)验证网关转发功能
curl http://localhost:9195/http/message -d '{"body": "1"}' -X POST -H 'Content-Type: application/json'
success%
带着问题看源码系列-如何完成HTTP请求转发
通过观察管理界面,猜想完成转发的是一个名为divide插件。在classpath里搜索 DividePlugin,在soul-plugin-divide模块中找到类 org.dromara.soul.plugin.divide.DividePlugin
在 org.dromara.soul.plugin.divide.DividePlugin#doExecute(ServerWebExchange, SoulPluginChain, SelectorData, RuleData) 方法中打一个断点
再次访问网关转发功能, 被断点拦截
final SoulContext soulContext = exchange.getAttribute(Constants.CONTEXT); // 获取soul上下文
assert soulContext != null; // 断言 soul上下文不为空
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);
上面的代码只是解析了需要分发的url设置到exange的上下文中,真正进行请求的代码还需要重新找......
通过对 Constants.HTTP_URL 进行引用查找,找到还有 WebClientPlugin(org.dromara.soul.plugin.httpclient.WebClientPlugin) 和 NettyHttpClientPlugin(org.dromara.soul.plugin.httpclient.NettyHttpClientPlugin) 引用了这个常量
查看这两个类,发现都有进行HTTP请求发送和返回的操作,但 NettyHttpClientPlugin 的返回值不再进行chain调用,猜想请求会先走 WebClientPlugin, 再走 NettyHttpClientPlugin
在两个类中各打个断点,发送请求......
请求走了 WebClientPlugin 但没有走 NettyHttpClientPlugin,猜想有误。仔细读 org.dromara.soul.plugin.httpclient.WebClientPlugin#handleRequestBody 方法
private Mono<Void> handleRequestBody(final WebClient.RequestBodySpec requestBodySpec,
final ServerWebExchange exchange,
final long timeout,
final int retryTimes,
final SoulPluginChain chain) {
return requestBodySpec.headers(httpHeaders -> {
httpHeaders.addAll(exchange.getRequest().getHeaders());
httpHeaders.remove(HttpHeaders.HOST);
})
.contentType(buildMediaType(exchange))
.body(BodyInserters.fromDataBuffers(exchange.getRequest().getBody()))
.exchange() // 这里定义了交互的行为
.doOnError(e -> log.error(e.getMessage()))
.timeout(Duration.ofMillis(timeout))
.retryWhen(Retry.onlyIf(x -> x.exception() instanceof ConnectTimeoutException)
.retryMax(retryTimes)
.backoff(Backoff.exponential(Duration.ofMillis(200), Duration.ofSeconds(20), 2, true)))
.flatMap(e -> doNext(e, exchange, chain));
}
可以看到, 在这个方法中就进行了和本地服务完成交互。