soul网关源码分析之一个请求的处理过程(Http篇)

目标

  • 分析一个被soul网关代理的http接口的处理流程
  • 总结

soul网关代理的http接口的处理流程

  • 准备工作

    启动soul-admin和soul网关,然后启动一个springMvc的rest服务,并且rest服务所暴露的接口有soul网关代理,具体的搭建网关和soul-admin流程可以参考往期文章或者直接去soul官网

  • 测试soul网关代理和直连

通过soul网关代理
在这里插入图片描述

直连
在这里插入图片描述

可以看到都能够获取到想要的结果,下面我们就看一下soul网关是怎样完成请求转发和响应的流程

  • 源码分析

    • 入手

    一开始,我们很懵逼,网关就集成了一些插件,并且yml配置也看不到任何暴露程序入口痕迹,只能看到配置了网关监听的端口号,这时候还是老办法,看pom文件吧,pom文件中插件的依赖我们不用关心,之前的文档也大概分析了某些插件,这时候我们主要关注的其实就是soul-spring-boot-starter-gateway这个依赖包,打开这个依赖包,我们可以看到一个类都没有,这是spring.factories就是我们的关注点了,可以看到其内容是provides: soul-web,我们就知道了,这是要家在soul-web这个包,话不多说,直接看soul-web

    • 还是那个soul-web

    我是直接打开的网关的依赖包,当然也可以直接看soul项目中的源码,可以看到这里边指出能够EnableAutoConfiguration的配置类是SoulConfiguration

在这里插入图片描述

    下面是SoulConfiguration的代码,可以看到初始化了SoulWebHandlerDispatcherHandlerPluginDataSubscriber等等,看到这里,我觉得请求的接收十有八九是SoulWebHandlerDispatcherHandler来处理的,因为PluginDataSubscriber之前我们分析过,它是用来订阅插件数据变更的通知的,其他初始化的Bean多是些过滤器暂时先不考虑。

@Configuration
@ComponentScan("org.dromara.soul")
@Import(value = {ErrorHandlerConfiguration.class, SoulExtConfiguration.class, SpringExtConfiguration.class})
@Slf4j
public class SoulConfiguration {
    
    /**
     * Init SoulWebHandler.
     *
     * @param plugins this plugins is All impl SoulPlugin.
     * @return {@linkplain SoulWebHandler}
     */
    @Bean("webHandler")
    public SoulWebHandler soulWebHandler(final ObjectProvider<List<SoulPlugin>> plugins) {
        List<SoulPlugin> pluginList = plugins.getIfAvailable(Collections::emptyList);
        final List<SoulPlugin> soulPlugins = pluginList.stream()
                .sorted(Comparator.comparingInt(SoulPlugin::getOrder)).collect(Collectors.toList());
        soulPlugins.forEach(soulPlugin -> log.info("load plugin:[{}] [{}]", soulPlugin.named(), soulPlugin.getClass().getName()));
        return new SoulWebHandler(soulPlugins);
    }
    
    /**
     * init dispatch handler.
     *
     * @return {@link DispatcherHandler}.
     */
    @Bean("dispatcherHandler")
    public DispatcherHandler dispatcherHandler() {
        return new DispatcherHandler();
    }
    
    /**
     * Plugin data subscriber plugin data subscriber.
     *
     * @param pluginDataHandlerList the plugin data handler list
     * @return the plugin data subscriber
     */
    @Bean
    public PluginDataSubscriber pluginDataSubscriber(final ObjectProvider<List<PluginDataHandler>> pluginDataHandlerList) {
        return new CommonPluginDataSubscriber(pluginDataHandlerList.getIfAvailable(Collections::emptyList));
    }
    
    ...
}

    话不多说,赶紧看看SoulWebHandlerDispatcherHandler代码,打开DispatcherHandler,发现这个东西是spring的,既然是spring的那肯定不是我们自己定义的用来接收请求的,但是又不知道他是干啥的,赶紧查一下,DispatcherHandler在spring webflux中相当于springmvc的DispatcherServlet,多一跳过它直接看SoulWebHandler

      • SoulWebHandler分析

    下面是SoulWebHandler部分代码,我们一部分一部分分析,首先该类实现了WebHandler接口,这又是个啥??赶紧查查,原来这是springwebflux定义的Web请求必要的一些处理行为的接口,其实在看这篇源码之前,我也简单了解下了springwebfulx,但其实还是不是很透彻,总之实现了这个接口并重写他的handle方法,那么handle方法就是用来处理Web请求的,可以看到在初始化SoulWebHandler的时候,将SoulPlugin列表也初始化了,并且还初始化了一个Scheduler,这里就不得不说到spring webfulx所配合的Reactor了,Scheduler其实就是Reactor并行运行的调度器,关于Reactor的知识,这里先不说了,免得跑题,接下来看handle方法,可以看出来它是将插件的列表作为参数初始化DefaultSoulPluginChain进行执行的。

