SpringCloud Gateway——请求转发源码分析

SpringCloud Gateway——请求转发源码分析

1. 分享目的

SpringCloud Gateway功能很多,其中使用了非阻塞的WebFlux框架让人印象深刻,想学习这种WebFlux的,Gateway是一个很好的切入点。使用SpringCloud Gateway做为网关已经有很长一段时间了,也对其中的功能进行了扩展和增强,接下来对Gateway几个感兴趣的方面进行专门的分析。

2. 请求转发源码分析

1. 主流程

在这里插入图片描述

2. DispatcherHandler

Gateway是基于Spring WebFlux服务器的,当收到一个请求时,会到入口类DispatcherHandler.handle()

DispatcherHandler

private List<HandlerMapping> handlerMappings;

public Mono<Void> handle(ServerWebExchange exchange) {
    return this.handlerMappings == null ? this.createNotFoundError() : Flux.fromIterable(this.handlerMappings).concatMap((mapping) -> {
        return mapping.getHandler(exchange);
    }).next().switchIfEmpty(this.createNotFoundError()).flatMap((handler) -> {
        return this.invokeHandler(exchange, handler);
    }).flatMap((result) -> {
        return this.handleResult(exchange, result);
    });
}

该方法会调用HandlerMapping.getHandler().

3. RoutePredicateHandlerMapping

HandlerMappingGateway中的实现是RoutePredicateHandlerMapping,而RoutePredicateHandlerMapping继承自AbstractHandlerMapping

AbstractHandlerMapping

public Mono<Object> getHandler(ServerWebExchange exchange) {
    return this.getHandlerInternal(exchange).map((handler) -> {
        if (this.logger.isDebugEnabled()) {
            this.logger.debug(exchange.getLogPrefix() + "Mapped to " + handler);
        }

        ServerHttpRequest request = exchange.getRequest();
        if (this.hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
            CorsConfiguration config = this.corsConfigurationSource != null ? this.corsConfigurationSource.getCorsConfiguration(exchange) : null;
            CorsConfiguration handlerConfig = this.getCorsConfiguration(handler, exchange);
            config = config != null ? config.combine(handlerConfig) : handlerConfig;
            if (!this.corsProcessor.process(config, exchange) || CorsUtils.isPreFlightRequest(request)) {
                return REQUEST_HANDLED_HANDLER;
            }
        }

        return handler;
    });
}

protected abstract Mono<?> getHandlerInternal(ServerWebExchange var1);

getHandler()用到了模板模式,主体的逻辑在抽象方法getHandlerInternal()中,RoutePredicateHandlerMapping实现了该方法。

RoutePredicateHandlerMapping

private final FilteringWebHandler webHandler;

@Override
protected Mono<?> getHandlerInternal(ServerWebExchange exchange) {
    if (this.managementPortType == DIFFERENT && this.managementPort != null
        && exchange.getRequest().getURI().getPort() == this.managementPort) {
        return Mono.empty();
    }
    exchange.getAttributes().put(GATEWAY_HANDLER_MAPPER_ATTR, getSimpleName());

    // 查找对应的Route
    return lookupRoute(exchange)
        .flatMap((Function<Route, Mono<?>>) r -> {
            exchange.getAttributes().remove(GATEWAY_PREDICATE_ROUTE_ATTR);
            if (logger.isDebugEnabled()) {
                logger.debug(
                    "Mapping [" + getExchangeDesc(exchange) + "] to " + r);
            }

            // 将对应的Route做为一个属性放到exchange
            exchange.getAttributes().put(GATEWAY_ROUTE_ATTR, r);
            // 返回FilteringWebHandler
            return Mono.just(webHandler);
        }).switchIfEmpty(Mono.empty().then(Mono.fromRunnable(() -> {
        exchange.getAttributes().remove(GATEWAY_PREDICATE_ROUTE_ATTR);
        if (logger.isTraceEnabled()) {
            logger.trace("No RouteDefinition found for ["
                         + getExchangeDesc(exchange) + "]");
        }
    })));
}

/**
 * 查找符合条件的Route
 * @param exchange
 * @return
 */
protected Mono<Route> lookupRoute(ServerWebExchange exchange) {
    return this.routeLocator.getRoutes()
        // individually filter routes so that filterWhen error delaying is not a
        // problem
        .concatMap(route -> Mono.just(route).filterWhen(r -> {
            // add the current route we are testing
            exchange.getAttributes().put(GATEWAY_PREDICATE_ROUTE_ATTR, r.getId());
            // 执行谓词,是否符合
            return r.getPredicate().apply(exchange);
        })
                   .doOnError(e -> logger.error(
                       "Error applying predicate for route: " + route.getId(),
                       e))
                   .onErrorResume(e -> Mono.empty()))
        .next()
        .map(route -> {
            if (logger.isDebugEnabled()) {
                logger.debug("Route matched: " + route.getId());
            }
            validateRoute(route, exchange);
            return route;
        });
}

getHandlerInternal()逻辑:

  1. lookupRoute()根据谓词筛选符合条件的Route

  2. 将符合条件的Route做为exchange的一个属性;

  3. 返回FilteringWebHandler.

4. FilteringWebHandler

FilteringWebHandler的作用是根据Route构建过滤链,然后执行过滤器

FilteringWebHandler

@Override
public Mono<Void> handle(ServerWebExchange exchange) {
    // 从exchange属性中获取Route
    Route route = exchange.getRequiredAttribute(GATEWAY_ROUTE_ATTR);
    // Route中的过滤器
    List<GatewayFilter> gatewayFilters = route.getFilters();

    // 合并全局过滤器
    List<GatewayFilter> combined = new ArrayList<>(this.globalFilters);
    combined.addAll(gatewayFilters);
    // TODO: needed or cached?
    // 过滤器排序
    AnnotationAwareOrderComparator.sort(combined);

    if (logger.isDebugEnabled()) {
        logger.debug("Sorted gatewayFilterFactories: " + combined);
    }

    // 构建过滤器链,并执行过滤器
    return new DefaultGatewayFilterChain(combined).filter(exchange);
}

