SpringCloud Gateway路由核心原理解析
一、介紹
SpringCloud Gateway 是 Spring 官方基于 Spring Spring Boot 和 Project Reactor 等技術開發(fā)的網關,Gateway 旨在為微服務架構提供一種簡單而有效的統(tǒng)一的API路由管理方式。Gateway作為 Spring Cloud 生態(tài)系中的網關,目標是替代 ZUUL,其不僅提供統(tǒng)一的路由方式,并且基于 Filter 鏈的方式提供了關基本的功能,例如:安全,監(jiān)控/埋點,和限流等。
1.1 工作原理
1.2 重要概念
- 路由(Route): 路由是Gateway的基礎構建塊。它由ID、目標URL、一組斷言和過濾器定義。如果斷言評估為true,則路由匹配并執(zhí)行相關的過濾器鏈。
- 斷言(Predicate): 輸入的請求會被一組斷言評估,如果斷言為true,則路由匹配。常用的斷言包括路徑匹配、查詢參數(shù)匹配、請求頭匹配等。
- 過濾器(Filter): 過濾器在特定的生命周期中攔截并修改請求和響應。過濾器可以作用在代理前、代理后、或者出錯時。常見的過濾器有:添加響應頭、限流、日志記錄等。
二、基本應用
2.1 構建項目
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: # 網關路由配置 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
內容,結果如下:
三、核心原理解析
3.1 GatewayAutoConfiguration
根據(jù)約定大于配置思想,結合springboot start原理,可以找到 Gateway自動注入類。
結合SpringMVC底層原理分析,Url的匹配,是在 HandlerMapping 中找到對應的 HandlerAdaptor 后進行處理,Gateway也運用了這一點,核心存在一個 RoutePredicateHandlerMapping 類,用于根據(jù)請求url以及配置的所有路由,查找到一個 Route(路由),在進行相關處理后,進行Http調用。
@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進行匹配,找到對應的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); }
則最終會調用到 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); } // 構建一個FilterChain,最終進行調用 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排序后,構建成一個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)); } }
到此這篇關于SpringCloud Gateway路由核心原理解析的文章就介紹到這了,更多相關SpringCloud Gateway路由內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Java解析JSON數(shù)據(jù)時報錯問題解決方案
這篇文章主要介紹了Java解析JSON數(shù)據(jù)時報錯問題解決方案,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2020-10-10Java設計模式之單態(tài)模式(Singleton模式)介紹
這篇文章主要介紹了Java設計模式之單態(tài)模式(Singleton模式)介紹,本文講解了如何使用單例模式、使用單例模式注意事項等內容,需要的朋友可以參考下2015-03-03