<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          ?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),為客戶(hù)端提供統(tǒng)一服務(wù),一些與業(yè)務(wù)本身功能無(wú)關(guān)的公共邏輯可以在這里實(shí)現(xiàn),諸如認(rèn)證、鑒權(quán)、監(jiān)控、路由轉(zhuǎn)發(fā)等。

          Spring Cloud 圖片
          中間件NginxKongNetflix ZuulSpring Cloud Gatewayshenyu
          主要開(kāi)發(fā)語(yǔ)言CLuaJavaJavaJava
          依賴(lài)關(guān)系無(wú)基于 Nginx_Lua模塊無(wú)無(wú)無(wú)
          支持協(xié)議HTTPHTTP, GRPCHTTP,HTTPHTTP, WebSocket, Dubbo, GRPC
          擴(kuò)展基于 Lua 腳本基于 Lua 腳本Java 寫(xiě)過(guò)濾器Java 寫(xiě)過(guò)濾器、斷言Java 寫(xiě)插件
          編程模型多進(jìn)程 + io多路復(fù)用基于 NginxZuul 1 采用 Servlet, Zuul 2 采用 NettySpring WebFlux(Netty Reactor)Netty Reactor
          配置頁(yè)面無(wú)豐富無(wú)無(wú)豐富
          負(fù)載均衡寫(xiě)死的支持 Consul(間接可以支持使用 Consul 的 Spring Cloud)Spring Cloud 相關(guān)Spring Cloud 相關(guān)通過(guò)各種插件實(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)目中引入依賴(lài),具體的版本號(hào)視情況而定。

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

          如果需要開(kāi)啟負(fù)載均衡,需要引入對(duì)應(yīng)的依賴(lài),比如使用 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ù)載均衡,且無(wú)法找對(duì)對(duì)應(yīng)的服務(wù)實(shí)例,默然返回 502,通過(guò) 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 謂詞路由功能一樣,只不過(guò)這次是從 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}

          可以通過(guò)來(lái)過(guò)去占位命名變量值

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

          7. 查詢(xún)參數(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

          查詢(xún)參數(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 包下,通過(guò)如果想自定義實(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 中的屬性名,這樣才能通過(guò)反射
          26    // 將 2017-01-20T17:42:47.789-07:00[America/Denver] 映射到 Config.class 的 datetime 屬性上
          27    // 所以這里需要注意下順序和變量名,否則可能會(huì)出現(xiàn) Config.class 無(wú)法取到值的情況
          28        return Collections.singletonList(DATETIME_KEY)
          ;
          29    }
          30
          31    @Override
          32    public Predicate<ServerWebExchange> apply(Config config) {
          33        return new GatewayPredicate() {
          34
          35      // 這里返回 boolean 來(lái)確定是否能命中斷言
          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}

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

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

          四、過(guò)濾器的配置

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

          GatewayFilter

          通過(guò)為 route 配置 filters 來(lái)顯示的生效

          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,在日常的開(kāi)發(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\": \"用戶(hù)身份信息失效,請(qǐng)重新登錄\""
          10      + "}";
          11
          12  public AuthGatewayFilterFactory() {
          13    super(Config.class);
          14  }
          15
          16  @Override
          17  public List<String> shortcutFieldOrder() {
          18    // 這里也是注意順序和名稱(chēng)
          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)證通過(guò)返回 true;
          26      if (valid) {
          27        // 如果驗(yàn)證通過(guò)了,就繼續(xù)走過(guò)濾鏈
          28        return chain.filter(exchange);
          29      } else {
          30                // 驗(yàn)證沒(méi)通過(guò),直接返回 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}

          為指定的路由配置該過(guò)濾器

          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)該可以看出,這里也是感覺(jué)名字前綴來(lái)配置的

          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    // 指定此過(guò)濾器位于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è)置

          常見(jiàn)過(guò)濾器的優(yōu)先級(jí)和功能

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

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

          什么都沒(méi)干,還拋出了一個(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)求寫(xiě)回客戶(hù)端
          WebClientWriteResponseFilter-1否,代碼中無(wú)任何開(kāi)啟的方式
          與 NettyWriteResponseFilter 類(lèi)似
          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ā)并寫(xiě)入 response
          WebClientHttpRoutingFilterLOWEST_PRECEDENCE否,代碼中無(wú)任何開(kāi)啟的方式和 NettyRoutingFilter 功能一樣,轉(zhuǎn)發(fā)請(qǐng)求的方式改為了 WebClient

          一些常見(jiàn)的操作

          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    // 這里可以通過(guò)設(shè)置不同的優(yōu)先級(jí)來(lái)統(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 來(lái)實(shí)現(xiàn),ReadBodyRoutePredicateFactory 配置有兩個(gè)參數(shù)需要配置:1. inClass 用來(lái)配置將body 轉(zhuǎn)換的類(lèi)型;2. Predicate 判斷什么情況下可以轉(zhuǎn)。

          先配置一個(gè)永真的 Predicate 來(lái)確定執(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}'

          在后面的操作中,可以直接使用以下語(yǔ)句來(lái)獲取 Request Body 來(lái)進(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    // 指定此過(guò)濾器位于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)

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

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

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

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

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



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

          手機(jī)掃一掃分享

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

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  动漫黄色视频网站 | 婷婷亚洲国产五月 | 西西午夜无码视频色欲 | 豆花成人版视频WWW18 | 正在播放国产精品 |