在这里插入图片描述

    下面是DefaultSoulPluginChain代码,其核心的是execute,这里又是使用到了响应式编程Mono.defer,其中的逻辑是,遍历插件列表,插件满足skip条件时跳过,否则执行,这其实是一个责任链的设计模式,这其中skip方法设计的很有意思,是通过SoulContext来判断是否需要skip

在这里插入图片描述

我们跳过其他的插件,直接看处理请求转发的插件,以下是我配置的所有插件,先看名字再猜哪个是处理请求转发的嘛,因为是Http请求嘛,那就是WebClientPlugin插件呗。

在这里插入图片描述

      • WebClientPlugin分析

    直接上代码,代码比较多,这里核心的处理方法是execute,其他各个方法的作用在代码中已经标出来了,这里执行插件列表用到的责任链模式特别巧妙,当所有的插件执行完成,这一整个请求就执行完了,但请注意,插件的执行顺序也是固定的,比如限流插件肯定要在WebClientPlugin之前执行

@Slf4j
public class WebClientPlugin implements SoulPlugin {

    private final WebClient webClient;

    /**
     * Instantiates a new Web client plugin.
     *
     * @param webClient the web client
     */
    public WebClientPlugin(final WebClient webClient) {
        this.webClient = webClient;
    }

    @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("The request urlPath is {}, retryTimes is {}", urlPath, retryTimes);
        HttpMethod method = HttpMethod.valueOf(exchange.getRequest().getMethodValue());
        // 请求转发
        WebClient.RequestBodySpec requestBodySpec = webClient.method(method).uri(urlPath);
        return handleRequestBody(requestBodySpec, exchange, timeout, retryTimes, chain);
    }

    @Override
    public int getOrder() {
        return PluginEnum.DIVIDE.getCode() + 1;
    }

    @Override
    public String named() {
        return "webClient";
    }

    // 是否跳过该插件
    @Override
    public Boolean skip(final ServerWebExchange exchange) {
        final SoulContext soulContext = exchange.getAttribute(Constants.CONTEXT);
        assert soulContext != null;
        return !Objects.equals(RpcTypeEnum.HTTP.getName(), soulContext.getRpcType())
                && !Objects.equals(RpcTypeEnum.SPRING_CLOUD.getName(), soulContext.getRpcType());
    }

    // 构造Http请求头
    private MediaType buildMediaType(final ServerWebExchange exchange) {
        return MediaType.valueOf(Optional.ofNullable(exchange
                .getRequest()
                .getHeaders().getFirst(HttpHeaders.CONTENT_TYPE))
                .orElse(MediaType.APPLICATION_JSON_VALUE));
    }

    // 构造Http请求参数
    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));

    }

    // 执行下一个责任链
    private Mono<Void> doNext(final ClientResponse res, final ServerWebExchange exchange, final SoulPluginChain chain) {
        if (res.statusCode().is2xxSuccessful()) {
            exchange.getAttributes().put(Constants.CLIENT_RESPONSE_RESULT_TYPE, ResultEnum.SUCCESS.getName());
        } else {
            exchange.getAttributes().put(Constants.CLIENT_RESPONSE_RESULT_TYPE, ResultEnum.ERROR.getName());
        }
        exchange.getAttributes().put(Constants.CLIENT_RESPONSE_ATTR, res);
        return chain.execute(exchange);
    }
}

总结

    好了,一个Http请求的具体代理流程大致就分析完了,实际上还是留下了很多细节,比如说如何判断skip,插件列表的执行顺序,Reactor的API的具体说明等等,这些只能是慢慢分析了,有一点很重要,就是在分析这个流程时,一定要对Spring WebFluxReactor的API有一定的了解,不然理解起来很费劲。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Redick01

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值