Soul网关源码阅读02-体验http代理

本次将结合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)理解响应式?
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值