SpringCloud Gateway路由核心原理解析
一、介紹
SpringCloud Gateway 是 Spring 官方基于 Spring Spring Boot 和 Project Reactor 等技術(shù)開發(fā)的網(wǎng)關(guān),Gateway 旨在為微服務(wù)架構(gòu)提供一種簡單而有效的統(tǒng)一的API路由管理方式。Gateway作為 Spring Cloud 生態(tài)系中的網(wǎng)關(guān),目標是替代 ZUUL,其不僅提供統(tǒng)一的路由方式,并且基于 Filter 鏈的方式提供了關(guān)基本的功能,例如:安全,監(jiān)控/埋點,和限流等。
1.1 工作原理

1.2 重要概念
- 路由(Route): 路由是Gateway的基礎(chǔ)構(gòu)建塊。它由ID、目標URL、一組斷言和過濾器定義。如果斷言評估為true,則路由匹配并執(zhí)行相關(guān)的過濾器鏈。
- 斷言(Predicate): 輸入的請求會被一組斷言評估,如果斷言為true,則路由匹配。常用的斷言包括路徑匹配、查詢參數(shù)匹配、請求頭匹配等。
- 過濾器(Filter): 過濾器在特定的生命周期中攔截并修改請求和響應(yīng)。過濾器可以作用在代理前、代理后、或者出錯時。常見的過濾器有:添加響應(yīng)頭、限流、日志記錄等。
二、基本應(yīng)用
2.1 構(gòu)建項目
pom.xml
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.6.13</spring-boot.version>
<spring-cloud.version>2021.0.5</spring-cloud.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2021.0.5</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
</dependencies>2.2 添加yaml路由配置
spring:
cloud:
gateway:
# 網(wǎng)關(guān)路由配置
routes:
# 路由id,自定義,只要唯一即可
- id: provider-baidu
# 路由的目標地址
uri: https://www.baidu.com
predicates: # 路由斷言,判斷請求是否符合路由規(guī)則的條件
- Path=/s/** # 按照路徑匹配(PathRoutePredicateFactory),以/s/開頭的請求就符合要求
- id: provider-csdn
# 路由的目標地址
uri: https://blog.csdn.net/
predicates: # 路由斷言,判斷請求是否符合路由規(guī)則的條件
- Path=/csdn/** # 按照路徑匹配(PathRoutePredicateFactory),以/csdn/開頭的請求就符合要求 2.3 測試
訪問 http://localhost:8080/s?wd=1,則自動顯示 https://www.baidu.com/s?wd=1 內(nèi)容,結(jié)果如下:


三、核心原理解析
3.1 GatewayAutoConfiguration
根據(jù)約定大于配置思想,結(jié)合springboot start原理,可以找到 Gateway自動注入類。

結(jié)合SpringMVC底層原理分析,Url的匹配,是在 HandlerMapping 中找到對應(yīng)的 HandlerAdaptor 后進行處理,Gateway也運用了這一點,核心存在一個 RoutePredicateHandlerMapping 類,用于根據(jù)請求url以及配置的所有路由,查找到一個 Route(路由),在進行相關(guān)處理后,進行Http調(diào)用。
@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 RoutePredicateHandlerMapping routePredicateHandlerMapping(FilteringWebHandler webHandler,
RouteLocator routeLocator, GlobalCorsProperties globalCorsProperties, Environment environment) {
return new RoutePredicateHandlerMapping(webHandler, routeLocator, globalCorsProperties, environment);
}
}3.2 RoutePredicateHandlerMapping

RoutePredicateHandlerMapping 繼承了 AbstractHandlerMapping 類,實現(xiàn)了
public abstract class AbstractHandlerMapping extends ApplicationObjectSupport
implements HandlerMapping, Ordered, BeanNameAware {
@Override
public Mono<Object> getHandler(ServerWebExchange exchange) {
return getHandlerInternal(exchange).map(handler -> {
if (logger.isDebugEnabled()) {
logger.debug(exchange.getLogPrefix() + "Mapped to " + handler);
}
ServerHttpRequest request = exchange.getRequest();
if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
CorsConfiguration config = (this.corsConfigurationSource != null ?
this.corsConfigurationSource.getCorsConfiguration(exchange) : null);
CorsConfiguration handlerConfig = getCorsConfiguration(handler, exchange);
config = (config != null ? config.combine(handlerConfig) : handlerConfig);
if (config != null) {
config.validateAllowCredentials();
}
if (!this.corsProcessor.process(config, exchange) || CorsUtils.isPreFlightRequest(request)) {
return NO_OP_HANDLER;
}
}
return handler;
});
}
/**
* 查找給定請求的處理程序,如果未找到特定的處理程序,則返回空的
*/
protected abstract Mono<?> getHandlerInternal(ServerWebExchange exchange);
}則 RoutePredicateHandlerMapping#getHandlerInternal 核心代碼如下:
public class RoutePredicateHandlerMapping extends AbstractHandlerMapping {
@Override
protected Mono<?> getHandlerInternal(ServerWebExchange exchange) {
// don't handle requests on management port if set and different than server port
if (this.managementPortType == DIFFERENT && this.managementPort != null
&& exchange.getRequest().getURI().getPort() == this.managementPort) {
return Mono.empty();
}
exchange.getAttributes().put(GATEWAY_HANDLER_MAPPER_ATTR, getSimpleName());
return lookupRoute(exchange) // 查找路由
// .log("route-predicate-handler-mapping", Level.FINER) //name this
.flatMap((Function<Route, Mono<?>>) r -> {
exchange.getAttributes().remove(GATEWAY_PREDICATE_ROUTE_ATTR);
if (logger.isDebugEnabled()) {
logger.debug("Mapping [" + getExchangeDesc(exchange) + "] to " + r);
}
exchange.getAttributes().put(GATEWAY_ROUTE_ATTR, r);
return Mono.just(webHandler);
}).switchIfEmpty(Mono.empty().then(Mono.fromRunnable(() -> {
exchange.getAttributes().remove(GATEWAY_PREDICATE_ROUTE_ATTR);
if (logger.isTraceEnabled()) {
logger.trace("No RouteDefinition found for [" + getExchangeDesc(exchange) + "]");
}
})));
}
/**
* 查找路由
*/
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);
})
// instead of immediately stopping main flux due to error, log and
// swallow it
.doOnError(e -> logger.error("Error applying predicate for route: " + route.getId(), e))
.onErrorResume(e -> Mono.empty()))
// .defaultIfEmpty() put a static Route not found
// or .switchIfEmpty()
// .switchIfEmpty(Mono.<Route>empty().log("noroute"))
.next()
// TODO: error handling
.map(route -> {
if (logger.isDebugEnabled()) {
logger.debug("Route matched: " + route.getId());
}
validateRoute(route, exchange);
return route;
});
}
}其中,final RouteLocator routeLocator; 對象存儲了所有配置的路由(route),如下:

