1. ?Java | Spring Cloud Gateway 使用和一些實(shí)現(xiàn)細(xì)節(jié)

        共 24194字,需瀏覽 49分鐘

         ·

        2021-07-03 12:31

        網(wǎng)關(guān)中間件

        所謂的API網(wǎng)關(guān),就是指系統(tǒng)的統(tǒng)一入口,它封裝了應(yīng)用程序的內(nèi)部結(jié)構(gòu),為客戶端提供統(tǒng)一服務(wù),一些與業(yè)務(wù)本身功能無關(guān)的公共邏輯可以在這里實(shí)現(xiàn),諸如認(rèn)證、鑒權(quán)、監(jiān)控、路由轉(zhuǎn)發(fā)等。

        Spring Cloud 圖片
        中間件NginxKongNetflix ZuulSpring Cloud Gatewayshenyu
        主要開發(fā)語言CLuaJavaJavaJava
        依賴關(guān)系基于 Nginx_Lua模塊
        支持協(xié)議HTTPHTTP, GRPCHTTP,HTTPHTTP, WebSocket, Dubbo, GRPC
        擴(kuò)展基于 Lua 腳本基于 Lua 腳本Java 寫過濾器Java 寫過濾器、斷言Java 寫插件
        編程模型多進(jìn)程 + io多路復(fù)用基于 NginxZuul 1 采用 Servlet, Zuul 2 采用 NettySpring WebFlux(Netty Reactor)Netty Reactor
        配置頁面豐富豐富
        負(fù)載均衡寫死的支持 Consul(間接可以支持使用 Consul 的 Spring Cloud)Spring Cloud 相關(guān)Spring Cloud 相關(guān)通過各種插件實(shí)現(xiàn)
        GitHubnginx/nginxKong/kongNetflix/zuulspring-cloud/spring-cloud-gatewayapache/incubator-shenyu

        Netflix Zuul 使用和一些實(shí)現(xiàn)

        Zuul 1 實(shí)現(xiàn)請(qǐng)求轉(zhuǎn)發(fā)的細(xì)節(jié)

        Spring Cloud Gateway 使用和一些實(shí)現(xiàn)細(xì)節(jié)

        官網(wǎng)地址:https://docs.spring.io/spring-cloud-gateway/docs/2.2.8.RELEASE/reference/html/


        默認(rèn)已經(jīng)提供的功能:

        • http 請(qǐng)求轉(zhuǎn)發(fā)和負(fù)責(zé)均衡

        • websocket 的請(qǐng)求轉(zhuǎn)發(fā)和負(fù)載均衡

        • 限流

        Spring Boot 項(xiàng)目中引入依賴,具體的版本號(hào)視情況而定。

        1<dependency>
        2  <groupId>org.springframework.cloud</groupId>
        3  <artifactId>spring-cloud-starter-gateway</artifactId>
        4</dependency>

        如果需要開啟負(fù)載均衡,需要引入對(duì)應(yīng)的依賴,比如使用 Nacos 則需要引入

        1<dependency>
        2  <groupId>com.alibaba.cloud</groupId>
        3  <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        4</dependency>

        日常使用

        Spring Cloud Gateway 相關(guān)配置均在 spring.cloud.gateway 下,需要配置均在這里

        一、全局跨域配置

         1spring:
        2  cloud:
        3    gateway:
        4      globalcors:
        5        cors-configurations:
        6          '[/**]':     // 全部請(qǐng)求
        7            allow-credentials: true
        8            allowed-origins: "*"
        9            allowed-headers: "*"
        10            allowed-methods: "*"
        11            max-age: 3600

        各個(gè)參數(shù)可以定制化

        二、負(fù)載均衡失效的配置

        如果請(qǐng)求時(shí),配置了負(fù)載均衡,且無法找對(duì)對(duì)應(yīng)的服務(wù)實(shí)例,默然返回 502,通過 loadbalancer.use404 可以將其改為 404 返回

        1spring:
        2  cloud:
        3    gateway:
        4      loadbalancer:
        5        use404: true

        三、各種謂詞路由的配置

        1.  時(shí)間謂詞路由

        這個(gè)主要控制某個(gè)時(shí)間范圍走指定的路由

        指定時(shí)間點(diǎn)之前

        1spring:
        2  cloud:
        3    gateway:
        4      routes:
        5      - id: before_route
        6        uri: https://example.org
        7        predicates:
        8        - Before=2017-01-20T17:42:47.789-07:00[America/Denver]

        指定時(shí)間段范圍內(nèi)

        1spring:
        2  cloud:
        3    gateway:
        4      routes:
        5      - id: between_route
        6        uri: https://example.org
        7        predicates:
        8        - Between=2017-01-20T17:42:47.789-07:00[America/Denver], 2017-01-21T17:42:47.789-07:00[America/Denver]

        指定時(shí)間點(diǎn)之后

        1spring:
        2  cloud:
        3    gateway:
        4      routes:
        5      - id: after_route
        6        uri: https://example.org
        7        predicates:
        8        - After=2017-01-20T17:42:47.789-07:00[America/Denver]

        2. Cookie 謂詞路由

        cookie 中指定 key 的 值符合指定正則

        1spring:
        2  cloud:
        3    gateway:
        4      routes:
        5      - id: cookie_route
        6        uri: https://example.org
        7        predicates:
        8        - Cookie=chocolate, ch.p

        此路由匹配具有名為 Chocolate 的 cookie 的請(qǐng)求,該 cookie 的值與 ch.p 正則表達(dá)式匹配。

        3. Header 謂詞路由

        和 Cookie 謂詞路由功能一樣,只不過這次是從 headers 里面判斷

        1spring:
        2  cloud:
        3    gateway:
        4      routes:
        5      - id: header_route
        6        uri: https://example.org
        7        predicates:
        8        - Header=X-Request-Id, \d+

        以下內(nèi)容太多,看官網(wǎng)吧:https://docs.spring.io/spring-cloud-gateway/docs/2.2.8.RELEASE/reference/html/

        4. Host 謂詞路由

        1spring:
        2  cloud:
        3    gateway:
        4      routes:
        5      - id: host_route
        6        uri: https://example.org
        7        predicates:
        8        - Host=**.somehost.org,**.anotherhost.org

        5. 請(qǐng)求方法謂詞路由

        1spring:
        2  cloud:
        3    gateway:
        4      routes:
        5      - id: method_route
        6        uri: https://example.org
        7        predicates:
        8        - Method=GET,POST

        6. 路徑參數(shù)謂詞路由

        1spring:
        2  cloud:
        3    gateway:
        4      routes:
        5      - id: path_route
        6        uri: https://example.org
        7        predicates:
        8        - Path=/red/{segment},/blue/{segment}

        可以通過來過去占位命名變量值

        1Map<String, String> uriVariables = ServerWebExchangeUtils.getPathPredicateVariables(exchange);
        2
        3String segment = uriVariables.get("segment");

        7. 查詢參數(shù)謂詞路由

        請(qǐng)求參數(shù)中有 key 為 green 的請(qǐng)求參數(shù)

        1spring:
        2  cloud:
        3    gateway:
        4      routes:
        5      - id: query_route
        6        uri: https://example.org
        7        predicates:
        8        - Query=green

        查詢參數(shù)中有 key 為 name 的變量,且值符合 gree. 正則

        1spring:
        2  cloud:
        3    gateway:
        4      routes:
        5      - id: query_route
        6        uri: https://example.org
        7        predicates:
        8        - Query=name, gree.

        8. RemoteAddr 地址謂詞路由

        可以做白名單

        1spring:
        2  cloud:
        3    gateway:
        4      routes:
        5      - id: remoteaddr_route
        6        uri: https://example.org
        7        predicates:
        8        - RemoteAddr=192.168.1.1/24

        9. 權(quán)重謂詞路由

        第一個(gè)參數(shù)是所在組,另一個(gè)是權(quán)重

         1spring:
        2  cloud:
        3    gateway:
        4      routes:
        5      - id: weight_high
        6        uri: https://weighthigh.org
        7        predicates:
        8        - Weight=group1, 8
        9      - id: weight_low
        10        uri: https://weightlow.org
        11        predicates:
        12        - Weight=group1, 2

        如何實(shí)現(xiàn)一個(gè)謂詞

        默認(rèn)提供的謂詞實(shí)現(xiàn)都在 org.springframework.cloud.gateway.handler.predicate 包下,通過如果想自定義實(shí)現(xiàn)一個(gè)謂詞,只需繼承AbstractRoutePredicateFactory, 即可,看一下 時(shí)間謂詞路由 Before 是怎么實(shí)現(xiàn)的

         1package org.springframework.cloud.gateway.handler.predicate;
        2
        3import java.time.ZonedDateTime;
        4import java.util.Collections;
        5import java.util.List;
        6import java.util.function.Predicate;
        7
        8import org.springframework.web.server.ServerWebExchange;
        9
        10
        11public class BeforeRoutePredicateFactory extends AbstractRoutePredicateFactory<BeforeRoutePredicateFactory.Config{
        12
        13    public static final String DATETIME_KEY = "datetime";
        14
        15    public BeforeRoutePredicateFactory() {
        16        super(Config.class);
        17    }
        18
        19    @Override
        20    public List<String> shortcutFieldOrder() 
        21    // 這里返回的 list 變量名需要和配置文件中一一對(duì)應(yīng),順序和變量名都得對(duì)應(yīng)上
        22    // - Before=2017-01-20T17:42:47.789-07:00[America/Denver]
        23    // 比如這樣順序決定時(shí)間和變量名的對(duì)應(yīng)關(guān)系,這里的情況則為
        24    // datetime 對(duì)應(yīng) 2017-01-20T17:42:47.789-07:00[America/Denver],
        25    // 其中 datatime 又需要對(duì)應(yīng)上 Config.class 中的屬性名,這樣才能通過反射
        26    // 將 2017-01-20T17:42:47.789-07:00[America/Denver] 映射到 Config.class 的 datetime 屬性上
        27    // 所以這里需要注意下順序和變量名,否則可能會(huì)出現(xiàn) Config.class 無法取到值的情況
        28        return Collections.singletonList(DATETIME_KEY)
        ;
        29    }
        30
        31    @Override
        32    public Predicate<ServerWebExchange> apply(Config config) {
        33        return new GatewayPredicate() {
        34
        35      // 這里返回 boolean 來確定是否能命中斷言
        36            @Override
        37            public boolean test(ServerWebExchange serverWebExchange) {
        38                final ZonedDateTime now = ZonedDateTime.now();
        39                return now.isBefore(config.getDatetime());
        40            }
        41        };
        42    }
        43
        44    public static class Config {
        45
        46        private ZonedDateTime datetime;
        47
        48        public ZonedDateTime getDatetime() {
        49            return datetime;
        50        }
        51
        52        public void setDatetime(ZonedDateTime datetime) {
        53            this.datetime = datetime;
        54        }
        55    }
        56}

        通過上面的代碼可以確定出,只要 test 方法即可

        注意 實(shí)現(xiàn)謂詞時(shí),需要以 XxxxRoutePredicateFactory 命名,其中 Xxxx 就是以后配置時(shí)的前綴了

        四、過濾器的配置

        過濾器分兩種:GlobalFilter 針對(duì)全局路由使用;GatewayFilter  針對(duì)指定的路由的使用

        GatewayFilter

        通過為 route 配置 filters 來顯示的生效

        1spring:
        2  cloud:
        3    gateway:
        4      routes:
        5      - id: add_request_header_route
        6        uri: https://example.org
        7        filters:
        8        - AddRequestHeader=X-Request-red, blue

        如何自定義個(gè) GatewayFilter,在日常的開發(fā)中,有些接口是需要登錄,有些不需要登錄,這里以驗(yàn)證為例,看一下如何定制 GatewayFilter

        定制 GatewayFilter 需要實(shí)現(xiàn)的是 AbstractGatewayFilterFactory

         1// 實(shí)現(xiàn) AbstractGatewayFilterFactory 這里也需要一個(gè) Config 對(duì)象
        2// 和實(shí)現(xiàn)謂詞基本一致
        3@Component
        4@Slf4j
        5public class AuthGatewayFilterFactory extends AbstractGatewayFilterFactory<Config{
        6
        7  private String message = "{\n"
        8      + " \"code\": 401,\n"
        9      + " \"errorMessage\": \"用戶身份信息失效,請(qǐng)重新登錄\""
        10      + "}";
        11
        12  public AuthGatewayFilterFactory() {
        13    super(Config.class);
        14  }
        15
        16  @Override
        17  public List<String> shortcutFieldOrder() {
        18    // 這里也是注意順序和名稱
        19    return Arrays.asList("executor");
        20  }
        21
        22  @Override
        23  public GatewayFilter apply(Config config) {
        24    return (exchange, chain) -> {
        25      boolean valid = 這里應(yīng)該是驗(yàn)證邏輯,如果驗(yàn)證通過返回 true;
        26      if (valid) {
        27        // 如果驗(yàn)證通過了,就繼續(xù)走過濾鏈
        28        return chain.filter(exchange);
        29      } else {
        30                // 驗(yàn)證沒通過,直接返回 401
        31        ServerHttpResponse response = exchange.getResponse();
        32        //設(shè)置 headers
        33        response.getHeaders().setContentType(MediaType.APPLICATION_JSON_UTF8);
        34        //設(shè)置body
        35        DataBuffer bodyDataBuffer = response.bufferFactory().wrap(message.getBytes());
        36        response.setStatusCode(HttpStatus.UNAUTHORIZED);
        37        return response.writeWith(Mono.just(bodyDataBuffer));
        38      }
        39    };
        40  }
        41
        42  @ToString
        43  public static class Config {
        44
        45    @Getter
        46    @Setter
        47    private String executor;
        48
        49  }
        50
        51}

        為指定的路由配置該過濾器

        1spring:
        2  cloud:
        3    gateway:
        4      routes:
        5      - id: add_request_header_route
        6        uri: https://example.org
        7        filters:
        8        - Auth=JWT

        看到這里應(yīng)該可以看出,這里也是感覺名字前綴來配置的

        GlobalFilter

        GlobalFilter 對(duì)全部的路由都有效,不要額外進(jìn)行配置,注入就能用。

        自定義 GlobalFilter 直接實(shí)現(xiàn) GlobalFilter 即可

         1@Component
        2@Slf4j
        3public class TimeStatisticalFilter implements GlobalFilterOrdered {
        4
        5  private static final String START_TIME = "startTime";
        6
        7  @Override
        8  public int getOrder() {
        9    // 指定此過濾器位于NettyWriteResponseFilter之后
        10    // 即待處理完響應(yīng)體后接著處理響應(yīng)頭
        11    return Ordered.LOWEST_PRECEDENCE;
        12  }
        13
        14  @Override
        15  public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        16    exchange.getAttributes().put(START_TIME, System.currentTimeMillis());
        17
        18    return chain.filter(exchange).then(Mono.fromRunnable(() -> {
        19      Long startTime = exchange.getAttribute(START_TIME);
        20      if (startTime != null) {
        21        long executeTime = (System.currentTimeMillis() - startTime);
        22        log.info(exchange.getRequest().getURI().getRawPath() + " : " + executeTime + "ms");
        23      }
        24    }));
        25  }
        26}

        注意事項(xiàng)

        exchange 本身中對(duì) request、response 不能直接修改,如果需要修改,需要生成一個(gè)新的 exchange 對(duì)象進(jìn)行修改,調(diào)用鏈本身有順序,如果要自定義 Filter 注意優(yōu)先級(jí)的設(shè)置

        常見過濾器的優(yōu)先級(jí)和功能

        每個(gè)版本的 Spring Cloud Gateway 可能不一樣,具體看 org.springframework.cloud.gateway.config.GatewayAutoConfiguration 里面相關(guān)配置

        名稱優(yōu)先級(jí)是否啟用請(qǐng)求階段響應(yīng)階段
        RemoveCachedBodyFilterHIGHEST_PRECEDENCE
        如果發(fā)現(xiàn)有 RequestBody 就去除
        AdaptCachedBodyGlobalFilterHIGHEST_PRECEDENCE + 1000把 requestBody 緩存到 cachedRequestBody Attribute 中
        DefaultValue

        什么都沒干,還拋出了一個(gè)異常
        ForwardPathFilter0set the path in the request URI if the {@link Route} URI has the scheme
        GatewayMetricsFilter0記錄下發(fā)起時(shí)間統(tǒng)計(jì)耗時(shí)
        NettyWriteResponseFilter-1
        將最終的 exchange 請(qǐng)求寫回客戶端
        WebClientWriteResponseFilter-1否,代碼中無任何開啟的方式
        與 NettyWriteResponseFilter 類似
        RouteToRequestUrlFilter10000將原始請(qǐng)求地址和路由配置的地址進(jìn)行替換,將替換成的新地址放在 GATEWAY_REQUEST_URL_ATTR  屬性中
        ReactiveLoadBalancerClientFilter10150如果是 lb 則根據(jù)服務(wù)發(fā)現(xiàn)找到應(yīng)的實(shí)例將實(shí)例地址設(shè)置成當(dāng)前請(qǐng)求的 host
        NoLoadBalancerClientFilter10150當(dāng) ReactorLoadBalancer 不存在且 spring.cloud.gateway.loadbalancer 屬性存在

        WebsocketRoutingFilterLOWEST_PRECEDENCE - 1WebSocket  的請(qǐng)求轉(zhuǎn)發(fā)

        ForwardRoutingFilterLOWEST_PRECEDENCE
        如果 shema 中含有 forward 則轉(zhuǎn)發(fā)
        NettyRoutingFilterLOWEST_PRECEDENCE如果 shema 中為 http 則轉(zhuǎn)發(fā)并寫入 response
        WebClientHttpRoutingFilterLOWEST_PRECEDENCE否,代碼中無任何開啟的方式和 NettyRoutingFilter 功能一樣,轉(zhuǎn)發(fā)請(qǐng)求的方式改為了 WebClient

        一些常見的操作

        1. 修改 response headers

        1ServerHttpResponse response = exchange.getResponse();
        2response.getHeaders().setContentType(MediaType.APPLICATION_JSON_UTF8);  // 這句如果出現(xiàn)異常,直接 catch 即可,不影響修改

        2. 修改 response 狀態(tài)碼

        1ServerHttpResponse response = exchange.getResponse();
        2response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);

        3. 修改 response 示例

        1ServerHttpResponse response = exchange.getResponse();
        2
        3//設(shè)置 headers
        4response.getHeaders().setContentType(MediaType.APPLICATION_JSON_UTF8);
        5
        6//設(shè)置body
        7DataBuffer bodyDataBuffer = response.bufferFactory().wrap("{}".getBytes());
        8response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
        9return response.writeWith(Mono.just(bodyDataBuffer));

        4. 設(shè)置或者添加屬性

        1exchange.getAttributes().put(key, value);
        2exchange.getAttribute(key)

        5. 統(tǒng)計(jì)請(qǐng)求時(shí)間示例

         1public class TimeStatisticalFilter implements GlobalFilterOrdered {
        2
        3  private static final String START_TIME = "startTime";
        4
        5  @Override
        6  public int getOrder() {
        7    // 這里可以通過設(shè)置不同的優(yōu)先級(jí)來統(tǒng)計(jì)不同的階段的時(shí)間
        8    return Ordered.HIGHEST_PRECEDENCE;
        9  }
        10
        11  @Override
        12  public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        13    exchange.getAttributes().put(START_TIME, System.currentTimeMillis());
        14    return chain.filter(exchange).then(Mono.fromRunnable(() -> {
        15      Long startTime = exchange.getAttribute(START_TIME);
        16      if (startTime != null) {
        17        long executeTime = (System.currentTimeMillis() - startTime);
        18        log.info(exchange.getRequest().getURI().getRawPath() + " : " + executeTime + "ms");
        19      }
        20    }));
        21  }
        22}

        6. 讀取 Request Body

        有一些情況,我們可能要讀取 Request Body,比如要對(duì) Request Body 加解密或者其他的判斷,如果只是讀取操作,可以使用 ReadBodyRoutePredicateFactory 來實(shí)現(xiàn),ReadBodyRoutePredicateFactory 配置有兩個(gè)參數(shù)需要配置:1. inClass 用來配置將body 轉(zhuǎn)換的類型;2. Predicate 判斷什么情況下可以轉(zhuǎn)。

        先配置一個(gè)永真的 Predicate 來確定執(zhí)行這個(gè)謂詞

         1@Configuration
        2public class Config {
        3
        4  @Bean
        5  public Predicate bodyPredicate(){
        6    return new Predicate() {
        7      @Override
        8      public boolean test(Object o) {
        9        return true;
        10      }
        11    };
        12  }
        13}

        增加配置 route 配置,對(duì)需要讀取 Request Body 的路由進(jìn)行配置,這里配置將 Request Body 轉(zhuǎn)換成 String,也方便后面使用的直接進(jìn)行其他轉(zhuǎn)換操作,例如 JSON。

         1spring:
        2  cloud:
        3    gateway:
        4      routes:
        5        - id: common
        6          uri: lb://hhhh
        7          predicates:
        8            - Path=/hhhh
        9            - name: ReadBody
        10              args:
        11                inClass: '#{T(String)}'
        12                predicate: '#{@bodyPredicate}'

        在后面的操作中,可以直接使用以下語句來獲取 Request Body 來進(jìn)行其他操作

        1String requestBody = exchange.getAttribute("cachedRequestBodyObject");

        7. 刪除重復(fù)的 headers

         1@Component
        2public class RemoveDuplicateResponseHeaderFilter implements GlobalFilterOrdered {
        3
        4  @Override
        5  public int getOrder() {
        6    // 指定此過濾器位于NettyWriteResponseFilter之后
        7    // 即待處理完響應(yīng)體后接著處理響應(yīng)頭
        8    return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER + 1;
        9  }
        10
        11  @Override
        12  public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        13    return chain.filter(exchange).then(Mono.defer(() -> {
        14      exchange.getResponse().getHeaders().entrySet().stream()
        15          .filter(kv -> (kv.getValue() != null && kv.getValue().size() > 1))
        16          .forEach(kv -> {
        17            kv.setValue(new ArrayList<String>() {{
        18              add(kv.getValue().get(0));
        19            }});
        20          });
        21      return chain.filter(exchange);
        22    }));
        23  }
        24}

        為什么使用網(wǎng)關(guān)

        正如開始提到的它封裝了應(yīng)用程序的內(nèi)部結(jié)構(gòu),為客戶端提供統(tǒng)一服務(wù),一些與業(yè)務(wù)本身功能無關(guān)的公共邏輯可以在這里實(shí)現(xiàn),諸如認(rèn)證、鑒權(quán)、監(jiān)控、路由轉(zhuǎn)發(fā)等。既然 Nginx 也可以實(shí)現(xiàn)類似的功能,為什么還用 Spring Cloud Gateway ?

        • 轉(zhuǎn)發(fā):Nginx 性能更好,Spring Cloud Gateway 的性能差之,不過其可以整合服務(wù)發(fā)現(xiàn),更加靈活,謂詞方式更多

        • 可擴(kuò)展性:Spring Cloud Gateway 可以自己定義過濾器更加的靈活

        • 開發(fā):相對(duì)于 Nginx 其對(duì) Java 開發(fā)更友好

        具體實(shí)現(xiàn)轉(zhuǎn)發(fā)的細(xì)節(jié)見 Java | Spring Cloud Gateway 是如何工作的



        瀏覽 124
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
          
          

            1. 色综合久久久久久久 | 久久伊人天堂 | 女侠白嫩的玉腿被分肆意蹂躏 | 精品国自产在线观看 | 男生操女生的逼逼 |