這篇SpringCloud GateWay 詳解,你用的到

點贊再看,養成習慣,微信搜索【牧小農】關注我獲取更多資訊,風裡雨里,小農等你,很高興能夠成為你的朋友。
項目源碼地址:公眾號回復 sentinel,即可免費獲取源碼

背景

在微服務架構中,通常一個系統會被拆分為多個微服務,面對這麼多微服務客戶端應該如何去調用呢?如果沒有其他更優方法,我們只能記錄每個微服務對應的地址,分別去調用,但是這樣會有很多的問題和潛在因素。

  1. 客戶端多次請求不同的微服務,會增加客戶端代碼和配置的複雜性,維護成本比價高。
  2. 認證複雜,每個微服務可能存在不同的認證方式,客戶端去調用,要去適配不同的認證,
  3. 存在跨域的請求,調用鏈有一定的相對複雜性(防火牆 / 瀏覽器不友好的協議)。
  4. 難以重構,隨着項目的迭代,可能需要重新劃分微服務

為了解決上面的問題,微服務引入了 網關 的概念,網關為微服務架構的系統提供簡單、有效且統一的API路由管理,作為系統的統一入口,提供內部服務的路由中轉,給客戶端提供統一的服務,可以實現一些和業務沒有耦合的公用邏輯,主要功能包含認證、鑒權、路由轉發、安全策略、防刷、流量控制、監控日誌等。

網關在微服務中的位置:

自己手繪的

官網上的

網關對比

  • Zuul 1.0 : Netflix開源的網關,使用Java開發,基於Servlet架構構建,便於二次開發。因為基於Servlet內部延遲嚴重,並發場景不友好,一個線程只能處理一次連接請求。

  • Zuul 2.0 : 採用Netty實現異步非阻塞編程模型,一個CPU一個線程,能夠處理所有的請求和響應,請求響應的生命周期通過事件和回調進行處理,減少線程數量,開銷較小

  • GateWay : 是Spring Cloud的一個全新的API網關項目,替換Zuul開發的網關服務,基於Spring5.0 + SpringBoot2.0 + WebFlux(基於⾼性能的Reactor模式響應式通信框架Netty,異步⾮阻塞模型)等技術開發,性能高於Zuul

  • Nginx+lua : 性能要比上面的強很多,使用Nginx的反向代碼和負載均衡實現對API服務器的負載均衡以及高可用,lua作為一款腳本語言,可以編寫一些簡單的邏輯,但是無法嵌入到微服務架構中

  • Kong : 基於OpenResty(Nginx + Lua模塊)編寫的高可用、易擴展的,性能高效且穩定,支持多個可用插件(限流、鑒權)等,開箱即可用,只支持HTTP協議,且二次開發擴展難,缺乏更易用的管理和配置方式

GateWay

官方文檔://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gateway-starter

Spring Cloud Gateway 是Spring Cloud的一個全新的API網關項目,目的是為了替換掉Zuul1,它基於Spring5.0 + SpringBoot2.0 + WebFlux(基於⾼性能的Reactor模式響應式通信框架Netty,異步⾮阻塞模型)等技術開發,性能⾼於Zuul,官⽅測試,Spring Cloud GateWay是Zuul的1.6倍 ,旨在為微服務架構提供⼀種簡單有效的統⼀的API路由管理⽅式。

  1. 可以與Spring Cloud Discovery Client(如Eureka)、Ribbon、Hystrix等組件配合使用,實現路由轉發、負載均衡、熔斷、鑒權、路徑重寫、⽇志監控等

  2. Gateway還內置了限流過濾器,實現了限流的功能。

  3. 設計優雅,容易拓展

基本概念

路由(Route)是GateWay中最基本的組件之一,表示一個具體的路由信息載體,主要由下面幾個部分組成:

  1. id:路由唯一標識,區別於其他的route
  2. url: 路由指向的目的地URL,客戶端請求最終被轉發到的微服務
  3. order: 用於多個Route之間的排序,數值越小越靠前,匹配優先級越高
  4. predicate:斷言的作用是進行條件判斷,只有斷言為true,才執行路由
  5. filter: 過濾器用於修改請求和響應信息