Gateway會根據(jù)斷言Predicate進行匹配,找到對應(yīng)的Route,再進行路由等其他處理。
Route
public class Route implements Ordered {
private final String id;
private final URI uri;
private final int order;
private final AsyncPredicate<ServerWebExchange> predicate;
private final List<GatewayFilter> gatewayFilters;
private final Map<String, Object> metadata;
private Route(String id, URI uri, int order, AsyncPredicate<ServerWebExchange> predicate,
List<GatewayFilter> gatewayFilters, Map<String, Object> metadata) {
this.id = id;
this.uri = uri;
this.order = order;
this.predicate = predicate;
this.gatewayFilters = gatewayFilters;
this.metadata = metadata;
}
}RoutePredicateHandlerMapping#getHandler 如果匹配成功,最終會返回一個處理 FilteringWebHandler 類,如下:

3.3 FilteringWebHandler

WebHandler
/**
* 用于處理 Web 請求的協(xié)定。
*/
public interface WebHandler {
/**
* Handle the web server exchange.
* @param exchange the current server exchange
* @return {@code Mono<Void>} to indicate when request handling is complete
*/
Mono<Void> handle(ServerWebExchange exchange);
}則最終會調(diào)用到 FilteringWebHandler#handle 方法中:
public class FilteringWebHandler implements WebHandler {
@Override
public Mono<Void> handle(ServerWebExchange exchange) {
Route route = exchange.getRequiredAttribute(GATEWAY_ROUTE_ATTR);
// 獲取當前 Route 配置的Filter
List<GatewayFilter> gatewayFilters = route.getFilters();
// 獲取全局的Filter
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);
}
// 構(gòu)建一個FilterChain,最終進行調(diào)用
return new DefaultGatewayFilterChain(combined).filter(exchange);
}
private static class DefaultGatewayFilterChain implements GatewayFilterChain {
private final int index;
private final List<GatewayFilter> filters;
DefaultGatewayFilterChain(List<GatewayFilter> filters) {
this.filters = filters;
this.index = 0;
}
private DefaultGatewayFilterChain(DefaultGatewayFilterChain parent, int index) {
this.filters = parent.getFilters();
this.index = index;
}
public List<GatewayFilter> getFilters() {
return filters;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange) {
return Mono.defer(() -> {
if (this.index < filters.size()) {
GatewayFilter filter = filters.get(this.index);
DefaultGatewayFilterChain chain = new DefaultGatewayFilterChain(this, this.index + 1);
return filter.filter(exchange, chain);
}
else {
return Mono.empty(); // complete
}
});
}
}
}handle 方法處理邏輯為:
- 獲取當前 Route 配置的Filter;
- 獲取全局的Filter;
- 將所有的Filter排序后,構(gòu)建成一個FilterChain;
- 在所有的Filter中,根據(jù)具體協(xié)議選擇最終的Filter,進行最終路由;
3.4 NettyRoutingFilter
NettyRoutingFilter 為最終進行Http路由的類。
public class NettyRoutingFilter implements GlobalFilter, Ordered {
@Override
@SuppressWarnings("Duplicates")
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
URI requestUrl = exchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR);
String scheme = requestUrl.getScheme();
if (isAlreadyRouted(exchange) || (!"http".equalsIgnoreCase(scheme) && !"https".equalsIgnoreCase(scheme))) {
return chain.filter(exchange);
}
setAlreadyRouted(exchange);
ServerHttpRequest request = exchange.getRequest();
final HttpMethod method = HttpMethod.valueOf(request.getMethodValue());
final String url = requestUrl.toASCIIString();
HttpHeaders filtered = filterRequest(getHeadersFilters(), exchange);
final DefaultHttpHeaders httpHeaders = new DefaultHttpHeaders();
filtered.forEach(httpHeaders::set);
boolean preserveHost = exchange.getAttributeOrDefault(PRESERVE_HOST_HEADER_ATTRIBUTE, false);
Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR);
// 獲取HttpClient,并進行send
Flux<HttpClientResponse> responseFlux = getHttpClient(route, exchange).headers(headers -> {
headers.add(httpHeaders);
// Will either be set below, or later by Netty
headers.remove(HttpHeaders.HOST);
if (preserveHost) {
String host = request.getHeaders().getFirst(HttpHeaders.HOST);
headers.add(HttpHeaders.HOST, host);
}
}).request(method).uri(url).send((req, nettyOutbound) -> {
if (log.isTraceEnabled()) {
nettyOutbound.withConnection(connection -> log.trace("outbound route: "
+ connection.channel().id().asShortText() + ", inbound: " + exchange.getLogPrefix()));
}
return nettyOutbound.send(request.getBody().map(this::getByteBuf));
}).responseConnection((res, connection) -> {
// Defer committing the response until all route filters have run
// Put client response as ServerWebExchange attribute and write
// response later NettyWriteResponseFilter
exchange.getAttributes().put(CLIENT_RESPONSE_ATTR, res);
exchange.getAttributes().put(CLIENT_RESPONSE_CONN_ATTR, connection);
ServerHttpResponse response = exchange.getResponse();
// put headers and status so filters can modify the response
HttpHeaders headers = new HttpHeaders();
res.responseHeaders().forEach(entry -> headers.add(entry.getKey(), entry.getValue()));
String contentTypeValue = headers.getFirst(HttpHeaders.CONTENT_TYPE);
if (StringUtils.hasLength(contentTypeValue)) {
exchange.getAttributes().put(ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR, contentTypeValue);
}
setResponseStatus(res, response);
// make sure headers filters run after setting status so it is
// available in response
HttpHeaders filteredResponseHeaders = HttpHeadersFilter.filter(getHeadersFilters(), headers, exchange,
Type.RESPONSE);
if (!filteredResponseHeaders.containsKey(HttpHeaders.TRANSFER_ENCODING)
&& filteredResponseHeaders.containsKey(HttpHeaders.CONTENT_LENGTH)) {
// It is not valid to have both the transfer-encoding header and
// the content-length header.
// Remove the transfer-encoding header in the response if the
// content-length header is present.
response.getHeaders().remove(HttpHeaders.TRANSFER_ENCODING);
}
exchange.getAttributes().put(CLIENT_RESPONSE_HEADER_NAMES, filteredResponseHeaders.keySet());
response.getHeaders().addAll(filteredResponseHeaders);
return Mono.just(res);
});
Duration responseTimeout = getResponseTimeout(route);
if (responseTimeout != null) {
responseFlux = responseFlux
.timeout(responseTimeout,
Mono.error(new TimeoutException("Response took longer than timeout: " + responseTimeout)))
.onErrorMap(TimeoutException.class,
th -> new ResponseStatusException(HttpStatus.GATEWAY_TIMEOUT, th.getMessage(), th));
}
return responseFlux.then(chain.filter(exchange));
}
}到此這篇關(guān)于SpringCloud Gateway路由核心原理解析的文章就介紹到這了,更多相關(guān)SpringCloud Gateway路由內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- 解決SpringCloud gateway網(wǎng)關(guān)配置MVC攔截器報錯問題
- SpringCloud Gateway 權(quán)限認證的實現(xiàn)
- SpringCloud Gateway中斷言路由和過濾器的使用詳解
- SpringCloud Zuul和Gateway的實例代碼(搭建方式)
- SpringCloudGateway路由失效問題
- SpringCloud GateWay動態(tài)路由用法
- SpringCloud-Gateway網(wǎng)關(guān)的使用實例教程
- 使用SpringCloud Gateway解決跨域問題
- SpringCloud網(wǎng)關(guān)Gateway功能實現(xiàn)
相關(guān)文章
springboot2.X整合prometheus監(jiān)控的實例講解
這篇文章主要介紹了springboot2.X整合prometheus監(jiān)控的實例講解,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-03-03
win10和win7下java開發(fā)環(huán)境配置教程
這篇文章主要為大家詳細介紹了win7下Java開發(fā)環(huán)境配置教程,win10下Java開發(fā)環(huán)境配置,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-06-06
SpringBoot中yml多環(huán)境配置的3種方法
這篇文章主要給大家介紹了SpringBoot中yml多環(huán)境配置的3種方法,文中有詳細的代碼示例供大家參考,對大家的學習或工作有一定的幫助,需要的朋友可以參考下2023-10-10
spring-boot-starter-security的簡單使用方式
文章介紹了三種使用Spring Boot Security的方法:基于配置文件、基于配置類和基于注解的方式,通過這些方法,可以實現(xiàn)對Web應(yīng)用的權(quán)限控制,確保只有授權(quán)用戶才能訪問特定資源2024-11-11

