개발 일지

Spring Cloud Gateway Code 분석

북극곰은콜라 2023. 10. 28. 12:38
반응형

 


개요

Spring Cloud Gateway 동작 원리를 code를 통해 분석

 


Spring Cloud Gateway 동작 원리

Predicates and filters are specific to routes

Spring Cloud Gateway의 핵심 아이디어인 “Routing 및 지역적 filter apply” 를 위해 HandlerMapping을 구현해서 추가했다.
HandlerMapping은 Filter 이후 Exchange를 처리할 Handler를 찾는 클래스이다.
 
RoutePredicateHandlerMapping으로 구현했으며, 구현된 핵심은
exchange를 기반으로 Route를 찾아서 attribute에 route를 넣는 작업
이후 FilteringWebHandler(webHandler 구현체)를 통해 작업을 진행
 
FilteringWebHandler는 exchange에서 Route를 가져와서 필터가 필요한지 확인한다.
적용된 필터와, GlobalFilter랑 합쳐서 FilterChain을 만들어 처리한다.

 


GatewayAutoConfiguration

@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(name = "spring.cloud.gateway.enabled", matchIfMissing = true)
@EnableConfigurationProperties
@AutoConfigureBefore({ HttpHandlerAutoConfiguration.class, WebFluxAutoConfiguration.class })
@AutoConfigureAfter({ GatewayReactiveLoadBalancerClientAutoConfiguration.class,
		GatewayClassPathWarningAutoConfiguration.class })
@ConditionalOnClass(DispatcherHandler.class)
public class GatewayAutoConfiguration {
  ...
  
  @Bean
  @ConditionalOnMissingBean
  public FilteringWebHandler filteringWebHandler(List<GlobalFilter> globalFilters) {
		return new FilteringWebHandler(globalFilters);
  }
	
  ...
  
  @Bean
  @ConditionalOnMissingBean
  public RoutePredicateHandlerMapping routePredicateHandlerMapping(FilteringWebHandler webHandler,
			RouteLocator routeLocator, GlobalCorsProperties globalCorsProperties, Environment environment) {
		return new RoutePredicateHandlerMapping(webHandler, routeLocator, globalCorsProperties, environment);
  }
  
  ...
}


RoutePredicateHandlerMapping

// AbstractHandlerMapping
public Mono<Object> getHandler(ServerWebExchange exchange) {
  return this.getHandlerInternal(exchange).map((handler) -> {
    ...
    ServerHttpRequest request = exchange.getRequest();
    ...
    return handler;
  });
}
HandlerMapping은 전략패턴으로 구성되었으며
구현체의 getHandlerInternal()을 호출해 Mono로 Handler를 받아 Handler 공통 작업을 수행한다.
@Override
protected Mono<?> getHandlerInternal(ServerWebExchange exchange) {
	...

	return Mono.deferContextual(contextView -> {
		...
		return lookupRoute(exchange)
				.flatMap((Function<Route, Mono<?>>) r -> {
					...

					exchange.getAttributes().put(GATEWAY_ROUTE_ATTR, r);
					return Mono.just(webHandler);
				}).switchIfEmpty(Mono.empty().then(Mono.fromRunnable(() -> {
					...
				})));
	});
}

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);
			})
					...
			.next()
			.map(route -> {
				...
				validateRoute(route, exchange);
				return route;
			});
}
RoutePredicateHandlerMapping은 lookupRoute로 Predicate에 부합하는 Route 객체를 exchange에 attribute로 put 한다.

FilteringWebHandler (Gateway)

@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);
    AnnotationAwareOrderComparator.sort(combined);

    ...

    return new DefaultGatewayFilterChain(combined).filter(exchange);
}
HandlerMapping의 getHandler() 는 최종적으로
return Mono.just(webHandler); 를 통해서 FilteringWebHandler를 반환한다.
이 후 handlerAdapter에서 FilteringWebHandler의 handle()을 호출한다.

 


Match Exchange with Route

lookupRoute

// RoutePredicateHandlerMapping.java
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);
			})
			...
			.next()
			.map(route -> {
				...
				validateRoute(route, exchange);
				return route;
			});
}
더보기
// RouteDefinitionRouteLocator
@Override
public Flux<Route> getRoutes() {
	return getRoutes(this.routeDefinitionLocator.getRouteDefinitions()); // RouteDefinitions from CacheFlux
}

private Flux<Route> getRoutes(Flux<RouteDefinition> routeDefinitions) {
	Flux<Route> routes = routeDefinitions.map(this::convertToRoute);
    ...
	return routes.map(route -> {
		...
		return route;
	});
}
Cache로 부터 모든 Route 객체를 변환 받아 predicate를 돌린다.

 


번외: Predicate Route

 
Java Predicate를 reactior를 통해 Async하게 test할 수 있게 하는 Spring Cloud Gateway의 아이디어이다.
사용자가 작성한 Predicate 전체를 sync하게 처리하지 않고, 모든 predicate를 async하게 바꿔 처리하기 위함으로 추정된다.
delegate와 decorate 패턴을 활용한다.

Java Predicate

더보기
@FunctionalInterface
public interface Predicate<T> {
  boolean test(T var1);

  default Predicate<T> and(Predicate<? super T> other) {
    Objects.requireNonNull(other);
    return (t) -> {
      return this.test(t) && other.test(t);
    };
  }

  default Predicate<T> negate() {
    return (t) -> {
      return !this.test(t);
    };
  }

