一、前言
Spring Cloud 原先整合 Zuul 作為網關元件,Zuul 由 Netflix 公司提供的,現在已經不維護了。後面 Netflix 公司又出來了一個 Zuul2.0 網關,但由于一直沒有釋出穩定版本,是以 Spring Cloud 等不及了就自己推出一個網關,已經不打算整合 zuul2.0 了。
Spring Cloud Gateway 是 Spring 公司基于 Spring 5.0, Spring Boot 2.0 和 Project Reactor 等技術開發的網關,它旨在為微服務架構提供一種簡單有效的統一的 API 路由管理方式。它的目标是替代 Netflix Zuul,其不僅提供統一的路由方式,并且基于 Filter 鍊的方式提供了網關基本的功能,例如:安全,監控和限流。
二、Gateway 介紹
路由:網關的基本建構組成,表示一個具體的路由資訊載體。它由 ID,目标 URI,謂詞集合和過濾器集合定義
謂詞/斷言:Java 8 函數謂詞,輸入類型是 Spring Framework ServerWebExchange,可以比對 HTTP 請求中的所有内容,例如請求頭或參數
過濾器:使用特定工廠構造的 Spring Framework GatewayFilter 執行個體,可以在發送給下遊請求之前或之後修改請求和響應
執行流程大體如下:
Gateway Client 向 Gateway Server 發送請求
請求首先會被 HttpWebHandlerAdapter 進行提取組裝成網關上下文
然後網關的上下文會傳遞到 DispatcherHandler,它負責将請求分發給 RoutePredicateHandlerMapping
RoutePredicateHandlerMapping 負責路由查找,并根據路由斷言判斷路由是否可用
如果過斷言成功,由 FilteringWebHandler 建立過濾器鍊并調用
請求會一次經過 PreFilter -> 微服務 -> PostFilter 的方法,最終傳回響應
三、環境搭建
為了更好的了解上邊提到核心概念,我們現用簡單的實戰案例示範。
項目名稱
端口
描述
gateway-test
-
pom 項目,父工廠
user-service
9001
使用者微服務,服務注冊到 nacos
gateway-service
9090
網關服務,服務注冊到 nacos
注意:搭建項目啟動前,必須先開啟 Nacos 服務。
該工程為 pom 項目,隻需要添加如下依賴:
該項目為使用者微服務,模拟提供使用者相關接口。
1. 添加依賴:
2. 配置檔案(application.yml):
3.業務類:
4.啟動類:
啟動使用者微服務,浏覽器輸入:h t t p:// localhost:9001/user/findById/1,結果如下圖:
使用者微服務正常。
該服務提供網關功能,核心就是配置路由規則。
1.添加依賴:
2.配置檔案(application.yml):
我們暫不配置路由規則。
3.啟動類:
啟動網關項目,我們試着通過網關請求使用者微服務接口。
請求規則:網關位址/微服務應用名/接口
我們在浏覽器輸入:h t t p://localhost:9090/user-service/user/findById/2,結果如下圖:
請求成功,網關項目搭建完成。
使用路由規則:
其中:
id: 路由辨別符,差別于其他 Route
uri:路由指向的目的地 uri,即用戶端請求最終被轉發到的微服務
predicate:斷言,用于條件判斷,隻有斷言都傳回真,才會真正的執行路由
filter:過濾器用于修改請求和響應資訊
添加 routes 相關配置,重新開機網關項目,請求使用者微服務接口。
請求規則:網關位址/斷言配置的 Path 路徑/接口
我們在浏覽器輸入:h t t p://localhost:9090/user-api/user/findById/3,結果如下圖:
路由規則生效。
簡單的使用了路由規則,下文将具體介紹路由規則的使用方式。
四、斷言
Predicate(斷言, 謂詞) 用于進行條件判斷,隻有斷言都傳回真,才會真正的執行路由。
SpringCloud Gateway 的斷言通過繼承 AbstractRoutePredicateFactory 類實作,是以我們可以根據自己的需求自定義斷言。
當然,開發團隊已為使用者提供了一些内置斷言工廠,在開發中已足夠使用,請繼續閱讀下文。
Spring Cloud Gateway 包括 11 種内置的斷言工廠,所有這些斷言都與 HTTP 請求的不同屬性比對。
補充:斷言可以同時使用
AfterRoutePredicateFactory:接收一個日期參數,判斷請求日期是否晚于指定日期
BeforeRoutePredicateFactory:接收一個日期參數,判斷請求日期是否早于指定日期
BetweenRoutePredicateFactory:接收兩個日期參數,判斷請求日期是否在指定時間段内
上邊三個斷言工廠都是根據時間判斷。使用方式如下:
我們設定在 2021年10月01日之後才能通路接口,目前請求時間為 2021年08月12日,請求結果如下圖:
請求接口失敗。
4.CookieRoutePredicateFactory: 接收兩個參數,cookie 名字和值。 判斷請求 cookie 是否具有給定名稱且值與正規表達式比對。
其中,token 為 cookie 名稱,123456 為 cookie 值。
我們可以支援 curl 的工具測試,鍵入 <code>curl http://localhost:9090/user-api/user/findById/3 --cookie token=123456</code>,結果如下圖:
5.HeaderRoutePredicateFactory:接收兩個參數,标題名稱和正規表達式。 判斷請求 Header 是否具有給定名稱且值與正規表達式比對。
其中,X-Request-Id 為 header 名稱,\d+ 為正規表達式,表示數字。
我們可以支援 curl 的工具測試,鍵入 <code>curl http://localhost:9090/user-api/user/findById/3 --header "X-Request-Id:9527"</code>,結果如下圖:
6.HostRoutePredicateFactory:接收一個參數,主機名模式。判斷請求的 Host 是否滿足比對規則。
支援 URI 模闆變量(例如{sub} .myhost.org),如果請求的主機标頭的值為w w w.somehost.org或 beta.somehost.org 或 w w w.anotherhost.org,則此路由比對
4.MethodRoutePredicateFactory: 接收一個參數,判斷請求類型是否跟指定的類型比對。
如果請求方法是 GET 或 POST,則此路由比對。
7.PathRoutePredicateFactory:接收一個參數,判斷請求的 URI 部分是否滿足路徑規則。
這個就是我們在上邊配置的斷言,請求是 <code>/user-api/</code> 開頭,則路由到使用者微服務上。
8.QueryRoutePredicateFactory:接收兩個參數,請求 param 和正規表達式, 判斷請求參數是否具有給定名稱且值與正規表達式比對。
請求包含名稱為 cardId 的參數,且參數值為數字,則比對路由。
測試如下:
9.RemoteAddrRoutePredicateFactory:接收一個 IP 位址段,判斷請求主機位址是否在位址段中
其中,192.168.0.1 是 IP 位址,而 16 是子網路遮罩。當請求的遠端位址為該值時,比對路由。
10.WeightRoutePredicateFactory:接收一個[組名,權重], 然後對于同一個組内的路由按照權重轉發。
配置多組路由規則時使用。路由會将約 80% 的流量轉發至 weighthigh.org,并将約 20% 的流量轉發至 weightlow.org。
當内置的斷言不滿足我們的業務需求時,我們可以自定義斷言工廠。
比如,我們需要判斷請求 url 中傳過來的 age 值在 18~60 範圍才可正常路由。
1. 配置斷言:
2. 我們需要建立一個類繼承 AbstractRoutePredicateFactory 類:
注意:自定義類名有格式要求-> 斷言名稱 + RoutePredicateFactory。此處斷言名稱為 Age,對應配置檔案中的 Age。
3. 儲存,重新開機網關項目,測試結果如下:
五、過濾器
路由過濾器允許以某種方式修改傳入的 HTTP 請求或傳出的 HTTP 響應。
在 Gateway 中, Filter 的生命周期隻有兩個: “pre” 和 “post”。
PRE:這種過濾器在請求被路由之前調用。我們可利用這種過濾器實作身份驗證、在叢集中選擇請求的微服務、記錄調試資訊等
POST:這種過濾器在路由到微服務以後執行。這種過濾器可用來為響應添加标準的 HTTP Header、收集統計資訊和名額、将響應從微服務發送給用戶端等。
根據 Filter 的作用範圍可以分成兩種:GatewayFilter 與 GlobalFilter。
GatewayFilter:應用到單個路由或者一個分組的路由上。
GlobalFilter:應用到所有的路由上。
局部過濾器是針對單個路由的過濾器。
Spring Cloud Gateway 也提供了 31 種局部的内置 GatewayFilter 工廠。
由于數量較多,筆者隻列舉部分内置局部過濾器進行展示。
過濾器工廠
作用
參數
AddRequestHeader
為原始請求添加Header
Header的名稱及值
AddRequestParameter
為原始請求添加請求參數
參數名稱及值
AddResponseHeader
為原始響應添加Header
DedupeResponseHeader
剔除響應頭中重複的值
需要去重的Header名稱及去重政策
PrefixPath
為原始請求路徑添加字首
字首路徑
RequestRateLimiter
用于對請求限流, 限流算法為令牌桶
keyResolver、rateLimiter、statusCode、denyEmptyKey、emptyKeyStatus
RedirectTo
将原始請求重定向到指定的URL
http狀态碼及重定向的url
StripPrefix
用于截斷原始請求的路徑
使用數字表示要截斷的路徑的數量
Retry
針對不同的響應進行重試
retries、 statuses、methods、 series
ModifyRequestBody
在轉發請求之前修改原始請求體内容
修改後的請求體内容
ModifyResponseBody
修改原始響應體的内容
修改後的響應體内容
SetStatus
修改原始響應的狀态碼
HTTP 狀态碼, 可以是數字, 也可以是字元串
使用方式:
同樣地,當内置的局部過濾器不符合我們的業務需求時,我們也可以自定義過濾器。
比如:我們需要在調用/路由一個接口之前列印一下日志。
1. 配置局部過濾器
2. 建立一個類繼承 AbstractGatewayFilterFactory 類:
注意:自定義類名有格式要求-> 過濾器名稱 + GatewayFilterFactory。此處過濾器名稱為 Log,對應配置檔案中的 Log。
全局過濾器作用于所有路由, 無需配置。通過全局過濾器可以實作對權限的統一校驗,安全性驗證等功能。
同樣地,架構也内置了一些全局過濾器,它們都實作 GlobalFilter 和 Ordered 接口。有興趣的讀者可以自行檢視 GlobalFilter 的實作類或浏覽下文提供的官方文檔擷取詳細資訊。
這裡我們主要示範自定義全局過濾器。
比如:我們在接受請求時需要驗證 token。
由于是全局過濾器,是以無需修改配置檔案,需要定義類實作 GlobalFilter 和 Ordered 接口。
儲存,重新開機網關項目,測試結果如下:
token 驗證失敗,傳回 401,鑒權失敗的提示;token 驗證成功,傳回接口結果。
六、 路由失敗處理
當請求路由位址不比對或斷言為 false 時,Gateway 會預設傳回 Whitelabel Error Page 錯誤頁面,這種錯誤提示不符合我們業務需求。
1. 我們可以自定義傳回一個較為友好的錯誤提示,需要建立一個類繼承 DefaultErrorWebExceptionHandler 類,重寫其方法:
2. 配置 Bean 執行個體:
3. 儲存後重新開機網關項目,請求一個錯誤的接口位址,結果如下:
請求的 url 位址不比對路由規則傳回我們定義的錯誤提示。
七、跨域問題
針對 PC 端的頁面請求,如果項目前後端分離,則請求會出現跨域請求問題。為什麼呢?接着看。
URL 由協定、域名、端口和路徑組成,如果兩個 URL 的協定、域名和端口相同,則表示它們同源,否則反之。
浏覽器提供同源政策,限制了來自不同源的 document 或腳本,對目前 document 讀取或設定某些屬性。其目的是為了保證使用者資訊的安全,防止惡意的網站竊取資料。
下面筆者示範跨域問題,編寫一個簡單頁面:
啟動一個服務容器(筆者采用 sublime 的插件),配置設定了 10800 端口,請求結果如下:
由于請求端的端口與網關端口不一緻,不是同源,是以出現跨域問題。
解決方案有兩種,如下:
方式一:修改配置檔案
方式二:配置 CorsWebFilter 過濾器
八、整合 Sentinel
網關作為微服務,我們也可以對其進行限流和降級操作。
注意:配置前記得啟動 Sentinel 控制台。
2. 修改配置檔案,連接配接 Sentinel 控制台:
3. 配置 Sentinel Filter 執行個體
最後,重新開機網關微服務,在 Sentinel 控制台檢視或配置規則即可。
在 Sentinel 控制台配置規後,服務出現限流或降級時,我們需要服務端傳回友好的異常資訊,而不是一個簡單的錯誤頁面。
我們需要配置 BlockRequestHandler 執行個體。
注意:當多個 Bean 上都配置 @Order 注解時,要多留意 order 值,否則接口請求後達不到預期效果
最後
需要完整版的小夥伴,可以一鍵三連後,點選這裡!