基于spring cloud gateway(Spring Cloud Hoxton.SR9)
网关流程图
① 路由判断;客户端的请求到达网关后,先经过 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;
});
}
routeLocator.getRoutes()获取所有可用的路由列表,这是通过routeLocator对象调用getRoutes()方法实现的,返回一个包含路由信息的Flux<Route>(Flux是Reactor中的一个发布者,代表一个包含多个元素的异步序列)。- 使用
concatMap操作符对每个路由进行处理。concatMap操作符将路由序列中的每个元素都转换为一个新的Mono(Mono是Reactor中的一个发布者,代表一个包含单个元素或错误信号的异步序列),并将这些Mono按顺序连接在一起。 - 对于每个路由,我们使用
Mono.just(route)来创建一个包含该路由的Mono,然后使用filterWhen操作符来根据断言条件进行过滤。filterWhen操作符使用一个返回Mono<Boolean>的谓词函数来决定是否保留当前路由。 - 在应用断言条件时,我们使用
r.getPredicate().apply(exchange)来对当前的exchange应用该路由的断言条件。如果断言条件返回true,表示该路由匹配成功,会继续进行后续处理。如果断言条件返回false,表示该路由不匹配当前的exchange,将继续处理下一个路由。 - 在应用断言条件时,我们也将当前正在测试的路由ID添加到
exchange的属性中,使用exchange.getAttributes().put(GATEWAY_PREDICATE_ROUTE_ATTR, r.getId())。 - 如果在应用断言条件时出现错误(例如,断言函数抛出异常),我们使用
doOnError操作进行错误处理。在这里,我们使用logger.error()来记录错误信息。 - 我们还使用
onErrorResume操作符来处理错误。在这里,我们返回一个空的Mono,表示出现错误时忽略当前路由,继续处理其他路由。 - 使用
next()操作符获取第一个匹配成功的路由。由于之前使用concatMap连接了多个Mono,next()会返回第一个元素(匹配成功的路由),或者如果没有匹配成功的路由,则返回一个空的Mono。 - 在获取到匹配的路由后,通过
map操作符对该路由进行后续处理。在这里,我们会对路由进行日志记录,并调用validateRoute(route, exchange)对路由进行验证。最终,将路由结果作为Mono<Route>返回。
这边比较特殊的是,当spring.cloud.discovery.reactive.enabled=true开启时,DiscoveryClientRouteDefinitionLocator会自动收集注册中心的实力的路由信息,如果当配置文件配置的断言匹配路径和服务的实例相同,默认会优先选择注册中心实例的路由.所以当在配置文件配置自定义的filters就会失效.
解决方式: 就是匹配的断言路径不要和服务的名称一致
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 的原理