核心流程

核心概念:

  1. Gateway ClientSpring Cloud Gateway 發送請求
  2. 請求首先會被 HttpWebHandlerAdapter 進行提取組裝成網關上下文
  3. 然後網關的上下文會傳遞到 DispatcherHandler ,它負責將請求分發給 RoutePredicateHandlerMapping
  4. RoutePredicateHandlerMapping 負責路由查找,並根據路由斷言判斷路由是否可用
  5. 如果過斷言成功,由 FilteringWebHandler 創建過濾器鏈並調用
  6. 通過特定於請求的 Fliter 鏈運行請求,Filter 被虛線分隔的原因是Filter可以在發送代理請求之前(pre)和之後(post)運行邏輯
  7. 執行所有pre過濾器邏輯。然後進行代理請求。發出代理請求後,將運行「post」過濾器邏輯。
  8. 處理完畢之後將 Response 返回到 Gateway 客戶端

Filter過濾器:

  • Filter在pre類型的過濾器可以做參數效驗、權限效驗、流量監控、日誌輸出、協議轉換等。
  • Filter在post類型的過濾器可以做響應內容、響應頭的修改、日誌輸出、流量監控等

核心思想

當用戶發出請求達到 GateWay 之後,會通過一些匹配條件,定位到真正的服務節點,並且在這個轉發過程前後,進行一些細粒度的控制,其中 Predicate(斷言) 是我們的匹配條件,Filter 是一個攔截器,有了這兩點,再加上URL,就可以實現一個具體的路由,核心思想:路由轉發+執行過濾器鏈

這個過程就好比考試,我們考試首先要找到對應的考場,我們需要知道考場的地址和名稱(id和url),然後我們進入考場之前會有考官查看我們的准考證是否匹配(斷言),如果匹配才會進入考場,我們進入考場之後,(路由之前)會進行身份的登記和考試的科目,填寫考試信息,當我們考試完成之後(路由之後)會進行簽字交卷,走出考場,這個就類似我們的過濾器

Route(路由) :構建網關的基礎模塊,由ID、目標URL、過濾器等組成

Predicate(斷言) :開發人員可以匹配HTTP請求中的內容(請求頭和請求參數),如果請求斷言匹配賊進行路由

Filter(過濾) :GateWayFilter的實例,使用過濾器,可以在請求被路由之前或者之後對請求進行修改

框架搭建

通過上述講解已經了解了基礎概念,我們來動手搭建一個GateWay項目,來看看它到底是如何運行的
新建項目:cloud-alibaba-gateway-9006

版本對應:

GateWay屬於SprinigCloud且有web依賴,在我們導入對應依賴時,要注意版本關係,我們這裡使用的版本是 2.2.x的版本,所以配合使用的Hoxton.SR5版本

在這裡我們要注意的是引入GateWay一定要刪除spring-boot-starter-web依賴,否則會有衝突無法啟動

父類pom引用:

<spring-cloud-gateway-varsion>Hoxton.SR5</spring-cloud-gateway-varsion>

 <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-dependencies</artifactId>
    <version>${spring-cloud-gateway-varsion}</version>
    <type>pom</type>
    <scope>import</scope>
</dependency>

子類POM引用:

<dependencies>
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-gateway</artifactId>
        <version>2.2.5.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
</dependencies>

yml配置

server:
  port: 9006
