上篇分享了SCG的启动原理,主要剖析了SCG如何在weblfux整体流程里切入自己的启动和初始化。本文我们将聚焦webflux DispatcherHandler 三大组件,更加深入的聊聊SCG的 Predicate 的实现原理,其中 RoutePredicateHandlerMapping 作为 DispatcherHandler 一个组件实现,从一开始就接管了请求。
作为前端控制器设计模式的实现,webflux中的 DispatcherHandler 和 SpringMVC 中 DispatcherServlet 如出一辙,是请求的集中访问点。对于 SCG,或者说所有基于 webflux 构建的系统,所有请求都是由 DispatcherHandler 调度的。如上图所示,DispatcherHandler(下文将简写为DH)有三大组件,
- 一组HandlerMapping: 缓存了系统所有请求映射规则(即路由规则) DH 处理请求的第一步便是从中找出当前请求最合适的Handler。
- 一组HandlerAdapter: Handler适配器组件,针对第一步找到的handler,DH 会从中匹配对应的一个 HandlerAdapter 来进一步处理请求,请求结果可能会返回HandlerResult。
- 一组 HandlerResultHandler: 针对第二步处理返回的 HandlerResult (也有可能没有,则不必处理),会从中匹配一个对应的 HandlerResultHandler 进一步处理。
上面activity图清晰表达了处理逻辑,如果不够过瘾,在来看下核心代码:
@Override
public Mono<Void> handle(ServerWebExchange exchange) {
if (this.handlerMappings == null) {
return createNotFoundError();
}
if (CorsUtils.isPreFlightRequest(exchange.getRequest())) {
return handlePreFlight(exchange);
}
return Flux.fromIterable(this.handlerMappings)
.concatMap(mapping -> mapping.getHandler(exchange))
.next()
.switchIfEmpty(createNotFoundError())
.flatMap(handler -> invokeHandler(exchange, handler))
.flatMap(result -> handleResult(exchange, result));
}
全部逻辑都在最后一行Flux代码里,对Reactive代码不熟悉的同学,我稍微解释一下这段代码的意思:
- 从handlerMappings列表构造一个flux对象:Flux.fromIterable(this.handlerMappings)
- 依次对flux中HandlerMapping对象执行getHandler,找到则结束:concatMap(mapping -> mapping.getHandler(exchange)).next()
- 遍历完都没找到handler,则返回notFoundError: switchIfEmpty(createNotFoundError()) 对找到的handler执行invokeHandler方法: flatMap(handler -> invokeHandler(exchange, handler))
- 对上一步返回的结果进行处理(若有结果): flatMap(result -> handleResult(exchange, result))
这里有个类似逻辑但是更简单的例子可以run一下看看:FluxHandlerTest.java
了解整体思路之后,我们来看 HandlerMapping 实现:
webflux提供了三个HM, 对应图中蓝色的类:
- RouterFunctionMapping
- SimpleUrlHandlerMapping
- RequestMappingHandlerMapping
SCG实现了一个自己的HandlerMapping: RoutePredicateHanderMapping(RPHM) 。SCG从这里开始接管 webflux 请求整个处理逻辑,其后的 Route 规则匹配、netty client对upstream的调用都在此刻埋下伏笔。
先看 webflux 中提供的三个 HandlerMapping,WebFluxConfigurationSupport在启动的时候 会初始化好这三个 bean,然后 springboot 会将他们自动注入到 DispatcherHandler 中。
RouterFunctionMapping
用于函数式断点的路由, 如果他匹配,则会将请求交给一个RouterFunction处理,比较抽象,举个例子:
//手动一个RouterFunction Bean加到 RouterFunctionMapping 中
@Bean
public RouterFunction<ServerResponse> routerHandlerConfig(){
return RouterFunctions.route(GET("/helloflux"), this::hello);
}
那么请求 /helloflux 时,在 DH 第一阶段就会匹配到 RouterFunctionMapping 得到这个 RouteFunction 对象处理,有兴趣可以 debug 一下,代码链接:RouterConfig.java
SimpleUrlHandlerMapping
用于显示注册URL模式匹配,这里不举例了,早期写springMVC代码的时候经常有类似的配置。
RequestMappingHandlerMapping
基于注解的路由控制,项目中用的最多,比如:
@RestController
public class HelloController {
@GetMapping("/hello")
public Mono<String> hello(){
return Mono.just("hello");
}
}
我们来详细分析一下,在启动的时候会把所有类似上面代码解析成一个 RequestMappingInfo 对象并注册在 MappingRegistration 对象里:
一个 RequestMappingInfo 对象表达了详细的请求 mapping 规则,或者说路由规则,从上面类图很容易发现他包含请求path、参数、header、method等条件。所以,我们可以看一个稍微复杂一点的例子,不仅需要请求路劲匹配 /hello, 另外必须是get请求、带有请求参数foo=bar、带一个token=wls的header、并且 Content-Type 为 text/plain
@RequestMapping(path = "/hello",
method = RequestMethod.GET,
params = "foo=bar",
headers = "token=wls",
consumes = "text/plain")
public Mono<String> helloWithMoreRouteConditions(){
return Mono.just("helloWithMoreRouteConditions");
}
于是我们根据这个HelloController.java会得到如下mapping规则的注册信息( MappingRegistration ):
首先, pathLookup 有一个 /hello 的 mapping,映射到两个 RequestMappingInfo 对象,一个就是简单的 hello,另一个有多个条件。
其次, mappingRegistry 中对两个 RequestMappingInfo 对象分别映射到 handlerMethod (即 helloController#hello, helloController#helloWithMoreRouteConditions)
当执行简单的 /hello 请求时很显然会匹配并执行helloController#hello方法
curl http://localhost:10000/hello
而当带上更加精确的条件时的helloController里的两个方法都匹配,但是最佳匹配是helloWithMoreRouteConditions:
curl -X GET -H 'token:wls' -H 'Content-Type:text/plain' http://localhost:10000/hello?foo=bar
这里有一个最佳匹配规则,对应代码332、333行。
最后,我们来研究 SCG 实现的 RoutePredicateHanderMapping
RoutePredicateHanderMapping 集成了一个 RouteLocator 对象,根据上一篇”SCG的启动原理“分享,我们知道SCG的路由配置都会初始化好,并可以被 routeLocator 对象查到。此外,他还集成了一个 FilteringWebHandler 对象最为 webHandler,这些 Bean 都是在 GatewayAutoConfiguration 中自动创建和初始化的。所以,这里重点看一下当请求进来被 SCG 接管之后如何执行。 整体的执行逻辑还是再 DispatchHandler 框架内,第一步执行到 RoutePredicateHanderMapping#getHandlerInternal
@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());
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);
}
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()
.concatMap(route -> Mono.just(route).filterWhen(r -> {
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;
});
}
代码还是很清晰的,第一步lookupRoute,找到第一个所有 predicate#apply 都为 true 的 route。第二步把该 route 缓存在 ServerWebExchange 属性里,然后返回 webHandler(这里是 SCG 的 FilteringWebHandler 对象),他的 handle 方法会被 DispatcherHandler 调用。我们进入到 FilteringWebHandler :
@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);
}
他是执行 SCG 的 Filter 链,这将是另一趴逻辑,具体展开得放到后续文章里,本文先可以明确的是当执行到其中一个叫 NettyRoutingFilter 时,netty HttpClient 会向 upstream 发起真正的请求,拿到 upstream 的结果之后返回给 downstream.
以上便是本文分享的全部内容,最后附全文引用的完整类图: