Spring Cloud Gateway學(xué)習(xí)

簡介

本文主要是用來學(xué)習(xí)Spring Cloud gateway網(wǎng)關(guān)使用的;API網(wǎng)關(guān)作為后端服務(wù)的統(tǒng)一入口,可提供請求路由、協(xié)議轉(zhuǎn)換、安全認(rèn)證、服務(wù)鑒權(quán)、流量控制、日志監(jiān)控等服務(wù)。

概覽

gateway網(wǎng)關(guān)處理流程圖如下:


網(wǎng)關(guān)
  1. 請求發(fā)送到網(wǎng)關(guān),DispatcherHandler是Http請求的中央分發(fā)器,將請求匹配到相應(yīng)的HandlerMapping;
  2. 請求和處理器之間有一個映射關(guān)系,網(wǎng)關(guān)將會對請求進(jìn)行路由,handler會匹配RoutePredicateHandlerMapping,以匹配到對應(yīng)的Route;
  3. 經(jīng)過RoutePredicateHandlerMapping處理后,請求會發(fā)送到Web處理器,該WebHandler代理了一系列網(wǎng)關(guān)過濾器和全局過濾,此時會對請求或者響應(yīng)頭進(jìn)行處理;
  4. 最后轉(zhuǎn)發(fā)到具體的代理服務(wù);

DispatcherHandler--->RoutePredicateHandlerMapping
RoutePredicateHandlerMapping---->FilteringWebHandler
FilteringWebHandler----->DefaultGatewayFilterChain

Route路由

什么是路由?一個路由就代表一個真實的下游請求路徑,路由在gateway里面有兩個重要的輔助概念-路由定義和定位器;

  • 路由定義RouteDefinition:路由定義是Gateway Properties中的屬性,可以定義獲取路由的方式;
  • 定位器Locator:定位器分為路由定位器RouteLocatorRouteDefinitionLocator,
    RouteLocator用來獲取routes,而RouteDefinitionLocator則用來獲RouteDefinitions;

RouteDefinition路由定義

路由定義有五個基本屬性:

  • id: 路由id,默認(rèn)為uuid;
  • predicates: 路由斷言定義列表;
  • fliters:該路由需要經(jīng)過的過濾器;
  • URI:該路由請求的路徑;
  • order:優(yōu)先級 ;

定位器

RouteLocator

用來獲取路由的定位器,其源碼如下:

public interface RouteLocator {

    Flux<Route> getRoutes();
}

其內(nèi)置了3個路由定位器實現(xiàn)類:

  • CachingRouteLocator:具備緩存功能的路由定位器,其內(nèi)具有監(jiān)聽RefreshRoutesEvent事件,并能及時刷新緩存;
    @EventListener(RefreshRoutesEvent.class)
    /* for testing */ void handleRefresh() {
        refresh();
    }
  • CompositeRouteLocator:組合路由定位器,其內(nèi)有代理了一系列的定位器,來實現(xiàn)路由查詢;
  • RouteDefinitionRouteLocator:基于路由定義的路由定位器,根據(jù)路由定義定位器獲取路由定義并將其轉(zhuǎn)換成對應(yīng)的路由;代碼如下:
public Flux<Route> getRoutes() {
        return this.routeDefinitionLocator.getRouteDefinitions()
                .map(this::convertToRoute)
                //TODO: error handling
                .map(route -> {
                    if (logger.isDebugEnabled()) {
                        logger.debug("RouteDefinition matched: " + route.getId());
                    }
                    return route;
                });
    }

也可以自定義一個路由定位器,如下代碼:



@SpringBootApplication
public class DemogatewayApplication {
    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
        return builder.routes()
            .route("path_route", r -> r.path("/get")
                .uri("http://httpbin.org"))//將所有/get開頭的請求路徑轉(zhuǎn)發(fā)到httpbin.org
            .route("host_route", r -> r.host("*.myhost.org")
                .uri("http://httpbin.org"))//將域名以myhost.org結(jié)尾的請求轉(zhuǎn)發(fā)
            .route("rewrite_route", r -> r.host("*.rewrite.org")
                .filters(f -> f.rewritePath("/foo/(?<segment>.*)", "/${segment}"))
                .uri("http://httpbin.org"))//將域名以myhost.org結(jié)尾的請求轉(zhuǎn)發(fā)并進(jìn)行路徑重寫,保留/foo/后面路徑;
            .route("hystrix_route", r -> r.host("*.hystrix.org")
                .filters(f -> f.hystrix(c -> c.setName("slowcmd")))
                .uri("http://httpbin.org"))
            .route("hystrix_fallback_route", r -> r.host("*.hystrixfallback.org")
                .filters(f -> f.hystrix(c -> c.setName("slowcmd").setFallbackUri("forward:/hystrixfallback")))
                .uri("http://httpbin.org"))
            .route("limit_route", r -> r
                .host("*.limited.org").and().path("/anything/**")
                .filters(f -> f.requestRateLimiter(c -> c.setRateLimiter(redisRateLimiter())))
                .uri("http://httpbin.org"))
            .build();
    }
}


路由定義定位器

路由定義定位器用來獲取路由定義的,路由定義可以在配置文件中進(jìn)行設(shè)置,gateway內(nèi)置了三個路由定義定位器:

  • CompositeRouteDefinitionLocator:其內(nèi)代理了一系列RouteDefinitionLocators,通過其來獲取路由定義集合;
  • RouteDefinitionRepository & InMemoryRouteDefinitionRepository:該定位器實現(xiàn)了路由定義的增刪改查等操作,gateway內(nèi)置端點接口會用到這些操作,同時也可以通過該路由定義定位器可以實現(xiàn)路由倉庫,來動態(tài)更新路由定義信息;
  • CachingRouteDefinitionLocator:帶有緩存功能路由定義定位器;
  • DiscoveryClientRouteDefinitionLocator:服務(wù)發(fā)現(xiàn)路由定義定位器,通過服務(wù)發(fā)現(xiàn)機(jī)制以及Spel表達(dá)式來動態(tài)更新路由定義信息;獲取路由源碼如下:
@Override
    public Flux<RouteDefinition> getRouteDefinitions() {
                 //獲取Spel表達(dá)式
        SpelExpressionParser parser = new SpelExpressionParser();
        Expression includeExpr = parser.parseExpression(properties.getIncludeExpression());
        Expression urlExpr = parser.parseExpression(properties.getUrlExpression());

        Predicate<ServiceInstance> includePredicate;
        if (properties.getIncludeExpression() == null || "true".equalsIgnoreCase(properties.getIncludeExpression())) {
            includePredicate = instance -> true;
        } else {
            includePredicate = instance -> {
                Boolean include = includeExpr.getValue(evalCtxt, instance, Boolean.class);
                if (include == null) {
                    return false;
                }
                return include;
            };
        }
              //獲取service clients以及其instances,通過serviceId替換的方式獲取路由定義
        return Flux.fromIterable(discoveryClient.getServices())
                .map(discoveryClient::getInstances)
                .filter(instances -> !instances.isEmpty())
                .map(instances -> instances.get(0))
                .filter(includePredicate)
                .map(instance -> {
                    String serviceId = instance.getServiceId();

                    RouteDefinition routeDefinition = new RouteDefinition();
                    routeDefinition.setId(this.routeIdPrefix + serviceId);
                    String uri = urlExpr.getValue(evalCtxt, instance, String.class);
                    routeDefinition.setUri(URI.create(uri));

                    final ServiceInstance instanceForEval = new DelegatingServiceInstance(instance, properties);

                    for (PredicateDefinition original : this.properties.getPredicates()) {
                        PredicateDefinition predicate = new PredicateDefinition();
                        predicate.setName(original.getName());
                        for (Map.Entry<String, String> entry : original.getArgs().entrySet()) {
                            String value = getValueFromExpr(evalCtxt, parser, instanceForEval, entry);
                            predicate.addArg(entry.getKey(), value);
                        }
                        routeDefinition.getPredicates().add(predicate);
                    }

                    for (FilterDefinition original : this.properties.getFilters()) {
                        FilterDefinition filter = new FilterDefinition();
                        filter.setName(original.getName());
                        for (Map.Entry<String, String> entry : original.getArgs().entrySet()) {
                            String value = getValueFromExpr(evalCtxt, parser, instanceForEval, entry);
                            filter.addArg(entry.getKey(), value);
                        }
                        routeDefinition.getFilters().add(filter);
                    }

                    return routeDefinition;
                });
    }
  • PropertiesRouteDefinitionLocator: 通過配置文件來獲取路由定義集合,其源碼如下:
@Override
    public Flux<RouteDefinition> getRouteDefinitions() {
        return Flux.fromIterable(this.properties.getRoutes());
    }

Predicate斷言

Predicate通過Predicate函數(shù)式接口來判斷當(dāng)前請求是否滿足選擇條件,Predicate分為以下條件劃分為不同的路由斷言工廠類

Datetime類型

根據(jù)日期判斷是否滿足路由選擇條件,其有三個工廠類:

  • AfteRoutePredicateFactory: 接收一個日期參數(shù),判斷請求日期是否晚于指定日志;
  • BeforeRoutePredicateFactory:接收一個日期參數(shù),判斷請求日期是否早于指定日期;
  • BetweenRoutePredicateFactory:接收兩個日期參數(shù),判斷請求日期是否位于之間;
    其配置如下:
spring:
    cloud: 
        gateway:
            routes: 
            - id: after_route_id
            uri: http://www.baidu.com
            predicates: 
            - After= 2018-12-30T23:59:59.789+08:00[Asia/Shanghai]

Cookie類型

根據(jù)請求的Cookie正則匹配是否通過選擇條件;
唯一工廠類:CookieRoutePredicateFactory;
其配置實例如下:

spring:
  cloud:
    gateway:
      routes:
      - id: cookie_route
        uri: https://example.org
        predicates:
        - Cookie=chocolate, ch.p

Header頭斷言類型

請求頭屬性正則匹配是否通過選擇,唯一工廠類:HeaderRoutePredicateFactory,其配置如下:

spring:
  cloud:
    gateway:
      routes:
      - id: header_route
        uri: https://example.org
        predicates:
        - Header=X-Request-Id, \d+

Host域名斷言類型

Host域名正則匹配,是否通過選擇,唯一工廠類:HostRoutePredicateFactory,
其配置如下:

spring:
  cloud:
    gateway:
      routes:
      - id: host_route
        uri: https://example.org
        predicates:
        - Host=**.somehost.org,**.anotherhost.org

Method斷言類型

基于方法類型來進(jìn)行相應(yīng)匹配選擇,唯一工廠類MethodRoutePredicateFactory;
其配置如下:

spring:
  cloud:
    gateway:
      routes:
      - id: method_route
        uri: https://example.org
        predicates:
        - Method=GET

Path斷言類型

Path斷言類型會通過接受的兩個參數(shù)來判斷是否匹配選擇,唯一工廠類:PathRoutePredicateFactory,其配置如下:

spring:
  cloud:
    gateway:
      routes:
      - id: host_route
        uri: https://example.org
        predicates:
        - Path=/foo/{segment},/bar/{segment}

Query查詢類型

Query類型通過接受兩個參數(shù):一個要求的參數(shù)和一個可選的regexp表達(dá)式,來匹配是否選擇,其工廠類為QueryRoutePredicateFactory,配置示例如下:

spring:
  cloud:
    gateway:
      routes:
      - id: query_route
        uri: https://example.org
        predicates:
        - Query=baz,ba.

路由必須滿足有一個baz的查詢參數(shù),且該參數(shù)值必須滿足ba.表達(dá)式,方可通過請求;

RemoteAddr類型

該類型通過設(shè)置一系列的IPv4或者IPv6地址以及子網(wǎng)掩碼形式的字符串,來進(jìn)行匹配,其唯一工廠類為:RemoteAddrRoutePredicateFactory;
其配置如下:

spring:
  cloud:
    gateway:
      routes:
      - id: remoteaddr_route
        uri: https://example.org
        predicates:
        - RemoteAddr=192.168.1.1/24

路由請求必須滿足192.168.1.1/24指定的ip域內(nèi)方可通過選擇;

Weight類型

Weight類型通過設(shè)置權(quán)重,來進(jìn)行路由選擇,其配置如下:

spring:
  cloud:
    gateway:
      routes:
      - id: weight_high
        uri: https://weighthigh.org
        predicates:
        - Weight=group1, 8
      - id: weight_low
        uri: https://weightlow.org
        predicates:
        - Weight=group1, 2

上例說明路由百分之80將發(fā)向https://weighthigh.org,百分20發(fā)向https://weightlow.org

Filter過濾器

配置文件

fliter需要在配置文件bootstrap.yml中進(jìn)行相應(yīng)設(shè)置,或者手動定義一個實現(xiàn)了Filter;
配置文件設(shè)置filter屬性如下:

    gateway:
        discovery:
           locator:
              enabled: true
              predicates:
                  -  Path='/api/**/'+serviceId+'/**'                 
              filters:
              - name: RewritePath
                args: 
                     regexp: "'/api/.*/' + serviceId + '/(?<remaining>.*)'"                 
                     replacement: "'/${remaining}'"

通過設(shè)置name便可調(diào)用相應(yīng)的過濾器工廠類創(chuàng)建一個過濾器,上例中,前綴RewritePath便會通過RewritePathGatewayFilterFactory創(chuàng)建一個GatewayFilter對象,來進(jìn)行相應(yīng)過濾操作;

GlobalFilter

gateway網(wǎng)關(guān)Filter分為以下Global和Define,其中Gobal有以下幾種(按ordered從小到大):

  • AdaptCachedBodyGlobalFilter(Integer.Min_VALU):通過適配緩存requestbody參數(shù)來實現(xiàn)request的重新構(gòu)建;

  • NettyWriteResponseFilter(-1):將當(dāng)前請求的響應(yīng)進(jìn)行輸出到服務(wù)調(diào)用方;

  • ForwardPathFilter(0):解析路徑并將路徑轉(zhuǎn)發(fā)

  • GatewayMetricsFilter(0):網(wǎng)關(guān)性能測量器,可以用來收集網(wǎng)關(guān)請求參數(shù),方便創(chuàng)建一個Grafana Dashbord;

  • RouteToRequestUrlFilter(10000):轉(zhuǎn)換路由中的URI

  • LoadBalancerClientFilter(10100):通過負(fù)載均衡客戶端根據(jù)路由的URL解析轉(zhuǎn)換成真實的請求URL

  • WebsocketRoutingFilter(Integer.MAX_VALUE-1):負(fù)責(zé)處理Websocket類型請求響應(yīng)信息;

  • ForwardRoutingFilter(Integer.MAX_VALUE):其根據(jù) forward:// 前綴( Scheme )過濾處理,將請求轉(zhuǎn)發(fā)到當(dāng)前網(wǎng)關(guān)實例本地接口。

  • NettyRoutingFilter(Integer.MAX_VALUE)):通過HttpClient客戶端轉(zhuǎn)發(fā)真實的URL請求到服務(wù)提供方,并將響應(yīng)寫入到當(dāng)前的請求響應(yīng)中;

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容