private static class DefaultGatewayFilterChain implements GatewayFilterChain {

    private final int index;

    private final List<GatewayFilter> filters;

    DefaultGatewayFilterChain(List<GatewayFilter> filters) {
        this.filters = filters;
        this.index = 0;
    }

    private DefaultGatewayFilterChain(DefaultGatewayFilterChain parent, int index) {
        this.filters = parent.getFilters();
        this.index = index;
    }

    public List<GatewayFilter> getFilters() {
        return filters;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange) {
        return Mono.defer(() -> {
            if (this.index < filters.size()) {
                GatewayFilter filter = filters.get(this.index);
                DefaultGatewayFilterChain chain = new DefaultGatewayFilterChain(this,
                                                                                this.index + 1);
                return filter.filter(exchange, chain);
            }
            else {
                return Mono.empty(); // complete
            }
        });
    }

}

DefaultGatewayFilterChain用到了责任链模式,前面有专门的文章对责任链进行分析。

5. NettyRoutingFilter

NettyRoutingFilter是个全局过滤器,作用是将请求转发到具体的服务,利用Netty中的HttpClient进行http转发。

NettyRoutingFilter

@Override
@SuppressWarnings("Duplicates")
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    URI requestUrl = exchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR);

    String scheme = requestUrl.getScheme();
    if (isAlreadyRouted(exchange)
        || (!"http".equals(scheme) && !"https".equals(scheme))) {
        return chain.filter(exchange);
    }
    setAlreadyRouted(exchange);

    ServerHttpRequest request = exchange.getRequest();

    final HttpMethod method = HttpMethod.valueOf(request.getMethodValue());
    final String url = requestUrl.toASCIIString();

    HttpHeaders filtered = filterRequest(getHeadersFilters(), exchange);

    final DefaultHttpHeaders httpHeaders = new DefaultHttpHeaders();
    filtered.forEach(httpHeaders::set);

    boolean preserveHost = exchange
        .getAttributeOrDefault(PRESERVE_HOST_HEADER_ATTRIBUTE, false);
    Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR);

    // 构建请求,使用HttpClient进行转发
    Flux<HttpClientResponse> responseFlux = getHttpClient(route, exchange)
        .headers(headers -> {
            headers.add(httpHeaders);
            // Will either be set below, or later by Netty
            headers.remove(HttpHeaders.HOST);
            if (preserveHost) {
                String host = request.getHeaders().getFirst(HttpHeaders.HOST);
                headers.add(HttpHeaders.HOST, host);
            }
        }).request(method).uri(url).send((req, nettyOutbound) -> {
        if (log.isTraceEnabled()) {
            nettyOutbound
                .withConnection(connection -> log.trace("outbound route: "
                                                        + connection.channel().id().asShortText()
                                                        + ", inbound: " + exchange.getLogPrefix()));
        }
        return nettyOutbound.send(request.getBody().map(this::getByteBuf));
    }).responseConnection((res, connection) -> {

        // Defer committing the response until all route filters have run
        // Put client response as ServerWebExchange attribute and write
        // response later NettyWriteResponseFilter
        exchange.getAttributes().put(CLIENT_RESPONSE_ATTR, res);
        exchange.getAttributes().put(CLIENT_RESPONSE_CONN_ATTR, connection);

        ServerHttpResponse response = exchange.getResponse();
        // put headers and status so filters can modify the response
        HttpHeaders headers = new HttpHeaders();

        res.responseHeaders().forEach(
            entry -> headers.add(entry.getKey(), entry.getValue()));

        String contentTypeValue = headers.getFirst(HttpHeaders.CONTENT_TYPE);
        if (StringUtils.hasLength(contentTypeValue)) {
            exchange.getAttributes().put(ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR,
                                         contentTypeValue);
        }

        setResponseStatus(res, response);

        // make sure headers filters run after setting status so it is
        // available in response
        HttpHeaders filteredResponseHeaders = HttpHeadersFilter.filter(
            getHeadersFilters(), headers, exchange, Type.RESPONSE);

        if (!filteredResponseHeaders
            .containsKey(HttpHeaders.TRANSFER_ENCODING)
            && filteredResponseHeaders
            .containsKey(HttpHeaders.CONTENT_LENGTH)) {
            // It is not valid to have both the transfer-encoding header and
            // the content-length header.
            // Remove the transfer-encoding header in the response if the
            // content-length header is present.
            response.getHeaders().remove(HttpHeaders.TRANSFER_ENCODING);
        }

        exchange.getAttributes().put(CLIENT_RESPONSE_HEADER_NAMES,
                                     filteredResponseHeaders.keySet());

        response.getHeaders().putAll(filteredResponseHeaders);

        return Mono.just(res);
    });

    Duration responseTimeout = getResponseTimeout(route);
    if (responseTimeout != null) {
        responseFlux = responseFlux
            .timeout(responseTimeout, Mono.error(new TimeoutException(
                "Response took longer than timeout: " + responseTimeout)))
            .onErrorMap(TimeoutException.class,
                        th -> new ResponseStatusException(HttpStatus.GATEWAY_TIMEOUT,
                                                          th.getMessage(), th));
    }

    return responseFlux.then(chain.filter(exchange));
}


好了,到站下车,未来可期…

欢迎同频共振的那一部分人

作者公众号:Tarzan写bug

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值