  default Predicate<T> or(Predicate<? super T> other) {
    Objects.requireNonNull(other);
    return (t) -> {
      return this.test(t) || other.test(t);
    };
  }

  static <T> Predicate<T> isEqual(Object targetRef) {
    return null == targetRef ? Objects::isNull : (object) -> {
      return targetRef.equals(object);
    };
  }

  static <T> Predicate<T> not(Predicate<? super T> target) {
    Objects.requireNonNull(target);
    return target.negate();
  }
}
Java Objects의 Functional 인테페이스이다.
특정 Object의 조건을 서술한 Function을 사전에 정의하여
다른 로직에서 해당 조건을 불러와 사용할 수 있다.
또한
and, or, negate, not, isEqual 메서드를 지원하여 function의 chaining을 할 수 있게 했다.
// 심플한 사용 예
Predicate<Object> isString = o -> o instanceof String;
System.out.println(isString.test("")); // true
System.out.println(isString.test(1)); // false

// predicate Chain
Predicate<String> isStartWithA = s -> s.startsWith("A");
Predicate<String> isEndWithA = s -> s.endsWith("A");
System.out.println(isStartWithA.and(isEndWithA).test("ABB")); // false
System.out.println(isStartWithA.and(isEndWithA).test("ABA")); // true

Predicate with Reactor

public interface AsyncPredicate<T> extends Function<T, Publisher<Boolean>>, HasConfig {
  default AsyncPredicate<T> and(AsyncPredicate<? super T> other) {
	return new AndAsyncPredicate<>(this, other);
  }
  
  default AsyncPredicate<T> negate() {
		return new NegateAsyncPredicate<>(this);
  }
  
  default AsyncPredicate<T> not(AsyncPredicate<? super T> other) {
		return new NegateAsyncPredicate<>(other);
  }
  
  default AsyncPredicate<T> or(AsyncPredicate<? super T> other) {
		return new OrAsyncPredicate<>(this, other);
  }
	
  ...
}
Predicate를 Reactive 환경에서 Async하게 처리하기 위해 구현된 파트 중 일부이다.
Predicate의 체인을 만들고
Java Function apply를 통해 모든 Predicate를 순서있는 비동기 처리를 구현했다.

분석

// Default
@Override
public Publisher<Boolean> apply(T t) {
	return Mono.just(delegate.test(t));
}

// Negate
@Override
public Publisher<Boolean> apply(T t) {
	return Mono.from(predicate.apply(t)).map(b -> !b);
}

// And
@Override
public Publisher<Boolean> apply(T t) {
	return Mono.from(left.apply(t)).flatMap(result -> !result ? Mono.just(false) : Mono.from(right.apply(t)));
}

// Or
@Override
public Publisher<Boolean> apply(T t) {
	return Mono.from(left.apply(t)).flatMap(result -> result ? Mono.just(true) : Mono.from(right.apply(t)));
}
default는 최초의 Predicate를 받아 delegate 패턴을 통해 predicate를 위임하는 기초 구현체이다.
negate, and, or는 다른 AsyncPredicate를 생성자로 받아 apply 시 각각의 로직을 publisher에 append하는 구현체이다.
// Simple Example
Predicate<String> isStartWithA = s -> s.startsWith("A");
AsyncPredicate.DefaultAsyncPredicate<String> predicate1 = new AsyncPredicate.DefaultAsyncPredicate<>(isStartWithA);

Predicate<String> isEndWithA = s -> s.endsWith("A");
AsyncPredicate.DefaultAsyncPredicate<String> predicate2 = new AsyncPredicate.DefaultAsyncPredicate<>(isEndWithA);

AsyncPredicate.OrAsyncPredicate<String> orPredicate = new AsyncPredicate.OrAsyncPredicate<>(predicate1, predicate2);

Flux.just("BBB", "ABB", "ABA")
    .flatMap(orPredicate)
    .doOnNext(System.out::println)
    .subscribe();

-> false, true, true

AsyncPredicate in Spring Cloud Gateway

 
// RouteDefinitionRouteLocator
private Route convertToRoute(RouteDefinition routeDefinition) {
	AsyncPredicate<ServerWebExchange> predicate = combinePredicates(routeDefinition);
	...
}

private AsyncPredicate<ServerWebExchange> combinePredicates(RouteDefinition routeDefinition) {
  ...
  AsyncPredicate<ServerWebExchange> predicate = lookup(routeDefinition, predicates.get(0));
  for (PredicateDefinition andPredicate : predicates.subList(1, predicates.size())) {
		AsyncPredicate<ServerWebExchange> found = lookup(routeDefinition, andPredicate);
		predicate = predicate.and(found);
  }
  return predicate;
}

// RoutePredicateHandlerMapping
protected Mono<Route> lookupRoute(ServerWebExchange exchange) {
	return this.routeLocator.getRoutes()
		.concatMap(route -> Mono.just(route).filterWhen(r -> {
			...
			return r.getPredicate().apply(exchange);
		})
		...
}
RouteDefinition: Library 사용자가 Route Rule에 대해 정의 할 수 있는 객체이다.
Spring Cloud Gateway는 사용자가 정의한 RouteDefinition에서 predicate들을 flat한 AsyncPredicate로 만든다.
이 후 RoutePredicateHandlerMapping 에서 predicate를 통해 route를 찾을 때 사용한다.

 

반응형