spring cloud gateway系列教程目錄
- spring cloud gateway系列教程1—Route Predicate
- spring cloud gateway系列教程2——GatewayFilter_上篇
- spring cloud gateway系列教程2——GatewayFilter_下篇
- spring cloud gateway系列教程3—Global Filters
- spring cloud gateway系列教程4—其他配置
Global Filters
GlobalFilter
接口方法和GatewayFilter
是一樣的苗分,GlobalFilter
特別之處在于它的作用是全局的厌蔽。
1. Combined Global Filter and GatewayFilter Ordering
當(dāng)請求到來時(shí),Filtering Web Handler
處理器會(huì)添加所有GlobalFilter
實(shí)例和匹配的GatewayFilter
實(shí)例到過濾器鏈中摔癣,通過對filter
bean配置注解@Order
奴饮,則過濾器鏈會(huì)對這些過濾器實(shí)例bean
進(jìn)行排序纬向。
Spring Cloud Gateway將過濾器的邏輯按請求執(zhí)行點(diǎn)分為”pre"和"post"的一前一后處理,如果是高優(yōu)先級(jí)的過濾器戴卜,則在"pre"邏輯中最先執(zhí)行罢猪,在"post"邏輯中最后執(zhí)行。
ExampleConfiguration.java.
@Bean
@Order(-1)
public GlobalFilter a() {
return (exchange, chain) -> {
log.info("first pre filter");
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
log.info("third post filter");
}));
};
}
@Bean
@Order(0)
public GlobalFilter b() {
return (exchange, chain) -> {
log.info("second pre filter");
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
log.info("second post filter");
}));
};
}
@Bean
@Order(1)
public GlobalFilter c() {
return (exchange, chain) -> {
log.info("third pre filter");
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
log.info("first post filter");
}));
};
}
上面例子陸續(xù)會(huì)打印的是:
first pre filter
second pre filter
third pre filter
first post filter
second post filter
third post filter
2. Forward Routing Filter
ForwardRoutingFilter
會(huì)查看exchange的屬性ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR
的URI內(nèi)容叉瘩,如果url的scheme是forward
膳帕,比如:forward://localendpoint
,則它會(huì)使用Spirng的DispatcherHandler
來處理這個(gè)請求薇缅。
源碼實(shí)現(xiàn):
@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);
}
setAlreadyRouted(exchange);
//TODO: translate url?
if (log.isTraceEnabled()) {
log.trace("Forwarding to URI: "+requestUrl);
}
return this.dispatcherHandler.handle(exchange);
}
3. LoadBalancerClient Filter
LoadBalancerClientFilter
會(huì)查看exchange的屬性ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR
的URI內(nèi)容危彩,如果url的scheme是lb
,比如:lb://myservice
泳桦,或者是ServerWebExchangeUtils.GATEWAY_SCHEME_PREFIX_ATTR
屬性的內(nèi)容是lb
汤徽,則它會(huì)使用Spring Cloud的LoadBalancerClient
來將host轉(zhuǎn)化為實(shí)際的host和port,并以此替換屬性ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR
的內(nèi)容灸撰,原來的URL則會(huì)被添加到ServerWebExchangeUtils.GATEWAY_ORIGINAL_REQUEST_URL_ATTR
屬性的列表中谒府。
源碼實(shí)現(xiàn):
@Override
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);
log.trace("LoadBalancerClientFilter url before: " + url);
final ServiceInstance instance = loadBalancer.choose(url.getHost());
if (instance == null) {
throw new NotFoundException("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 = null;
if (schemePrefix != null) {
overrideScheme = url.getScheme();
}
URI requestUrl = loadBalancer.reconstructURI(new DelegatingServiceInstance(instance, overrideScheme), uri);
log.trace("LoadBalancerClientFilter url chosen: " + requestUrl);
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUrl);
return chain.filter(exchange);
}
application.yml.
spring:
cloud:
gateway:
routes:
- id: myRoute
uri: lb://service
predicates:
- Path=/service/**
默認(rèn)情況下,如果LoadBalancer
找不到服務(wù)實(shí)例浮毯,則會(huì)返回HTTP狀態(tài)碼503
完疫,你也可以通過修改spring.cloud.gateway.loadbalancer.use404=true
配置修改為返回狀態(tài)碼404
。
4. Netty Routing Filter
如果ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR
屬性中的url的scheme是http
或https
债蓝,則Netty Routing Filter才會(huì)執(zhí)行壳鹤,并使用Netty作為http請求客戶端對下游進(jìn)行代理請求。請求的響應(yīng)會(huì)放在exchange的ServerWebExchangeUtils.CLIENT_RESPONSE_ATTR
屬性中饰迹,以便后面的filter做進(jìn)一步的處理芳誓。
5. Netty Write Response Filter
如果NettyWriteResponseFilter
發(fā)現(xiàn)exchange的ServerWebExchangeUtils.CLIENT_RESPONSE_ATTR
屬性中存在Netty的HttpClientResponse
類型實(shí)例,在所有過濾器都執(zhí)行完畢后啊鸭,它會(huì)將響應(yīng)寫回到gateway客戶端的響應(yīng)中锹淌。
源碼實(shí)現(xiàn):
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// NOTICE: nothing in "pre" filter stage as CLIENT_RESPONSE_ATTR is not added
// until the WebHandler is run
return chain.filter(exchange).then(Mono.defer(() -> {
HttpClientResponse clientResponse = exchange.getAttribute(CLIENT_RESPONSE_ATTR);
if (clientResponse == null) {
return Mono.empty();
}
log.trace("NettyWriteResponseFilter start");
ServerHttpResponse response = exchange.getResponse();
NettyDataBufferFactory factory = (NettyDataBufferFactory) response.bufferFactory();
//TODO: what if it's not netty
final Flux<NettyDataBuffer> body = clientResponse.receive()
.retain() //TODO: needed?
.map(factory::wrap);
MediaType contentType = response.getHeaders().getContentType();
return (isStreamingMediaType(contentType) ?
response.writeAndFlushWith(body.map(Flux::just)) : response.writeWith(body));
}));
}
//TODO: use framework if possible
//TODO: port to WebClientWriteResponseFilter
private boolean isStreamingMediaType(@Nullable MediaType contentType) {
return (contentType != null && this.streamingMediaTypes.stream()
.anyMatch(contentType::isCompatibleWith));
}
6. RouteToRequestUrl Filter
如果exchange的ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR
屬性存放了Route
對象,則RouteToRequestUrlFilter
會(huì)根據(jù)基于請求的URI創(chuàng)建新的URI赠制,新的URI會(huì)更新到ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR
屬性中赂摆。
如果URI有scheme前綴,比如:lb:ws://serviceid
憎妙,lb
scheme截取出來放到ServerWebExchangeUtils.GATEWAY_SCHEME_PREFIX_ATTR
屬性中库正,方便后面的filter使用曲楚。
源碼實(shí)現(xiàn):
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR);
if (route == null) {
return chain.filter(exchange);
}
log.trace("RouteToRequestUrlFilter start");
URI uri = exchange.getRequest().getURI();
boolean encoded = containsEncodedParts(uri);
URI routeUri = route.getUri();
if (hasAnotherScheme(routeUri)) {
// this is a special url, save scheme to special attribute
// replace routeUri with schemeSpecificPart
exchange.getAttributes().put(GATEWAY_SCHEME_PREFIX_ATTR, routeUri.getScheme());
routeUri = URI.create(routeUri.getSchemeSpecificPart());
}
URI requestUrl = UriComponentsBuilder.fromUri(uri)
.uri(routeUri)
.build(encoded)
.toUri();
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUrl);
return chain.filter(exchange);
}
/* for testing */ static boolean hasAnotherScheme(URI uri) {
return schemePattern.matcher(uri.getSchemeSpecificPart()).matches() && uri.getHost() == null
&& uri.getRawPath() == null;
}
7. Websocket Routing Filter
如果請求URL的scheme是ws
或wss
的話厘唾,那么Websocket Routing Filter就會(huì)使用Spring Web Socket底層來處理對下游的請求轉(zhuǎn)發(fā)。
如果Websocket也使用了負(fù)載均衡龙誊,則需要這樣配置:lb:ws://serviceid
.
源碼實(shí)現(xiàn):
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
changeSchemeIfIsWebSocketUpgrade(exchange);
URI requestUrl = exchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR);
String scheme = requestUrl.getScheme();
if (isAlreadyRouted(exchange) || (!"ws".equals(scheme) && !"wss".equals(scheme))) {
return chain.filter(exchange);
}
setAlreadyRouted(exchange);
HttpHeaders headers = exchange.getRequest().getHeaders();
HttpHeaders filtered = filterRequest(getHeadersFilters(),
exchange);
List<String> protocols = headers.get(SEC_WEBSOCKET_PROTOCOL);
if (protocols != null) {
protocols = headers.get(SEC_WEBSOCKET_PROTOCOL).stream()
.flatMap(header -> Arrays.stream(commaDelimitedListToStringArray(header)))
.map(String::trim)
.collect(Collectors.toList());
}
return this.webSocketService.handleRequest(exchange,
new ProxyWebSocketHandler(requestUrl, this.webSocketClient,
filtered, protocols));
}
8. Making An Exchange As Routed
上面一些像ForwardRoutingFilter抚垃、Websocket Routing Filter的源碼中,都可以清楚看到gateway通過設(shè)置gatewayAlreadyRouted
標(biāo)識(shí)這個(gè)請求是否已經(jīng)路由轉(zhuǎn)發(fā)出去了,無需其他filter重復(fù)路由鹤树,這樣就可以避免重復(fù)錯(cuò)誤的路由操作铣焊,保證了路由的實(shí)現(xiàn)靈活性。
ServerWebExchangeUtils.isAlreadyRouted
檢查是否已被路由罕伯,ServerWebExchangeUtils.setAlreadyRouted
標(biāo)記已被路由狀態(tài)曲伊。
這一章介紹了Spring Cloud Gateway官方的Global Filters使用場景,下一章講gateway其他配置的使用追他。
如果想查看其他spring cloud gateway的案例和使用坟募,可以點(diǎn)擊查看