Gateway源码备忘录

简介

在日常开发中,我们经常会用到Gateway这个组件,但是我们只是做到会用是远远不够的,还得了解这个组件的执行原理,该文章只是自己用来记录一些关键的代码,可能写的不是很通俗易懂,望请见谅

下面,从一个请求到达网关开始,了解这个请求是如何被转发到我们的服务里的

先来看一个简单的一个Route配置
spring:
  cloud:
      routes:
        # 这个是我们配置的网关的ID
        - id: cms-system
        # 这个是配置指的是我们要转发到哪个服务
          uri: lb://cms-system
        # 这个是谓语配置,用来匹配指定的请求  
          predicates:
            - Path=/dev-api/cms/**
        # 过滤器,代表转发的时候,截取URI的 cms 部分  
          filters:
            - StripPrefix=1

上面这个路由的yml配置文件,是如何被Gateway识别的呢?这个问题可以先在心里打个问号。
我们先在浏览器输入一个地址

http://127.0.0.1/dev-api/cms/f/cms/media/getMediaContentDetailByFile?id=103

这个请求最先到达网关的 RoutePredicateHandlerMapping 的 getHandlerInternal 方法
通过 this.lookupRoute 来匹配我们的请求,然后得到对应的Route

 protected Mono<?> getHandlerInternal(ServerWebExchange exchange) {
  			// ... 省略部分代码
  			// 通过 lookupRoute 来匹配我们的请求 
            return this.lookupRoute(exchange).flatMap((r) -> {
                exchange.getAttributes().remove(ServerWebExchangeUtils.GATEWAY_PREDICATE_ROUTE_ATTR);
                if (this.logger.isDebugEnabled()) {
                    this.logger.debug("Mapping [" + this.getExchangeDesc(exchange) + "] to " + r);
                }

                exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR, r);
                return Mono.just(this.webHandler);
            }).switchIfEmpty(Mono.empty().then(Mono.fromRunnable(() -> {
                exchange.getAttributes().remove(ServerWebExchangeUtils.GATEWAY_PREDICATE_ROUTE_ATTR);
                if (this.logger.isTraceEnabled()) {
                    this.logger.trace("No RouteDefinition found for [" + this.getExchangeDesc(exchange) + "]");
                }

            })));
        }
    }

我们看 lookupRoute 的实现
通过 this.routeLocator.getRoutes() 获取所有的 Route
然后通过 route.getPredicate().apply(exchange) 返回与 /dev-api/cms/** 匹配的 Route

protected Mono<Route> lookupRoute(ServerWebExchange exchange) {
        return this.routeLocator.getRoutes().concatMap((route) -> {
            return Mono.just(route).filterWhen((r) -> {
				..省略部分代码
				返回 与 /dev-api/cms/ 匹配的 Route 
                return (Publisher)r.getPredicate().apply(exchange);
            }).doOnError((e) -> {
                this.logger.error("Error applying predicate for route: " + route.getId(), e);
            }).onErrorResume((e) -> {
                return Mono.empty();
            });
        })
    }
	...省略部分代码

那么 this.routeLocator.getRoutes() 是如何得到我们在 yml 配置的 Route 呢?
routeLocator 又是从哪里传入的呢?
通过 GatewayAutoConfiguration 这个配置类,可以看到 this.routeLocator 是通过
RoutePredicateHandlerMapping 的构造方法注入的
而 RouteLocator 的实现类是 RouteDefinitionRouteLocator 我们看他的 getRoutes()
方法

 public Flux<Route> getRoutes() {
 可以看到 routes  是通过 遍历 routeDefinitionLocator.getRouteDefinitions() 返回的
        Flux<Route> routes = this.routeDefinitionLocator.getRouteDefinitions().map(this::convertToRoute);
        if (!this.gatewayProperties.isFailOnRouteDefinitionError()) {
            routes = routes.onErrorContinue((error, obj) -> {
                if (this.logger.isWarnEnabled()) {
                    this.logger.warn("RouteDefinition id " + ((RouteDefinition)obj).getId() + " will be ignored. Definition has invalid configs, " + error.getMessage());
                }

            });
        }
        ...省略部分代码
}

private Route convertToRoute(RouteDefinition routeDefinition) {
        AsyncPredicate<ServerWebExchange> predicate = this.combinePredicates(routeDefinition);
        List<GatewayFilter> gatewayFilters = this.getFilters(routeDefinition);
        return ((AsyncBuilder)Route.async(routeDefinition).asyncPredicate(predicate).replaceFilters(gatewayFilters)).build();
    }        

可以看到 routes 是通过 遍历 routeDefinitionLocator.getRouteDefinitions() 获取所有 RouteDefinition 然后调用 convertToRoute 转换成 Route
那么 RouteDefinitionRouteDefinitionLocator 是干什么用的

RouteDefinitionLocator 是用来加载 RouteDefinition 的,从yml或者内存…等。有兴趣的小伙伴可以看看他们的具体实现类。
这里主要讲,RouteDefinition
RouteDefinition,见名知意这个是路由的定义,这个类和我们yml里配置 Route 字段是 对应的

public class RouteDefinition {
    private String id;
    @NotEmpty
    @Valid
    private List<PredicateDefinition> predicates = new ArrayList();
    @Valid
    private List<FilterDefinition> filters = new ArrayList();
    @NotNull
    private URI uri;
    private Map<String, Object> metadata = new HashMap();
    private int order = 0;

我们知道yml中的配置,会被转换成一个个的 RouteDefinition 对象
那么这个对象中的字段都是干啥用的呢

  • id 就是路由的ID
  • predicates 谓语集合
  • filters 过滤器集合
  • uri uri
  • order 排序优先级

上面说到的 通过 convertToRoute 函数 把 RouteDefinition 转换成 Route 对象
有一个关键的方法 this.combinePredicates 和 this.getFilters
这里主要说一下 combinePredicates 这个方法 getFilters 的原理是一样的
通过 PredicateDefinition 的 name 获取 RoutePredicateFactory 的实现 比如 PathRoutePredicateFactory


private AsyncPredicate<ServerWebExchange> combinePredicates(RouteDefinition routeDefinition) {
        List<PredicateDefinition> predicates = routeDefinition.getPredicates();
        if (predicates != null && !predicates.isEmpty()) {
            AsyncPredicate<ServerWebExchange> predicate = this.lookup(routeDefinition, (PredicateDefinition)predicates.get(0));
	 	...省略部分代码
    }
private AsyncPredicate<ServerWebExchange> lookup(RouteDefinition route, PredicateDefinition predicate) {
        RoutePredicateFactory<Object> factory = (RoutePredicateFactory)this.predicates.get(predicate.getName());
  
        Object config = ((ConfigurableBuilder)((ConfigurableBuilder)((ConfigurableBuilder)this.configurationService.with(factory).name(predicate.getName())).properties(predicate.getArgs())).eventFunction((bound, properties) -> {
                return new PredicateArgsEvent(this, route.getId(), properties);
            })).bind();
            return factory.applyAsync(config);
        }
    }

然后接着 RoutePredicateHandlerMapping 的 getHandlerInternal 方法
继续说,匹配到 Route 之后,返回一个 webHandler
这个 webHandler 是通过构造函数传入的 实现类是
FilteringWebHandler

 protected Mono<?> getHandlerInternal(ServerWebExchange exchange) {
  			// ... 省略部分代码
  			// 通过 lookupRoute 来匹配我们的请求 
            return this.lookupRoute(exchange).flatMap((r) -> {
          
                exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR, r);
                return Mono.just(this.webHandler);
            })
    }

我们看 FilteringWebHandler 干了什么
主要看他的 handle 方法
通过 getRequiredAttribute 获取到们在上面传入的 Route对象
然后 把 globalFilters 和 路由自己的 gatewayFilters
经过排序之后 组装成一个 GatewayFilterChain
然后挨个执行过滤器中的逻辑


public Mono<Void> handle(ServerWebExchange exchange) {
        Route route = (Route)exchange.getRequiredAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
        List<GatewayFilter> gatewayFilters = route.getFilters();
        List<GatewayFilter> combined = new ArrayList(this.globalFilters);
        combined.addAll(gatewayFilters);
        AnnotationAwareOrderComparator.sort(combined);
        if (logger.isDebugEnabled()) {
            logger.debug("Sorted gatewayFilterFactories: " + combined);
        }

        return (new FilteringWebHandler.DefaultGatewayFilterChain(combined)).filter(exchange);
    }

最终由 NettyRoutingFilter 向对应的服务发送请求

public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        URI requestUrl = (URI)exchange.getRequiredAttribute(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR);
        String scheme = requestUrl.getScheme();
        if (!ServerWebExchangeUtils.isAlreadyRouted(exchange) && ("http".equals(scheme) || "https".equals(scheme))) {
            ServerWebExchangeUtils.setAlreadyRouted(exchange);
            ServerHttpRequest request = exchange.getRequest();
            HttpMethod method = HttpMethod.valueOf(request.getMethodValue());
            String url = requestUrl.toASCIIString();
            HttpHeaders filtered = HttpHeadersFilter.filterRequest(this.getHeadersFilters(), exchange);
            DefaultHttpHeaders httpHeaders = new DefaultHttpHeaders();
            filtered.forEach(httpHeaders::set);
            boolean preserveHost = (Boolean)exchange.getAttributeOrDefault(ServerWebExchangeUtils.PRESERVE_HOST_HEADER_ATTRIBUTE, false);
            Route route = (Route)exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
            Flux<HttpClientResponse> responseFlux = ((RequestSender)this.getHttpClient(route, exchange).headers((headers) -> {
                headers.add(httpHeaders);
                headers.remove("Host");
                if (preserveHost) {
                    String host = request.getHeaders().getFirst("Host");
                    headers.add("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) -> {
                exchange.getAttributes().put(ServerWebExchangeUtils.CLIENT_RESPONSE_ATTR, res);
                exchange.getAttributes().put(ServerWebExchangeUtils.CLIENT_RESPONSE_CONN_ATTR, connection);
                ServerHttpResponse response = exchange.getResponse();
                HttpHeaders headers = new HttpHeaders();
                res.responseHeaders().forEach((entry) -> {
                    headers.add((String)entry.getKey(), (String)entry.getValue());
                });
                String contentTypeValue = headers.getFirst("Content-Type");
                if (StringUtils.hasLength(contentTypeValue)) {
                    exchange.getAttributes().put("original_response_content_type", contentTypeValue);
                }

                this.setResponseStatus(res, response);
                HttpHeaders filteredResponseHeaders = HttpHeadersFilter.filter(this.getHeadersFilters(), headers, exchange, Type.RESPONSE);
                if (!filteredResponseHeaders.containsKey("Transfer-Encoding") && filteredResponseHeaders.containsKey("Content-Length")) {
                    response.getHeaders().remove("Transfer-Encoding");
                }

                exchange.getAttributes().put(ServerWebExchangeUtils.CLIENT_RESPONSE_HEADER_NAMES, filteredResponseHeaders.keySet());
                response.getHeaders().putAll(filteredResponseHeaders);
                return Mono.just(res);
            });
            Duration responseTimeout = this.getResponseTimeout(route);
            if (responseTimeout != null) {
                responseFlux = responseFlux.timeout(responseTimeout, Mono.error(new TimeoutException("Response took longer than timeout: " + responseTimeout))).onErrorMap(TimeoutException.class, (th) -> {
                    return new ResponseStatusException(HttpStatus.GATEWAY_TIMEOUT, th.getMessage(), th);
                });
            }

            return responseFlux.then(chain.filter(exchange));
        } else {
            return chain.filter(exchange);
        }
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值