spring:
  application:
    name: cloud-gateway-service
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    gateway:
      discovery:
        locator:
          enabled: false #開啟註冊中心路由功能
      routes:  # 路由
        - id: nacos-provider #路由ID,沒有固定要求,但是要保證唯一,建議配合服務名
          uri: //localhost:9001/nacos-provider # 匹配提供服務的路由地址 lb://表示開啟負載均衡
          predicates: # 斷言
            - Path=/mxn/** # 斷言,路徑相匹配進行路由

我們在之前的cloud-alibaba-nacos-9001項目中添加下面測試代碼

@RestController
@RequestMapping("/mxn")
public class DemoController {

    @Value("${server.port}")
    private String serverPort;

    @GetMapping(value = "/hello")
    public String hello(){
        return "hello world ,my port is :"+serverPort;
    }

 }   

啟動Nacos、cloud-alibaba-nacos-9001cloud-alibaba-gateway-9006通過gateway網關去訪問9001的mxn/order看看。

首先我們在Nacos中看到我們服務是註冊到Nacos中了

然後我們訪問//localhost:9001/mxn/hello,確保是成功的,在通過//localhost:9006/mxn/hello去訪問,也是OK,說明我們GateWay搭建成功,我們進入下一步

在上述方法中我們是通過YML去完成的配置,GateWay還提供了另外一種配置方式,就是通過代碼的方式進行配置,@Bean 注入一個 RouteLocator

import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class GateWayConfig {

      /*
    配置了一個id為path_mxn的路由規則
    當訪問地址//localhost:9999/mxn/**
    就會轉發到//localhost:9001/nacos-provider/mxn/任何地址
     */
    @Bean
    public RouteLocator gateWayConfigInfo(RouteLocatorBuilder routeLocatorBuilder){
        // 構建多個路由routes
        RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
        // 具體路由地址
        routes.route("path_mxn",r -> r.path("/mxn/**").uri("//localhost:9001/nacos-provider")).build();
        // 返回所有路由規則
        return routes.build();
    }
}

我們可以將路由注釋掉之後看一下,重啟9006服務,訪問地址//localhost:9006/mxn/hello 就可以轉發到9001中具體的接口中

這裡並不推薦,使用代碼的方式來進行配置gateWay,大家有個了解就可以,因為代碼的配置維護的成本比較高,而且對於一些需要修改的項,需要改代碼才可以完成,這樣不利於維護和拓展,所以還是推薦大家使用yml進行配置。

GateWay負載均衡

在上述的講解中,我們已經掌握了 GateWay 的一些基本配置和兩種使用方式,下面我們就來講解一下 GateWay 如何實現負載均衡

我們只需要在9006中添加lb://nacos-provider就可以顯示負載均衡。

當我們去訪問//localhost:9006/mxn/hello的時候,就可以看到9001和9002不停的切換

Predicate 斷言

在這一篇中我們來研究一下 斷言 ,我們可以理解為:當滿足條件後才會進行轉發路由,如果是多個,那麼多個條件需要同時滿足

在官方提供的斷言種類有11種(最新的有12種類型):

具體地址://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gateway-request-predicates-factories

  1. After:匹配在指定日期時間之後發生的請求。
  2. Before:匹配在指定日期之前發生的請求。
  3. Between:需要指定兩個日期參數,設定一個時間區間,匹配此時間區間內的請求。
  4. Cookie:需要指定兩個參數,分別為name和regexp(正則表達式),也可以理解Key和Value,匹配具有給定名稱且其值與正則表達式匹配的Cookie。
  5. Header:需要兩個參數header和regexp(正則表達式),也可以理解為Key和Value,匹配請求攜帶信息。
  6. Host:匹配當前請求是否來自於設置的主機。
  7. Method:可以設置一個或多個參數,匹配HTTP請求,比如GET、POST
  8. Path:匹配指定路徑下的請求,可以是多個用逗號分隔
  9. Query:需要指定一個或者多個參數,一個必須參數和一個可選的正則表達式,匹配請求中是否包含第一個參數,如果有兩個參數,則匹配請求中第一個參數的值是否符合正則表達式。
  10. RemoteAddr:匹配指定IP或IP段,符合條件轉發。
  11. Weight:需要兩個參數group和weight(int),實現了路由權重功能,按照路由權重選擇同一個分組中的路由

1. After : 表示配置時間之後才進行轉發

時間戳獲取代碼,用於時間代碼的獲取:

    public static void main(String[] args) {
        ZonedDateTime zbj = ZonedDateTime.now();//默認時區
        System.out.println(zbj);
    }
