spring cloud gateway笔记

276 阅读6分钟

基于spring cloud gateway(Spring Cloud Hoxton.SR9)

网关流程图

image.png

① 路由判断;客户端的请求到达网关后,先经过 Gateway Handler Mapping 处理,这里面会做断言(Predicate)判断,看下符合哪个路由规则,这个路由映射后端的某个服务。

② 请求过滤:然后请求到达 Gateway Web Handler,这里面有很多过滤器,组成过滤器链(Filter Chain),这些过滤器可以对请求进行拦截和修改,比如添加请求头、参数校验等等,有点像净化污水。然后将请求转发到实际的后端服务。这些过滤器逻辑上可以称作 Pre-Filters,Pre 可以理解为“在...之前”。

③ 服务处理:后端服务会对请求进行处理。

④ 响应过滤:后端处理完结果后,返回给 Gateway 的过滤器再次做处理,逻辑上可以称作 Post-Filters,Post 可以理解为“在...之后”。

⑤ 响应返回:响应经过过滤处理后,返回给客户端。

跨域问题

定时加载拦截器到内存

CachingRouteLocator它是一个路由定位器,用于缓存路由信息,避免重复获取.


private static final String CACHE_KEY = "routes";

private final RouteLocator delegate;

private final Flux<Route> routes;

private final Map<String, List> cache = new ConcurrentHashMap<>();

private ApplicationEventPublisher applicationEventPublisher;


// 构造方法中是这个方法根据指定的缓存键(CACHE_KEY)从缓存中查找缓存的路由数据。如果缓存命中(缓存中有数据),则返回缓存中的数据,否则返回一个空的Flux
public CachingRouteLocator(RouteLocator delegate) {
   this.delegate = delegate;
   routes = CacheFlux.lookup(cache, CACHE_KEY, Route.class)
         .onCacheMissResume(this::fetch);
}


// 调用RouteDefinitionRouteLocator的getRoutes()方法
private Flux<Route> fetch() {
   return this.delegate.getRoutes().sort(AnnotationAwareOrderComparator.INSTANCE);
}



@Override
public Flux<Route> getRoutes() {
   return this.routes;
}


// 刷新路由的统一入口都是监听`RefreshRoutesEvent`事件
@Override
public void onApplicationEvent(RefreshRoutesEvent event) {
   try {
      fetch().collect(Collectors.toList()).subscribe(list -> Flux.fromIterable(list)
            .materialize().collect(Collectors.toList()).subscribe(signals -> {
               applicationEventPublisher
                     .publishEvent(new RefreshRoutesResultEvent(this));
               cache.put(CACHE_KEY, signals);
               // 将路由信息缓存到cache中
            }, throwable -> handleRefreshError(throwable)));
   }
   catch (Throwable e) {
      handleRefreshError(e);
   }
}


RouteDefinitionRouteLocator

@Override
public Flux<Route> getRoutes() {
   Flux<Route> routes = this.routeDefinitionLocator.getRouteDefinitions()
   // 主要是从下面三个类收集路由信息
   //1.DiscoveryClientRouteDefinitionLocator
   //2.InMemoryRouteDefinitionRepository
   //3.PropertiesRouteDefinitionLocator
         .map(this::convertToRoute);

   if (!gatewayProperties.isFailOnRouteDefinitionError()) {
      // instead of letting error bubble up, continue
      routes = routes.onErrorContinue((error, obj) -> {
         if (logger.isWarnEnabled()) {
            logger.warn("RouteDefinition id " + ((RouteDefinition) obj).getId()
                  + " will be ignored. Definition has invalid configs, "
                  + error.getMessage());
         }
      });
   }

   return routes.map(route -> {
      if (logger.isDebugEnabled()) {
         logger.debug("RouteDefinition matched: " + route.getId());
      }
      return route;
   });
}


