带着问题读源码-soul(2021-01-14)

下载编译

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

}

可以看到, 在这个方法中就进行了和本地服务完成交互。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值