overview
Gateway Handler Mapping
SpringMVC의 동작 구성과 비슷하다.
DispatcherServlet과 유사하게 Front Controller를 하나두어 진입점을 하나로 통합하였다.
client 호출에서 Gateway Web Handler에 도달하기 전까지의 호출 순서를 보면 아래와 같다
Client -> HttpWebHandlerAdapter.handle -> (predicate, filter에서 사용하는 ServerWebExchange 생성 및 전달 ) DispatcherHandler.handle -> (DispatcherServlet 유사)(handlerMapping을 찾아 handlerAdapter에서 수행) RoutePredicateHandlerMapping.getHandlerInternal ->
DispatcherHandler.handleRequestWith->(webHandler 실행) FilteringWebHandler.handle (Gateway Filter Chain)
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())
.onErrorResume(ex -> handleDispatchError(exchange, ex))
.flatMap(handler -> handleRequestWith(exchange, handler));
}
요청(exchange)을 적합하게 처리할 handlerMapping을 찾는다 -> RoutePredicateHandlerMapping
concatMap : flux를 single flux로 전환하여 실행, 다만 순차적으로 ( <-> flatMap)
next : Emit only the first item emitted by this Flux
RoutePredicateHandlerMapping
yml에 등록된 route들 중에 predicate가 true인 타겟을 찾아, exchange의 attribute에 저장
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);
})
.next()
.map(route -> {
if (logger.isDebugEnabled()) {
logger.debug("Route matched: " + route.getId());
}
validateRoute(route, exchange);
return route;
});
}
Gateway Web Handler
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);
}
filter list들의 order로 순서를 지정한다.
application.yml로 등록한 경우 위에 정의될수록 우선순위가 높아진다.
우선순위가 같은 경우 globalFilter가 route에 정의된 필터보다 먼저 수행된다.
NettyRoutingFilter : remote api 호출
NettyWriteResponseFilter: 응답을 exchange response에 매핑
필터의 경우 pre, post로 나뉘어서 정의한 내용에 맞게 동작한다.
pre
@Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
HttpHeaders headers = exchange.getRequest().getHeaders();
// ..
return chain.filter(exchange.mutate().request(request).build());
};
}
post
@Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
return chain.filter(exchange)
.then(Mono.fromRunnable(() -> {
// post logic
}));
};
}