private Route convertToRoute(RouteDefinition routeDefinition) {
   AsyncPredicate<ServerWebExchange> predicate = combinePredicates(routeDefinition);
   List<GatewayFilter> gatewayFilters = getFilters(routeDefinition);

   //它将路由定义中的断言条件和过滤器列表应用到构建的路由中,并返回最终的 `Route` 对象
   return Route.async(routeDefinition).asyncPredicate(predicate)
         .replaceFilters(gatewayFilters).build();
}




GatewayDiscoveryClientAutoConfiguration 配置类


// 当开启时,才会从注册中心获取服务的路由信息
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(value = "spring.cloud.discovery.reactive.enabled",
      matchIfMissing = true)
public static class ReactiveDiscoveryClientRouteDefinitionLocatorConfiguration {

   @Bean
   @ConditionalOnProperty(name = "spring.cloud.gateway.discovery.locator.enabled")
   public DiscoveryClientRouteDefinitionLocator discoveryClientRouteDefinitionLocator(
         ReactiveDiscoveryClient discoveryClient,
         DiscoveryLocatorProperties properties) {
      return new DiscoveryClientRouteDefinitionLocator(discoveryClient, properties);
   }

}

当一个请求过来,怎么获取对应拦截链路


DispatcherHandler


    // 请求统一在这边处理,主要是要获取RoutePredicateHandlerMapping
@Override
public Mono<Void> handle(ServerWebExchange exchange) {
   if (this.handlerMappings == null) {
      return createNotFoundError();
   }
   return Flux.fromIterable(this.handlerMappings)
         .concatMap(mapping -> mapping.getHandler(exchange))
         .next()
         .switchIfEmpty(createNotFoundError())
         .flatMap(handler -> invokeHandler(exchange, handler))
         .flatMap(result -> handleResult(exchange, result));
}



private Mono<HandlerResult> invokeHandler(ServerWebExchange exchange, Object handler) {
   if (this.handlerAdapters != null) {
      for (HandlerAdapter handlerAdapter : this.handlerAdapters) {
         if (handlerAdapter.supports(handler)) {
         // FilteringWebHandler处理拦截链路
            return handlerAdapter.handle(exchange, handler);
         }
      }
   }
   return Mono.error(new IllegalStateException("No HandlerAdapter: " + handler));
}



AbstractHandlerMapping

@Override
public Mono<Object> getHandler(ServerWebExchange exchange) {
   return getHandlerInternal(exchange).map(handler -> {
      if (logger.isDebugEnabled()) {
         logger.debug(exchange.getLogPrefix() + "Mapped to " + handler);
      }
      ServerHttpRequest request = exchange.getRequest();
      if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
         CorsConfiguration config = (this.corsConfigurationSource != null ? this.corsConfigurationSource.getCorsConfiguration(exchange) : null);
         CorsConfiguration handlerConfig = 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;
   });
}



RoutePredicateHandlerMapping


