Spring Cloud Gateway Handler Mapping 코드 분석

Updated:
Categories: Spring
Tags: #Spring #Spring Cloud Gateway

Spring Cloud Gateway Handler Mapping 코드 분석

Spring Cloud Gateway(v2.2.7.RELEASE) 코드에서 Handler Mapping 부분(아래 이미지 음영 외 부분)을 분석한다. Spring Cloud Gateway의 클라이언트 요청 처리는 Spring 프레임워크에서 처리하는 것과 동일하며, Gateway에 맞게 HandlerMapping 객체를 확장(RoutePredicateHandlerMapping)하여 요청을 처리할 WebHandler를 찾는데 사용한다.

클라이언트 요청이 처리되는 순서대로 코드 분석

  • 순서 요약: Client -> HttpWebHandlerAdapter.handle -> DispatcherHandler.handle -> RoutePredicateHandlerMapping.getHandlerInternal -> FilteringWebHandler.handle (Gateway Filter Chain)
  • 거쳐가는 객체가 더 있지만, 중요한 부분만 분석.

HttpWebHandlerAdapter

@Override
public Mono<Void> handle(ServerHttpRequest request, ServerHttpResponse response) {
	if (this.forwardedHeaderTransformer != null) {
		request = this.forwardedHeaderTransformer.apply(request);
	}
	// ServerWebExchange를 생성
	ServerWebExchange exchange = createExchange(request, response);

	LogFormatUtils.traceDebug(logger, traceOn ->
			exchange.getLogPrefix() + formatRequest(exchange.getRequest()) +
					(traceOn ? ", headers=" + formatHeaders(exchange.getRequest().getHeaders()) : ""));

	// getDelegate 으로 대상 WebHandler를 가져와서 exchange를 넘긴다.
	// Spring Cloud Gateway의 경우나 @EnableWebFlux를 사용하는 경우, 대상 WebHandler = DispatcherHandler
	return getDelegate().handle(exchange)
			.doOnSuccess(aVoid -> logResponse(exchange))
			.onErrorResume(ex -> handleUnresolvedError(exchange, ex))
			.then(Mono.defer(response::setComplete));
}

DispatcherHandler

  • 위치: org.springframework.web.reactive.DispatcherHandler
  • DispatcherHandler bean이 생성될 때, Application Context에서 HandlerMapping, HandlerAdapter, HandlerResultHandler 타입의 컴포넌트들을 찾아서 정렬한 뒤 멤버변수에 가지고 있는다.
  • 멤버 변수:
// HandlerMapping -- map requests to handler objects
@Nullable
private List<HandlerMapping> handlerMappings;
// HandlerAdapter -- for using any handler interface
@Nullable
private List<HandlerAdapter> handlerAdapters;
// HandlerResultHandler -- process handler return values
@Nullable
private List<HandlerResultHandler> resultHandlers;
  • handle 메서드: request를 처리하기 위해 등록된 handler들을 실행. handle 메서드 코드를 보면 멤버변수들의 역할을 어느정도 짐작 가능.
@Override
public Mono<Void> handle(ServerWebExchange exchange) {
	if (this.handlerMappings == null) {
		return createNotFoundError();
	}
	return Flux.fromIterable(this.handlerMappings)
			// (HandlerMapping) 요청 정보가 들어있는 exchange로 해당하는 Handler를 찾음. 
			.concatMap(mapping -> mapping.getHandler(exchange))
			.next()
			.switchIfEmpty(createNotFoundError())
			// (HandlerAdapter) invokeHandler 에서 this.handlerAdapters의 루프를 돌면서 해당하는 handlerAdapter를 찾고, 주어진 Handler로 요청을 처리.
			.flatMap(handler -> invokeHandler(exchange, handler))
			// (HandlerResultHandler) handleResult 에서 this.resultHandlers의 루프를 돌면서 해당하는 handleResult를 찾고, 응답 헤더를 수정하거나 응답에 데이터를 쓰는 것과 같은 처리.
			.flatMap(result -> handleResult(exchange, result));
}

RoutePredicateHandlerMapping

  • 위치: org.springframework.cloud.gateway.handler.RoutePredicateHandlerMapping
  • org.springframework.cloud.gateway.config.GatewayAutoConfiguration 에서 Bean으로 등록.
  • org.springframework.web.reactive.handler.AbstractHandlerMapping 를 상속받음.
    • AbstractHandlerMapping.getHandler
      • DispatcherHandler.handle 에서 handlerMapping 할 때 사용.
      • 구현체(ex. RoutePredicateHandlerMapping)에서 구현하도록한 getHandlerInternal 를 호출.
    • order 를 가지고 있음. (DispatcherHandler에서 정렬해서 가지고 있기 위한 HandlerMapping 순서이며, Spring Cloud Gateway에서는 RoutePredicateHandlerMapping를 order 1 로 설정.)
  • getHandlerInternal 메서드:
    • exchange로 매핑되는 route 를 찾고, FilteringWebHandler webHandler 를 반환. route를 찾지 못하면 Mono.empty 반환.
@Override
protected Mono<?> getHandlerInternal(ServerWebExchange exchange) {
  // ... 생략
  // lookupRoute: 별도 설명
  return lookupRoute(exchange)
      .flatMap((Function<Route, Mono<?>>) r -> {
        // ... exchange 업데이트 로직 생략
        // webHandler = FilteringWebHandler = Gateway Filter Chain 호출
        return Mono.just(webHandler);
      }).switchIfEmpty(Mono.empty().then(Mono.fromRunnable(() -> {
        // ... 매칭되는 RouteDefinition 를 찾지 못할 경우 로그 남기는 로직 생략
      })));
}
  • lookupRoute 메서드:
    • RouteLocator 에 있는 routes 에서 predicate를 만족하는 route를 반환한다. 없거나 에러 발생 시 Mono.empty 반환.
// 주석 및 코드 일부 생략
protected Mono<Route> lookupRoute(ServerWebExchange exchange) {
	return this.routeLocator.getRoutes() // Flux<Route> 반환
			// Route를 순서대로 검사
			.concatMap(route -> Mono.just(route).filterWhen(r -> {
				// route 의 predicate로 필터링
				return r.getPredicate().apply(exchange);
			})
					.doOnError(e -> logger.error(
							"Error applying predicate for route: " + route.getId(),
							e))
					.onErrorResume(e -> Mono.empty()))
			.next()
			.map(route -> {
				// 현재 버전에선 아직 구현이 안되어 있지만, Route의 Validation 체크를 하는 부분
				validateRoute(route, exchange);
				return route;
			});
}

참고

Comments