spring:
  application:
    name: cloud-gateway-service
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    gateway:
      discovery:
        locator:
          enabled: true #開啟註冊中心路由功能
      routes:  # 路由
        - id: nacos-provider #路由ID,沒有固定要求,但是要保證唯一,建議配合服務名
          uri: lb://nacos-provider # 匹配提供服務的路由地址 lb://表示開啟負載均衡
          predicates: # 斷言
            - Path=/mxn/** # 斷言,路徑相匹配進行路由
            - After=2022-06-11T16:30:40.785+08:00[Asia/Shanghai] #在這個時間之後的請求夠可以進行通過,之前的則不能進行訪問

如果在時間段之前訪問則404

Before

匹配ZonedDateTime類型的時間,表示匹配在指定日期時間之前的請求,之後的請求則拒絕404錯誤

predicates: # 斷言
  - Path=/mxn/** # 斷言,路徑相匹配進行路由
# - After=2022-06-11T16:30:40.785+08:00[Asia/Shanghai] #在這個時間之後的請求夠可以進行通過,之前的則不能進行訪問
  - Before=2022-06-11T15:30:40.785+08:00[Asia/Shanghai]

Between

Between 可以匹配ZonedDateTime類型的時間,由兩個ZonedDateTime參數組成,第一個參數為開始時間,第二參數為結束時間,逗號進行分隔,匹配在指定的開始時間與結束時間之內的請求,配置如下:

predicates: # 斷言
  - Path=/mxn/** # 斷言,路徑相匹配進行路由
# - After=2022-06-11T16:30:40.785+08:00[Asia/Shanghai] #在這個時間之後的請求夠可以進行通過,之前的則不能進行訪問
#  - Before=2022-06-11T15:30:40.785+08:00[Asia/Shanghai]
  - Between=2022-06-11T15:30:40.785+08:00[Asia/Shanghai],2022-06-11T16:30:40.785+08:00[Asia/Shanghai]

Cookie

由兩個參數組成,分別為name(Key)regexp(正則表達式)(Value),匹配具有給定名稱且其值與正則表達式匹配的Cookie。

路由規則會通過獲取Cookie name值和正則表達式去匹配,如果匹配上就會執行路由,如果匹配不上則不執行。

predicates: # 斷言
  - Path=/mxn/** # 斷言,路徑相匹配進行路由
# - After=2022-06-11T16:30:40.785+08:00[Asia/Shanghai] #在這個時間之後的請求夠可以進行通過,之前的則不能進行訪問
#  - Before=2022-06-11T15:30:40.785+08:00[Asia/Shanghai]
#  - Between=2022-06-11T15:30:40.785+08:00[Asia/Shanghai],2022-06-11T16:30:40.785+08:00[Asia/Shanghai]
   - Cookie=muxiaonong,[a-z]+ # 匹配Cookie的key和value(正則表達式)表示任意字母

小寫字母匹配成功:

數字匹配不成功:

Header

由兩個參數組成,第一個參數為Header名稱,第二參數為Header的Value值,指定名稱的其值和正則表達式相匹配的Header的請求

predicates: # 斷言
  - Path=/mxn/** # 斷言,路徑相匹配進行路由
# - After=2022-06-11T16:30:40.785+08:00[Asia/Shanghai] #在這個時間之後的請求夠可以進行通過,之前的則不能進行訪問
#  - Before=2022-06-11T15:30:40.785+08:00[Asia/Shanghai]
#  - Between=2022-06-11T15:30:40.785+08:00[Asia/Shanghai],2022-06-11T16:30:40.785+08:00[Asia/Shanghai]
#  - Cookie=muxiaonong,[a-z]+ # 匹配Cookie的key和value(正則表達式)表示任意字母
  - Header=headerName, \d+ # \d表示數字

請求頭攜帶數字斷言請求成功,

斷言字母匹配失敗:

Host

匹配當前請求是否來自於設置的主機。

predicates: # 斷言
  - Path=/mxn/** # 斷言,路徑相匹配進行路由
# - After=2022-06-11T16:30:40.785+08:00[Asia/Shanghai] #在這個時間之後的請求夠可以進行通過,之前的則不能進行訪問
#  - Before=2022-06-11T15:30:40.785+08:00[Asia/Shanghai]
#  - Between=2022-06-11T15:30:40.785+08:00[Asia/Shanghai],2022-06-11T16:30:40.785+08:00[Asia/Shanghai]
#  - Cookie=muxiaonong,[a-z]+ # 匹配Cookie的key和value(正則表達式)表示任意字母
#  - Header=headerName, \d+ # \d表示數字
  - Host=**.muxiaonong.com #匹配當前的主機地址發出的請求

滿足Host斷言,請求成功

不滿足Host斷言失敗

**Method **

可以設置一個或多個參數,匹配HTTP請求,比如POST,PUT,GET,DELETE

predicates: # 斷言
  - Path=/mxn/** # 斷言,路徑相匹配進行路由
# - After=2022-06-11T16:30:40.785+08:00[Asia/Shanghai] #在這個時間之後的請求夠可以進行通過,之前的則不能進行訪問
#  - Before=2022-06-11T15:30:40.785+08:00[Asia/Shanghai]
#  - Between=2022-06-11T15:30:40.785+08:00[Asia/Shanghai],2022-06-11T16:30:40.785+08:00[Asia/Shanghai]
#  - Cookie=muxiaonong,[a-z]+ # 匹配Cookie的key和value(正則表達式)表示任意字母
#  - Header=headerName, \d+ # \d表示數字
#  - Host=**.muxiaonong.com #匹配當前的主機地址發出的請求
  - Method=POST,GET

GET斷言成功

PUT斷言請求失敗

Query

由兩個參數組成,第一個為參數名稱(必須),第二個為參數值(可選-正則表達式),匹配請求中是否包含第一個參數,如果有兩個參數,則匹配請求中第一個參數的值是否符合第二個正則表達式。

predicates: # 斷言
  - Path=/mxn/** # 斷言,路徑相匹配進行路由
# - After=2022-06-11T16:30:40.785+08:00[Asia/Shanghai] #在這個時間之後的請求夠可以進行通過,之前的則不能進行訪問
#  - Before=2022-06-11T15:30:40.785+08:00[Asia/Shanghai]
#  - Between=2022-06-11T15:30:40.785+08:00[Asia/Shanghai],2022-06-11T16:30:40.785+08:00[Asia/Shanghai]
#  - Cookie=muxiaonong,[a-z]+ # 匹配Cookie的key和value(正則表達式)表示任意字母
#  - Header=headerName, \d+ # \d表示數字
#  - Host=**.muxiaonong.com #匹配當前的主機地址發出的請求
#  - Method=POST,GET
  - Query=id,.+ # 匹配任意請求參數,這裡如果需要匹配多個參數,可以寫多個- Query=

斷言匹配 請求成功

RemoteAddr

參數由CIDR 表示法(IPv4 或 IPv6)字符串組成,也就是匹配的ID地址,配置如下:

predicates: # 斷言
  - Path=/mxn/** # 斷言,路徑相匹配進行路由
# - After=2022-06-11T16:30:40.785+08:00[Asia/Shanghai] #在這個時間之後的請求夠可以進行通過,之前的則不能進行訪問
#  - Before=2022-06-11T15:30:40.785+08:00[Asia/Shanghai]
#  - Between=2022-06-11T15:30:40.785+08:00[Asia/Shanghai],2022-06-11T16:30:40.785+08:00[Asia/Shanghai]
#  - Cookie=muxiaonong,[a-z]+ # 匹配Cookie的key和value(正則表達式)表示任意字母
#  - Header=headerName, \d+ # \d表示數字
#  - Host=**.muxiaonong.com #匹配當前的主機地址發出的請求
#  - Method=POST,GET
#  - Query=id,.+ # 匹配任意請求參數,這裡如果需要匹配多個參數,可以寫多個Query
  - RemoteAddr=192.168.1.1/24

RemoteAddr

需要兩個參數group和weight(int)權重數值,實現了路由權重功能,表示將相同的請求根據權重跳轉到不同的uri地址,要求group的名稱必須一致

routes:  # 路由
  - id: weight_high #路由ID,沒有固定要求,但是要保證唯一,建議配合服務名
    uri: //blog.csdn.net/qq_14996421
    predicates: # 斷言
      - Weight=groupName,8
  - id: weight_low #路由ID,沒有固定要求,但是要保證唯一,建議配合服務名
    uri: //juejin.cn/user/2700056290405815
    predicates: # 斷言
      - Weight=groupName,2

直接訪問//localhost:9006/可以看到我們請求的地址成8/2比例交替顯示, 80% 的流量轉發到//blog.csdn.net/qq_14996421,將約 20% 的流量轉發//juejin.cn/user/2700056290405815

Predicate就是為了實現一組匹配規則,讓請求過來找到對應的Route進行處理。如果有多個斷言則全部命中後進行處理

GateWay Filter

路由過濾器允許修改傳入的HTTP請求或者返回的HTTP響應,路由過濾器的範圍是特定的路由.

Spring Cloud GateWay 內置的Filter生命周期有兩種:pre(業務邏輯之前)、post(業務邏輯之後)

GateWay本身自帶的Filter分為兩種: GateWayFilter(單一)、GlobalFilter(全局)

GateWay Filter提供了豐富的過濾器的使用,單一的有32種,全局的有9種,有興趣的小夥伴可以了解一下。

官方參考網址://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#global-filters

StripPrefix

StripPrefix 在我們當前請求中,通過規則值去掉某一部分地址,比如我們有一台服務中加入了一個前端nacos-provider想要通過這個去訪問,我們在項目cloud-alibaba-nacos-9001中加入 context-path

server:
  port: 9001
  servlet:
    context-path: /nacos-provider

現在9001的訪問路徑變為//localhost:9001/nacos-provider/mxn/hello,但是如果我們通過網關去訪問路徑就會變成//localhost:9006/mxn/nacos-provider/mxn/hello 這個時候我們通過這個路徑去訪問是訪問不成功的,想要解決這個方法,這個就用到了我們FIlter 中的 StripPrefix

routes:  # 路由
  - id: nacos-provider #路由ID,沒有固定要求,但是要保證唯一,建議配合服務名
    uri: lb://nacos-provider
    predicates: # 斷言
      - Path=/mxn/** # 匹配對應地址
    filters:
      - StripPrefix=1 # 去掉地址中的第一部分

我們重新啟動9006項目,再去訪問

自定義Filter

雖然Gateway給我們提供了豐富的內置Filter,但是實際項目中,自定義Filter的場景非常常見,因此單獨介紹下自定義FIlter的使用。

想要實現GateWay自定義過濾器,那麼我們需要實現GatewayFilter接口和Ordered接口

@Slf4j
@Component
public class MyFilter implements Ordered, GlobalFilter {
    /**
     * @param exchange 可以拿到對應的request和response
     * @param chain 過濾器鏈
     * @return 是否放行
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //獲取第一個參數
        String id = exchange.getRequest().getQueryParams().getFirst("id");
        //打印當前時間
        log.info("MyFilter 當前請求時間為:"+new Date());
        //判斷用戶是否存在
        if(StringUtils.isEmpty(id)){
            log.info("用戶名不存在,非法請求!");
            //如果username為空,返回狀態碼為407,需要代理身份驗證
            exchange.getResponse().setStatusCode(HttpStatus.PROXY_AUTHENTICATION_REQUIRED);
            // 後置過濾器
            return exchange.getResponse().setComplete();
        }
        return chain.filter(exchange);
    }

    /**
     * 設定過濾器的優先級,值越小則優先級越高
     * @return
     */
    @Override
    public int getOrder() {
        return 0;
    }
}

當我們訪問//localhost:9006/mxn/nacos-provider/mxn/hello請求,沒有攜帶ID參數,請求失敗

當我們訪問//localhost:9006/mxn/nacos-provider/mxn/hello?id=1請求,請求成功

總結

到這裡我們的GateWay就講解完了,對於GateWay的核心點主要有三個Route\Predicate\Filter,我們搞懂了這三點,基本上對於GateWay的知識就掌握的差不多了,GateWay核心的流程就是:路由轉發+執行過濾器鏈,如果對文中有疑問的小夥伴,歡迎留言討論。

創作不易,如果文中對你有幫助,記得點贊關注,您的支持是我創作的最大動力。

我是牧小農,怕什麼真理無窮,進一步有進一步的歡喜~