기본적인 GlobalFilter 사용법
Global Filter를 커스터마이징 해서 사용하는 방법은 GlobalFilter 인터페이스를 구현하여 filter를 재정의 하면 된다.
@Slf4j
@Component
public class MacaronCustomGlobalFilter implements GlobalFilter, Ordered {
@Bean
public GlobalFilter customFilter() {
return new MacaronCustomGlobalFilter();
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("##################### custom global filter #####################");
return chain.filter(exchange);
}
@Override
public int getOrder() {
return -1;
}
}
아래와 같이 ApiGateWay를 호출하면 log가 보임.
2021-01-06 09:14:27.011 DEBUG 29800 --- [ctor-http-nio-3] o.s.c.g.handler.FilteringWebHandler :
Sorted gatewayFilterFactories:
[
[GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.RemoveCachedBodyFilter@1859e55c}, order = -2147483648],
[GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.AdaptCachedBodyGlobalFilter@cb03411}, order = -2147482648],
[GatewayFilterAdapter{delegate=com.kst.macaront.gw.filter.MacaronCustomGlobalFilter@53079ae6}, order = -1],
[GatewayFilterAdapter{delegate=com.kst.macaront.gw.filter.MacaronCustomGlobalFilter@718ad3a6}, order = -1],
[GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.NettyWriteResponseFilter@3f6906f4}, order = -1],
[GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.ForwardPathFilter@d229912}, order = 0],
[
[StripPrefix parts = 1], order = 0
],
[GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.RouteToRequestUrlFilter@190bf8e4}, order = 10000],
[GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.LoadBalancerClientFilter@603c2dee}, order = 10100],
[GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.WebsocketRoutingFilter@5a06eeef}, order = 2147483646],
[GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.NettyRoutingFilter@50d666a2}, order = 2147483647],
[GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.ForwardRoutingFilter@7a8b7e11}, order = 2147483647]
]
gateway filter factory에 2번 등록되어져서 2번 호출되는 이유는 찾아 봐야 할 듯
아래부터는 기 구현되어져 있는 Global Filter들이다.
Forward Routing Filter
매뉴얼( docs.spring.io/spring-cloud-gateway/docs/2.2.6.BUILD-SNAPSHOT/reference/html/#forward-routing-filter ) 상 내용에서는 url scheme에서 forward:// 패턴이 있게 된다면 DispatcherHandler 가 처리하게 된다고 함.
이와 관련하여 볼 수 있는 소스는 아래에 있음.
org.springframework.cloud.gateway.filter.ForwardRoutingFilter.java
public class ForwardRoutingFilter implements GlobalFilter, Ordered {
private static final Log log = LogFactory.getLog(ForwardRoutingFilter.class);
private final ObjectProvider<DispatcherHandler> dispatcherHandlerProvider;
// do not use this dispatcherHandler directly, use getDispatcherHandler() instead.
private volatile DispatcherHandler dispatcherHandler;
public ForwardRoutingFilter(
ObjectProvider<DispatcherHandler> dispatcherHandlerProvider) {
this.dispatcherHandlerProvider = dispatcherHandlerProvider;
}
private DispatcherHandler getDispatcherHandler() {
if (dispatcherHandler == null) {
dispatcherHandler = dispatcherHandlerProvider.getIfAvailable();
}
return dispatcherHandler;
}
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
URI requestUrl = exchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR);
String scheme = requestUrl.getScheme();
if (isAlreadyRouted(exchange) || !"forward".equals(scheme)) {
return chain.filter(exchange);
}
// TODO: translate url?
if (log.isTraceEnabled()) {
log.trace("Forwarding to URI: " + requestUrl);
}
return this.getDispatcherHandler().handle(exchange);
}
}
소스에서 볼 수 있는 것 처럼 scheme 검사하고 forward일 경우 dispatcherHandler 통해서 처리하는 것으로 보임.
순서는 Ordered.LOWEST_PRECEDENCE 라고 선언해 두었는데 해당 값을 찾아 보면 "int LOWEST_PRECEDENCE = Integer.MAX_VALUE;"라고선언되어있음. 가장 마지막에 실행되는 것으로 보임
사용은 아래 소스에서 기본적으로 Bean 생성해서 사용하는 것으로 보임.
package org.springframework.cloud.gateway.config;
/**
* @author Spencer Gibb
* @author Ziemowit Stolarczyk
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(name = "spring.cloud.gateway.enabled", matchIfMissing = true)
@EnableConfigurationProperties
@AutoConfigureBefore({ HttpHandlerAutoConfiguration.class,
WebFluxAutoConfiguration.class })
@AutoConfigureAfter({ GatewayLoadBalancerClientAutoConfiguration.class,
GatewayClassPathWarningAutoConfiguration.class })
@ConditionalOnClass(DispatcherHandler.class)
public class GatewayAutoConfiguration {
...
@Bean
@ConditionalOnEnabledGlobalFilter
public ForwardRoutingFilter forwardRoutingFilter(
ObjectProvider<DispatcherHandler> dispatcherHandler) {
return new ForwardRoutingFilter(dispatcherHandler);
}
LoadBalancerClientFilter
url scheme에 lb:// 이 있다면 "이름"의 실제 호스트, 포트 정보를 resolve 해서 호출하게 됨.
예를 들어 eureka를 쓰는 경우 lb://myservice scheme 으로 호출하게 되면 eureka에서 해당 서비스의 이름을 쓰는 인스턴스 중 하나를 리턴하여 호출하게 됨.
관련된 필터 소스는 아래와 같음.
package org.springframework.cloud.gateway.filter;
/**
* @deprecated in favour of {@link ReactiveLoadBalancerClientFilter}
* @author Spencer Gibb
* @author Tim Ysewyn
*/
@Deprecated
public class LoadBalancerClientFilter implements GlobalFilter, Ordered {
/**
* Filter order for {@link LoadBalancerClientFilter}.
*/
public static final int LOAD_BALANCER_CLIENT_FILTER_ORDER = 10100;
private static final Log log = LogFactory.getLog(LoadBalancerClientFilter.class);
protected final LoadBalancerClient loadBalancer;
private LoadBalancerProperties properties;
public LoadBalancerClientFilter(LoadBalancerClient loadBalancer,
LoadBalancerProperties properties) {
this.loadBalancer = loadBalancer;
this.properties = properties;
}
@Override
public int getOrder() {
return LOAD_BALANCER_CLIENT_FILTER_ORDER;
}
@Override
@SuppressWarnings("Duplicates")
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
URI url = exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR);
String schemePrefix = exchange.getAttribute(GATEWAY_SCHEME_PREFIX_ATTR);
if (url == null
|| (!"lb".equals(url.getScheme()) && !"lb".equals(schemePrefix))) {
return chain.filter(exchange);
}
// preserve the original url
addOriginalRequestUrl(exchange, url);
if (log.isTraceEnabled()) {
log.trace("LoadBalancerClientFilter url before: " + url);
}
final ServiceInstance instance = choose(exchange);
if (instance == null) {
throw NotFoundException.create(properties.isUse404(),
"Unable to find instance for " + url.getHost());
}
URI uri = exchange.getRequest().getURI();
// if the `lb:<scheme>` mechanism was used, use `<scheme>` as the default,
// if the loadbalancer doesn't provide one.
String overrideScheme = instance.isSecure() ? "https" : "http";
if (schemePrefix != null) {
overrideScheme = url.getScheme();
}
URI requestUrl = loadBalancer.reconstructURI(
new DelegatingServiceInstance(instance, overrideScheme), uri);
if (log.isTraceEnabled()) {
log.trace("LoadBalancerClientFilter url chosen: " + requestUrl);
}
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUrl);
return chain.filter(exchange);
}
protected ServiceInstance choose(ServerWebExchange exchange) {
return loadBalancer.choose(
((URI) exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR)).getHost());
}
}
Deprecated 된 이유는 매뉴얼에서 알려주는 다음의 이유 때문인 것으로 보임
위의 내용으로 인해 ReactiveLoadBalancerClientFilter 를 앞으로 사용해야 하는 듯.
관련된 내용은 spring.io/blog/2020/03/25/spring-tips-spring-cloud-loadbalancer 참고하면 좋을 것으로 보임.
order 순서도 "public static final int LOAD_BALANCER_CLIENT_FILTER_ORDER = 10100;" 와 같이 설정되어 있는데 정확한 의미는 모르겠음.
service를 못 찾을 경우 기본 설정 상 503 코드가 리턴 되지만 다음과 같이 yml 파일에 설정하면 404로 리턴되게 됨.
spring:
cloud:
gateway:
loadbalancer:
use404: true
ReactiveLoadBalancerClientFilter
기본적인 내용은 LoadBalancerClientFilter 와 같아 보이며 위에서 이야기 했듯이 spring.cloud.loadbalancer.ribbon.enabled 값을 false로 하게 될 경우 사용하게 된다.
관련된 소스는 너무 길어 package 및 Class 이름만 참고.
org.springframework.cloud.gateway.filter.ReactiveLoadBalancerClientFilter.java
ribbon.enabled 값을 false로 하고 호출하게 될 경우 아래와 같이 gateway filter factory의 순서가 바뀌는 것을 볼 수 있다.
2021-01-06 09:00:25.590 DEBUG 20820 --- [ctor-http-nio-3] o.s.c.g.handler.FilteringWebHandler :
Sorted gatewayFilterFactories:
[
[GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.RemoveCachedBodyFilter@3149409c}, order = -2147483648],
[GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.AdaptCachedBodyGlobalFilter@367b22e5}, order = -2147482648],
[GatewayFilterAdapter{delegate=com.kst.macaront.gw.filter.MacaronCustomGlobalFilter@6528d339}, order = -1],
[GatewayFilterAdapter{delegate=com.kst.macaront.gw.filter.MacaronCustomGlobalFilter@2dd2ff87}, order = -1],
[GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.NettyWriteResponseFilter@bdda8a7}, order = -1],
[GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.ForwardPathFilter@2a9f8d47}, order = 0],
[
[StripPrefix parts = 1], order = 0
],
[GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.RouteToRequestUrlFilter@1c421b0f}, order = 10000],
[GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.ReactiveLoadBalancerClientFilter@6a38e3d1}, order = 10150],
[GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.WebsocketRoutingFilter@51297528}, order = 2147483646],
[GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.NettyRoutingFilter@28cf179c}, order = 2147483647],
[GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.ForwardRoutingFilter@4ce18cec}, order = 2147483647]
]
기존에는 LoadBalancerClientFilter를 사용하는 모습을 볼 수 있었지만 LoadBalancerClientFilter는 없어지고 대신에 ReactiveLoadBalancerClientFilter가 로그에서 보여진다.
NettyRoutingFilter, NettyWriteResponseFilter
netty를 사용하는 필터 같은데 정확히는 모르겠음.
RouteToRequestUrlFilter
scheme 패턴을 검토하고 url을 생성해서 proxy 해주는 필터로 보임.
RouteToRequestUrlFilter.java의 filter mehtod를 디버깅 해 보면 아래와 같이 url을 생성해서 다음 filter로 넘겨 줌.
http://localhost:8100/chauffeur/areas?areaType=STATE&parentCode=KR00 로 요청을 하였지만 route.predicate의 룰에 따라 http://localhost:8100/areas?areaType=STATE&parentCode=KR00 로 바뀌게 됨.
route 해줄 uri가 lb://CHAUFFEUR 이기 때문에 from인 http://localhost:8100을 lb://CHAUFFEUR로 바뀌게 된다.
WebsocketRoutingFilter
웹소켓 라우팅 필터로 ws://로 진입 시 해당 서비스로 proxy 해 준다.
GatewayMetricsFilter
gateway의 상태 정보를 보여주기 위한 용도로 사용 하는 것으로 보임.
springboot actuator를 사용하기 때문에 implementation 'org.springframework.boot:spring-boot-starter-actuator' 를 의존성에 넣어야 하며 metrics 관련 값은 다음과 같이 셋팅 되어 있어야 함.
spring:
cloud:
gateway:
metrics:
enabled: true
http://localhost:8100/actuator/metrics/gateway.requests 와 같이 접속하면 내용이 나온다고 하는데 잘 안나옴. 정확한 이유 모르겠음.
Marking An Exchange As Routed
중복되는 routing을 안하기 위해 ServerWebExchange의 gatewayAlreadyRouted 속성을 사용하여 판단한다. 이미 routing이 되었다면 다시 routing 하지 않고 skip 처리한다.
이를 사용하기 위해서 ServerWebExchangeUtils 의 GATEWAY_ALREADY_ROUTED_ATTR 상수를 사용한다.
/**
* Used when a routing filter has been successfully called. Allows users to write
* custom routing filters that disable built in routing filters.
*/
public static final String GATEWAY_ALREADY_ROUTED_ATTR = qualify(
"gatewayAlreadyRouted");
해당 값은 다음과 같은 메소드를 이용하여 판단하거나 저장한다.
public static void setAlreadyRouted(ServerWebExchange exchange) {
exchange.getAttributes().put(GATEWAY_ALREADY_ROUTED_ATTR, true);
}
public static void removeAlreadyRouted(ServerWebExchange exchange) {
exchange.getAttributes().remove(GATEWAY_ALREADY_ROUTED_ATTR);
}
public static boolean isAlreadyRouted(ServerWebExchange exchange) {
return exchange.getAttributeOrDefault(GATEWAY_ALREADY_ROUTED_ATTR, false);
}
'Architecture > MSA' 카테고리의 다른 글
Spring Cloud Gateway - HttpHeadersFilters (0) | 2021.01.07 |
---|---|
Spring Cloud Gateway - Custom Predicate with AbstractRoutePredicateFactory (0) | 2021.01.06 |
Spring Cloud Gateway - Route Predicate & Gateway Filter Factory (0) | 2020.12.28 |
Hystrix, Feign을 이용한 개발 (0) | 2020.12.18 |
Zuul을 이용한 Gateway 구축 시 설정 (0) | 2020.12.11 |