// 将路由信息放到exchange的Attributes中
@Override
protected Mono<?> getHandlerInternal(ServerWebExchange exchange) {
   // don't handle requests on management port if set and different than server port
   if (this.managementPortType == DIFFERENT && this.managementPort != null
         && exchange.getRequest().getURI().getPort() == this.managementPort) {
      return Mono.empty();
   }
   exchange.getAttributes().put(GATEWAY_HANDLER_MAPPER_ATTR, getSimpleName());

   return lookupRoute(exchange)
         // .log("route-predicate-handler-mapping", Level.FINER) //name this
         .flatMap((Function<Route, Mono<?>>) r -> {
            exchange.getAttributes().remove(GATEWAY_PREDICATE_ROUTE_ATTR);
            if (logger.isDebugEnabled()) {
               logger.debug(
                     "Mapping [" + getExchangeDesc(exchange) + "] to " + r);
            }

            exchange.getAttributes().put(GATEWAY_ROUTE_ATTR, r);
            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) + "]");
            }
         })));
}

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);
         })
               // instead of immediately stopping main flux due to error, log and
               // swallow it
               .doOnError(e -> logger.error(
                     "Error applying predicate for route: " + route.getId(),
                     e))
               .onErrorResume(e -> Mono.empty()))
         // .defaultIfEmpty() put a static Route not found
         // or .switchIfEmpty()
         // .switchIfEmpty(Mono.<Route>empty().log("noroute"))
         .next()
         // TODO: error handling
         .map(route -> {
            if (logger.isDebugEnabled()) {
               logger.debug("Route matched: " + route.getId());
            }
            validateRoute(route, exchange);
            return route;
         });

  
}

  1. routeLocator.getRoutes()获取所有可用的路由列表,这是通过routeLocator对象调用getRoutes()方法实现的,返回一个包含路由信息的Flux<Route>(Flux是Reactor中的一个发布者,代表一个包含多个元素的异步序列)。
  2. 使用concatMap操作符对每个路由进行处理。concatMap操作符将路由序列中的每个元素都转换为一个新的Mono(Mono是Reactor中的一个发布者,代表一个包含单个元素或错误信号的异步序列),并将这些Mono按顺序连接在一起。
  3. 对于每个路由,我们使用Mono.just(route)来创建一个包含该路由的Mono,然后使用filterWhen操作符来根据断言条件进行过滤。filterWhen操作符使用一个返回Mono<Boolean>的谓词函数来决定是否保留当前路由。
  4. 在应用断言条件时,我们使用r.getPredicate().apply(exchange)来对当前的exchange应用该路由的断言条件。如果断言条件返回true,表示该路由匹配成功,会继续进行后续处理。如果断言条件返回false,表示该路由不匹配当前的exchange,将继续处理下一个路由。
  5. 在应用断言条件时,我们也将当前正在测试的路由ID添加到exchange的属性中,使用exchange.getAttributes().put(GATEWAY_PREDICATE_ROUTE_ATTR, r.getId())
  6. 如果在应用断言条件时出现错误(例如,断言函数抛出异常),我们使用doOnError操作进行错误处理。在这里,我们使用logger.error()来记录错误信息。
  7. 我们还使用onErrorResume操作符来处理错误。在这里,我们返回一个空的Mono,表示出现错误时忽略当前路由,继续处理其他路由。
  8. 使用next()操作符获取第一个匹配成功的路由。由于之前使用concatMap连接了多个Mononext()会返回第一个元素(匹配成功的路由),或者如果没有匹配成功的路由,则返回一个空的Mono
  9. 在获取到匹配的路由后,通过map操作符对该路由进行后续处理。在这里,我们会对路由进行日志记录,并调用validateRoute(route, exchange)对路由进行验证。最终,将路由结果作为Mono<Route>返回。

这边比较特殊的是,当spring.cloud.discovery.reactive.enabled=true开启时,DiscoveryClientRouteDefinitionLocator会自动收集注册中心的实力的路由信息,如果当配置文件配置的断言匹配路径和服务的实例相同,默认会优先选择注册中心实例的路由.所以当在配置文件配置自定义的filters就会失效.

image.png

image.png

解决方式: 就是匹配的断言路径不要和服务的名称一致

image.png


FilteringWebHandler



// 获取拦截器列表,根据order排序
@Override
public Mono<Void> handle(ServerWebExchange exchange) {
   Route route = exchange.getRequiredAttribute(GATEWAY_ROUTE_ATTR);
   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);
}




// 执行对应的拦截器逻辑
@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
      }
   });
}


nacos 动态路由

集成对应nacos就行 ,网关配置统一放置到nacos统一管理.nacos控制台配置刷新时,会自动触发路由的刷新事件

# 深入理解 Spring Cloud Gateway 的原理

#定时刷新路由信息

# Spring Cloud Gateway 启动时加载并缓存路由

## gateway执行流程 # # SpringCloudGateway配置路由规则与服务发现的路由规则功能冲突