前のセクションでは、マイクロサービスが eureka に登録され、ゲートウェイがサービス ディスカバリを通じて対応するマイクロサービスにアクセスするという非常に単純な例を示しました。このセクションでは、ゲートウェイ要求転送プロセス全体を簡単に分析します。
1. コアプロセス
主なプロセス:
- ゲートウェイクライアントは Spring Cloud Gateway にリクエストを送信します
- リクエストはまず HttpWebHandlerAdapter によって抽出され、ゲートウェイ コンテキストに組み込まれます。
- 次に、ゲートウェイのコンテキストが DispatcherHandler に渡されます。DispatcherHandler は、リクエストを RoutePredicateHandlerMapping に配布します。
- RoutePredicateHandlerMapping は、ルートの検索と、ルート アサーションに基づいてルートが利用可能かどうかの判断を行います。
- アサーションが成功すると、フィルタ チェーンが FilteringWebHandler によって作成され、呼び出されます。
- リクエスト固有のフィルター チェーンを通じてリクエストを実行します。フィルターが点線で区切られている理由は、フィルターがプロキシ リクエストの送信前 (前) と後 (後) にロジックを実行できるためです。
- すべてのプレフィルターロジックを実行します。次に、プロキシ リクエストを実行します。プロキシ要求が行われた後、「post」フィルター ロジックが実行されます。
- 処理後、応答がゲートウェイ クライアントに返されます。
二、具体分析
リクエストが来ると、HttpWebHandlerAdapter.handle メソッドを通過します。これはリクエストのメインの入口として理解できます。
@Override
public Mono<Void> handle(ServerHttpRequest request, ServerHttpResponse response) {
if (this.forwardedHeaderTransformer != null) {
try {
request = this.forwardedHeaderTransformer.apply(request);
}
catch (Throwable ex) {
if (logger.isDebugEnabled()) {
logger.debug("Failed to apply forwarded headers to " + formatRequest(request), ex);
}
response.setStatusCode(HttpStatus.BAD_REQUEST);
return response.setComplete();
}
}
//组装上下文
ServerWebExchange exchange = createExchange(request, response);
LogFormatUtils.traceDebug(logger, traceOn ->
exchange.getLogPrefix() + formatRequest(exchange.getRequest()) +
(traceOn ? ", headers=" + formatHeaders(exchange.getRequest().getHeaders()) : ""));
//委派给delegate来处理
return getDelegate().handle(exchange)
.doOnSuccess(aVoid -> logResponse(exchange))
.onErrorResume(ex -> handleUnresolvedError(exchange, ex))
.then(Mono.defer(response::setComplete));
}
このデリゲートは何ですか? インターフェイス定義を見てください:
これはプロセッサであり、すべてのパラメータはコンテキスト交換でカプセル化されています。
public interface WebHandler {
/**
* Handle the web server exchange.
* @param exchange the current server exchange
* @return {@code Mono<Void>} to indicate when request handling is complete
*/
Mono<Void> handle(ServerWebExchange exchange);
}
最終的には DispatcherHandler に転送されます
@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));
}
handlerMappings とは何ですか? それは
リストです。HandlerMapping は現在のリクエストに従って対応するプロセッサを見つけることができます。/hello/world
などの現在のリクエストがゲートウェイ サービス上のコントローラに対応するインターフェイスである場合、これは RequestMappingHandlerAdapter を見つけることができますRequestMappingHandlerMapping。
現在のリクエストをダウンストリームのマイクロサービスに転送する必要がある場合は、RoutePredicateHandlerMapping
RoutePredicateHandlerMapping を見つけてルーティングのメイン ロジックを見つけます。
@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().getLocalAddress() != null
&& exchange.getRequest().getLocalAddress().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上下文中
exchange.getAttributes().put(GATEWAY_ROUTE_ATTR, r);
//返回的handler实际上是webHandler
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());
//用predicate作一下匹配,找出符合当前请求的路由
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;
});
/*
* TODO: trace logging if (logger.isTraceEnabled()) {
* logger.trace("RouteDefinition did not match: " + routeDefinition.getId()); }
*/
}
RouteLocator にはどのようなルートが含まれていますか? デバッグして確認してください
実際、サービスの登録と検出を使用した後、マイクロサービスはルートを自動的に登録します。たとえば、上記の hello-service では、ルートが /hello-service/** というパスに自動的に登録されます。これが、yml 設定ファイルにルーティングが設定されていない場合でも、hello-service リクエストを自動的に転送できる理由です。
同時に、このルートの下に ReWritePathFilter があることがわかります。これにより、サービス名が自動的に削除され、リクエストがダウンストリームのマイクロサービスに転送されます。
次に、FilteringWebHandler の処理ロジックを見てみましょう。
@Override
public Mono<Void> handle(ServerWebExchange exchange) {
//从上下文中取出路由,路由中包含filters
Route route = exchange.getRequiredAttribute(GATEWAY_ROUTE_ATTR);
List<GatewayFilter> gatewayFilters = route.getFilters();
//spring容器中的Global Filter也取出来
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);
}
//后面就是filter链式调用了
return new DefaultGatewayFilterChain(combined).filter(exchange);
}
コードには 2 つの主要なロジックがあることがわかります。
1. フィルターを含むコンテキストからルートを取り出します。
2. Spring コンテナー内のグローバル フィルターも取り出します。
3. 上記 2 つのフィルターをマージし、並べ替えます
。 4.フィルター リストを組み立てる 責任の連鎖を形成して呼び出しを行う
ソース コードを確認してから、コア プロセスの図を見ると、より明確になります。
もう少し詳細な別の図を要約すると、次のようになります。
このソース コードを見た私の個人的な経験: コア プロセス全体は複雑ではありませんが、難しいのはおそらくリアクター応答プログラミングです。これまでこの部分に触れたことがない場合は、どこで操作すればよいか分からず混乱するでしょう。次に行きましょう!したがって、このソースコードを学ぶには、reactor も学ぶ必要があります。