前言
為什么需要網(wǎng)關(guān)以及網(wǎng)關(guān)的作用
在微服務(wù)架構(gòu)中,網(wǎng)關(guān)是至關(guān)重要的組件,具有多重職責(zé),為整個(gè)系統(tǒng)提供了一系列關(guān)鍵功能。從下面的微服務(wù)結(jié)構(gòu)圖中,我們可以明確網(wǎng)關(guān)的幾項(xiàng)主要作用:
微服務(wù)結(jié)構(gòu)圖:
-
請(qǐng)求過濾與安全:
用戶的所有請(qǐng)求首先經(jīng)過網(wǎng)關(guān),這使得網(wǎng)關(guān)成為系統(tǒng)的第一道防線。通過對(duì)傳入請(qǐng)求的過濾、驗(yàn)證和安全策略的實(shí)施,網(wǎng)關(guān)確保只有合法的請(qǐng)求能夠訪問內(nèi)部的微服務(wù)。 -
路由與負(fù)載均衡:
網(wǎng)關(guān)具有路由功能,能夠?qū)⑹盏降恼?qǐng)求正確地分發(fā)給相應(yīng)的微服務(wù)。這種路由機(jī)制使得系統(tǒng)更加靈活,同時(shí),網(wǎng)關(guān)還結(jié)合了負(fù)載均衡的特性,確保各個(gè)微服務(wù)能夠平衡地處理請(qǐng)求,提升系統(tǒng)的性能和可用性。 -
統(tǒng)一的接入點(diǎn):
作為系統(tǒng)的入口,網(wǎng)關(guān)提供了一個(gè)統(tǒng)一的接入點(diǎn),簡(jiǎn)化了客戶端與微服務(wù)之間的通信??蛻舳酥恍枧c網(wǎng)關(guān)進(jìn)行交互,而無(wú)需直接與各個(gè)微服務(wù)打交道,從而降低了系統(tǒng)的復(fù)雜性。 -
請(qǐng)求轉(zhuǎn)換與聚合:
在實(shí)際應(yīng)用中,某些信息可能分布在多個(gè)微服務(wù)中。網(wǎng)關(guān)的聚合和轉(zhuǎn)換功能使得它能夠從多個(gè)微服務(wù)中收集數(shù)據(jù),然后以符合客戶端期望的格式返回。此外,網(wǎng)關(guān)還可以處理請(qǐng)求和響應(yīng)的轉(zhuǎn)換,以適應(yīng)不同微服務(wù)的接口。 -
請(qǐng)求限流與熔斷:
網(wǎng)關(guān)在系統(tǒng)入口處實(shí)施請(qǐng)求限流和熔斷機(jī)制,以防止不良請(qǐng)求對(duì)整個(gè)系統(tǒng)造成影響。通過在網(wǎng)關(guān)層面進(jìn)行控制,系統(tǒng)能夠有效地抵御過載和故障,提高整體的穩(wěn)定性。
綜合而言,網(wǎng)關(guān)在微服務(wù)架構(gòu)中扮演了關(guān)鍵角色,通過提供統(tǒng)一入口、安全性、路由、負(fù)載均衡等功能,為整個(gè)系統(tǒng)的可維護(hù)性、可伸縮性和可用性奠定了基礎(chǔ)。
網(wǎng)關(guān)的技術(shù)實(shí)現(xiàn)
在Spring Cloud中,實(shí)現(xiàn)網(wǎng)關(guān)的兩種主要方式是使用Zuul
和Gateway
。下面簡(jiǎn)要介紹它們的特點(diǎn)和區(qū)別:
1. Zuul
Zuul是一個(gè)基于Servlet的網(wǎng)關(guān)實(shí)現(xiàn),它在Spring Cloud中充當(dāng)了路由和過濾器的角色。主要特點(diǎn)包括:
-
阻塞式編程: Zuul采用阻塞式的處理方式,即每個(gè)請(qǐng)求都會(huì)在一個(gè)單獨(dú)的線程中處理,等待響應(yīng)完成后才能繼續(xù)處理其他請(qǐng)求。
-
功能全面: Zuul不僅提供了路由功能,還支持請(qǐng)求的過濾、認(rèn)證、授權(quán)等多種功能。這使得它成為一個(gè)功能較為完備的網(wǎng)關(guān)方案。
2. Spring Cloud Gateway
Spring Cloud Gateway是基于 Spring 5 中引入的 WebFlux 框架的響應(yīng)式編程實(shí)現(xiàn)。與 Zuul 相比,它具有以下特點(diǎn):
-
響應(yīng)式編程: Gateway 采用響應(yīng)式編程模型,利用反應(yīng)式流處理請(qǐng)求。這使得它能夠更高效地處理大量并發(fā)請(qǐng)求,具備更好的性能。
-
簡(jiǎn)化的過濾器鏈: Gateway 引入了全局過濾器、路由斷言和過濾器工廠等概念,使過濾器的配置更為靈活。相較于 Zuul,Gateway 提供了更清晰、簡(jiǎn)潔的過濾器鏈定義。
-
內(nèi)置斷言支持: Gateway 內(nèi)置了多種路由斷言,可以根據(jù)請(qǐng)求的各種屬性進(jìn)行路由,提供了更強(qiáng)大的路由功能。
-
動(dòng)態(tài)路由: Gateway 支持動(dòng)態(tài)路由配置,可以在運(yùn)行時(shí)添加、刪除路由規(guī)則,靈活適應(yīng)微服務(wù)架構(gòu)的變化。
總體而言,選擇使用Zuul還是 Spring Cloud Gateway 取決于具體需求和項(xiàng)目的性能要求。Zuul 在功能上較為全面,而 Spring Cloud Gateway 則更適用于對(duì)性能和響應(yīng)式編程有較高要求的場(chǎng)景。
一、Gateway 網(wǎng)關(guān)的搭建
在理解了為什么需要網(wǎng)關(guān)以及網(wǎng)關(guān)的作用后,我們可以嘗試在 cloud-demo
中搭建一個(gè) Gateway 網(wǎng)關(guān)。
1.1 創(chuàng)建 Gateway 模塊
首先,在項(xiàng)目中創(chuàng)建一個(gè)新的模塊 gateway
,作為我們的網(wǎng)關(guān)服務(wù)。
然后,為Gateway 模塊創(chuàng)建合適的包,并創(chuàng)建一個(gè)GatewayApplication
啟動(dòng)類:
@SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
1.2 引入依賴
在 pom.xml
文件中引入 Gateway 網(wǎng)關(guān)和 Nacos 注冊(cè)中心的依賴。
<!-- 網(wǎng)關(guān)依賴 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- Nacos 服務(wù)發(fā)現(xiàn)依賴 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
在上述 pom.xml
文件中你引入了 Spring Cloud Gateway 和 Nacos 服務(wù)發(fā)現(xiàn)的依賴。這兩個(gè)依賴的作用:
-
spring-cloud-starter-gateway
: 這是 Spring Cloud Gateway 的啟動(dòng)器依賴,它包含了構(gòu)建 Gateway 時(shí)所需的核心依賴和配置。 -
spring-cloud-starter-alibaba-nacos-discovery
: 這是阿里巴巴的 Nacos 服務(wù)發(fā)現(xiàn)的啟動(dòng)器依賴。它為項(xiàng)目提供了與 Nacos 服務(wù)注冊(cè)和發(fā)現(xiàn)相關(guān)的功能。
通過這兩個(gè)依賴,就可以在 Spring Cloud 中輕松搭建起一個(gè)網(wǎng)關(guān)服務(wù),并使用 Nacos 作為服務(wù)注冊(cè)與發(fā)現(xiàn)的工具。在微服務(wù)體系結(jié)構(gòu)中,Nacos 的作用是集中管理和發(fā)現(xiàn)各個(gè)微服務(wù),使它們能夠相互協(xié)作。
1.3 配置網(wǎng)關(guān)
在 application.yml
配置文件中,編寫網(wǎng)關(guān)的路由配置和 Nacos 的地址信息。
server:
port: 10010 # 網(wǎng)關(guān)端口
spring:
application:
name: gateway
cloud:
nacos:
server-addr: localhost:8848 # Nacos 地址
gateway:
routes: # 網(wǎng)關(guān)路由配置
- id: user-service # 路由 ID,自定義的唯一即可
# uri: http://127.0.0.1:8081 # 固定的地址
uri: lb://userservice # 路由的目標(biāo)地址,其中 lb 代表 loadBalance 負(fù)載均衡,然后是服務(wù)的名稱
predicates: # 路由斷言,也就是判斷請(qǐng)求是否符合路由規(guī)則的條件
- Path=/user/** # 這個(gè)是按照路徑匹配,只要以"/user/"開頭的請(qǐng)求就符合要求,即會(huì)路由到 userservice 服務(wù)
- id: order-service
uri: lb://orderservice
predicates:
- Path=/order/**
在上述 application.yml
中,配置了網(wǎng)關(guān)的路由規(guī)則和 Nacos 的地址信息。對(duì)這個(gè)配置文件各個(gè)部分的說明:
-
server.port
: 設(shè)置了網(wǎng)關(guān)服務(wù)的端口為10010
。 -
spring.application.name
: 將網(wǎng)關(guān)服務(wù)的應(yīng)用名稱設(shè)置為gateway
。 -
spring.cloud.nacos.server-addr
: 配置了 Nacos 注冊(cè)中心的地址為localhost:8848
。 -
spring.cloud.gateway.routes
: 定義了兩個(gè)網(wǎng)關(guān)路由規(guī)則。-
路由規(guī)則1 (
user-service
):-
id
: 路由的唯一標(biāo)識(shí),這里設(shè)置為user-service
。 -
uri
: 設(shè)置了路由的目標(biāo)地址為lb://userservice
,其中lb://
表示使用負(fù)載均衡,后面跟著服務(wù)的名稱userservice
。 -
predicates
: 路由斷言,指定了只有請(qǐng)求路徑以/user/
開頭才會(huì)匹配到這個(gè)路由規(guī)則。
-
-
路由規(guī)則2 (
order-service
):-
id
: 路由的唯一標(biāo)識(shí),這里設(shè)置為order-service
。 -
uri
: 設(shè)置了路由的目標(biāo)地址為lb://orderservice
,同樣使用了負(fù)載均衡,后面是服務(wù)的名稱orderservice
。 -
predicates
: 路由斷言,指定了只有請(qǐng)求路徑以/order/
開頭才會(huì)匹配到這個(gè)路由規(guī)則。
-
-
這樣配置的結(jié)果是,當(dāng)請(qǐng)求進(jìn)入網(wǎng)關(guān)時(shí),如果路徑以 /user/
開頭,它將被路由到 userservice
服務(wù);如果路徑以 /order/
開頭,它將被路由到 orderservice
服務(wù)。這樣通過網(wǎng)關(guān),可以在不暴露內(nèi)部服務(wù)地址的情況下,統(tǒng)一管理和調(diào)度服務(wù)。
1.4 驗(yàn)證網(wǎng)關(guān)是否搭建成功
通過上述網(wǎng)關(guān)的搭建工作之后,我們可以啟動(dòng) gateway
服務(wù),然后在瀏覽器中訪問 10010 端口,查看是否能夠成功訪問 userservice
和 orderservice
服務(wù):
此時(shí)發(fā)現(xiàn)分別訪問這兩個(gè)微服務(wù)都成功了,也就證明了我們的網(wǎng)關(guān)也搭建成功了。
1.5 微服務(wù)結(jié)構(gòu)分析
通過以上步驟,我們創(chuàng)建了一個(gè)簡(jiǎn)單的 Gateway 網(wǎng)關(guān),并配置了一個(gè)基本的路由規(guī)則,將請(qǐng)求轉(zhuǎn)發(fā)到用戶服務(wù)。整個(gè) cloud-demo
微服務(wù)的結(jié)構(gòu)如下圖所示:
這個(gè)微服務(wù)結(jié)構(gòu)圖很好地描述了整個(gè)系統(tǒng)的架構(gòu)和流程。以下是對(duì)圖中各個(gè)部分的詳細(xì)解釋:
-
Gateway 網(wǎng)關(guān):
- 網(wǎng)關(guān)是整個(gè)系統(tǒng)的入口點(diǎn),它接收外部請(qǐng)求并將其路由到不同的微服務(wù)。
- 網(wǎng)關(guān)的端口為
10010
,并通過路由規(guī)則確定請(qǐng)求的目標(biāo)微服務(wù)。
-
用戶服務(wù) (
userservice
) 微服務(wù):- 用戶服務(wù)處理與用戶相關(guān)的請(qǐng)求,例如用戶信息的獲取和操作。
- 用戶服務(wù)通過 Nacos 注冊(cè)到服務(wù)發(fā)現(xiàn)中心,以便網(wǎng)關(guān)可以發(fā)現(xiàn)和路由請(qǐng)求到該服務(wù)。
-
訂單服務(wù) (
orderservice
) 微服務(wù):- 訂單服務(wù)處理與訂單相關(guān)的請(qǐng)求,例如訂單的創(chuàng)建和查詢。
- 與用戶服務(wù)類似,訂單服務(wù)也通過 Nacos 注冊(cè)到服務(wù)發(fā)現(xiàn)中心。
-
Nacos 注冊(cè)中心:
- Nacos 提供服務(wù)注冊(cè)和發(fā)現(xiàn)功能,允許微服務(wù)注冊(cè)并發(fā)現(xiàn)其他服務(wù)的實(shí)例。
- 網(wǎng)關(guān)和微服務(wù)都通過 Nacos 注冊(cè)中心來(lái)獲取服務(wù)實(shí)例的列表,以實(shí)現(xiàn)負(fù)載均衡和動(dòng)態(tài)路由。
-
請(qǐng)求流程:
- 用戶通過瀏覽器訪問網(wǎng)關(guān)的路徑,例如
http://127.0.0.1:10010/user/1
。 - 網(wǎng)關(guān)根據(jù)路由規(guī)則確定將請(qǐng)求路由到
userservice
微服務(wù)。 - 網(wǎng)關(guān)通過 Nacos 獲取
userservice
的服務(wù)實(shí)例列表。 - 網(wǎng)關(guān)選擇一個(gè)服務(wù)實(shí)例,并使用負(fù)載均衡策略將請(qǐng)求轉(zhuǎn)發(fā)給該實(shí)例。
-
userservice
微服務(wù)處理請(qǐng)求并返回結(jié)果給網(wǎng)關(guān),然后網(wǎng)關(guān)將結(jié)果返回給用戶。
- 用戶通過瀏覽器訪問網(wǎng)關(guān)的路徑,例如
這個(gè)微服務(wù)結(jié)構(gòu)圖清晰地展示了整個(gè)系統(tǒng)的工作流程和各個(gè)組件之間的關(guān)系,有助于理解微服務(wù)架構(gòu)的運(yùn)作方式。通過網(wǎng)關(guān)和服務(wù)注冊(cè)中心的協(xié)作,系統(tǒng)能夠?qū)崿F(xiàn)高可用性、負(fù)載均衡和動(dòng)態(tài)擴(kuò)展,以滿足不同場(chǎng)景下的需求。
二、Gateway 斷言工廠
在上文搭建 Gateway 網(wǎng)關(guān)服務(wù)的演示中,我們提到了路由斷言(predicates)規(guī)則,而這些規(guī)則實(shí)際上是由斷言工廠(Predicate Factory)處理的。斷言工廠是 Spring Cloud Gateway 提供的一種機(jī)制,它們負(fù)責(zé)將配置文件中的字符串規(guī)則解析為具體的路由條件。
2.1 Spring 提供的斷言工廠
Spring 提供了多種基本的斷言工廠,每個(gè)工廠對(duì)應(yīng)一種路由判斷的條件。以下是一些常用的斷言工廠及其示例:
名稱 | 說明 | 示例 |
---|---|---|
After | 請(qǐng)求是否在某個(gè)時(shí)間點(diǎn)之后。 | After=2037-01-20T17:42:47.789-07:00[America/Denver] |
Before | 請(qǐng)求是否在某個(gè)時(shí)間點(diǎn)之前。 | Before=2031-04-13T15:14:47.433+08:00[Asia/Shanghai] |
Between | 請(qǐng)求是否在某兩個(gè)時(shí)間點(diǎn)之間。 | Between=2037-01-20T17:42:47.789-07:00[America/Denver], 2037-01-21T17:42:47.789-07:00[America/Denver] |
Cookie | 請(qǐng)求是否包含指定的 Cookie。 | Cookie=chocolate, ch.p |
Header | 請(qǐng)求是否包含指定的 Header。 | Header=X-Request-Id, \d+ |
Host | 請(qǐng)求是否訪問指定的 Host(域名)。 | Host=**.somehost.org, **.anotherhost.org |
Method | 請(qǐng)求的方法是否為指定的方式。 | Method=GET, POST |
Path | 請(qǐng)求的路徑是否符合指定規(guī)則。 | Path=/red/{segment}, /blue/** |
Query | 請(qǐng)求參數(shù)是否包含指定參數(shù)。 | Query=name, Jack |
RemoteAddr | 請(qǐng)求者的 IP 是否在指定范圍內(nèi)。 | RemoteAddr=192.168.1.1/24 |
Weight | 權(quán)重處理,通常在負(fù)載均衡場(chǎng)景下使用。 | Weight=group1, group2 |
這些斷言工廠為網(wǎng)關(guān)提供了靈活的路由規(guī)則配置,使得我們可以根據(jù)不同的條件來(lái)定制路由策略。在實(shí)際使用中,可以根據(jù)需求組合使用這些斷言工廠,構(gòu)建復(fù)雜而強(qiáng)大的路由規(guī)則。
2.2 示例:設(shè)置斷言工廠
如果我們想要配置某個(gè)斷言工廠,可以直接參考 Spring 的官方文檔:
例如,我們要想設(shè)置 After 斷言:
這個(gè)配置中,對(duì)于 orderservice
使用了兩個(gè)斷言:
-
Path 斷言工廠:
-
Path=/order/**
表示請(qǐng)求的路徑必須以/order/
開頭才會(huì)匹配這個(gè)路由規(guī)則。
-
-
After 斷言工廠:
-
After=2024-01-01T17:42:47.789+08:00[Asia/Shanghai]
表示請(qǐng)求的時(shí)間必須在指定的時(shí)間點(diǎn)之后,即在 2024 年 1 月 1 日 17:42:47(上海時(shí)區(qū))之后。
-
通過這兩個(gè)斷言,路由規(guī)則要求請(qǐng)求路徑必須以 /order/
開頭,并且請(qǐng)求的時(shí)間必須在 2024 年 1 月 1 日 17:42:47 之后。只有同時(shí)滿足這兩個(gè)條件的請(qǐng)求才會(huì)匹配到這個(gè)路由規(guī)則,然后被路由到相應(yīng)的微服務(wù)。
顯然當(dāng)前時(shí)間不符合 After 斷言工廠,如果我們此時(shí)嘗試訪問,則會(huì)出現(xiàn) 404 錯(cuò)誤:
那么如果此時(shí)將 After 改成 Before 斷言工廠呢:
顯然當(dāng)前的時(shí)間符合要求,再次重啟并訪問,便可以成功訪問了:
通過上述設(shè)置斷言工廠的例子,我們就會(huì)發(fā)現(xiàn)其實(shí)設(shè)置某個(gè)斷言工廠非常簡(jiǎn)單,并且也不需要我們刻意去記某個(gè)斷言工廠,只需要在使用的時(shí)候查閱官方文檔即可。
三、Gateway 路由過濾器及其工廠
3.1 路由過濾器 GatewayFilter
GatewayFilter 是網(wǎng)關(guān)中提供的一種強(qiáng)大的過濾器,允許對(duì)進(jìn)入網(wǎng)關(guān)的請(qǐng)求和微服務(wù)返回的響應(yīng)進(jìn)行全面的處理。通過網(wǎng)關(guān)過濾器,我們可以在請(qǐng)求和響應(yīng)的不同階段執(zhí)行自定義的邏輯,例如添加請(qǐng)求頭、修改請(qǐng)求體、記錄日志、驗(yàn)證權(quán)限等。
流程圖解釋:
- 用戶的請(qǐng)求首先經(jīng)過網(wǎng)關(guān)的路由進(jìn)行轉(zhuǎn)發(fā),然后通過一層一層的過濾器鏈進(jìn)行過濾。
- 過濾器的作用可以包括查看請(qǐng)求頭、修改請(qǐng)求體、記錄日志等操作,每個(gè)過濾器在過濾鏈中有特定的執(zhí)行順序。
- 經(jīng)過一系列的過濾器處理后,請(qǐng)求最終發(fā)送給目標(biāo)微服務(wù)。
- 微服務(wù)處理完請(qǐng)求后,響應(yīng)的結(jié)果同樣會(huì)經(jīng)過網(wǎng)關(guān)的過濾器鏈進(jìn)行處理,最終通過路由返回給用戶。
通過合理配置和組合不同的網(wǎng)關(guān)過濾器,我們能夠?qū)崿F(xiàn)靈活、高效的請(qǐng)求處理流程。在實(shí)際應(yīng)用中,可以根據(jù)具體的業(yè)務(wù)需求選擇合適的過濾器,并有選擇性地添加、移除或自定義過濾器,以實(shí)現(xiàn)定制化的網(wǎng)關(guān)處理邏輯。
3.2 路由過濾器工廠 GatewayFilter Factory
Spring 提供了三十多種不同的路由過濾器工廠,通過 Spring 官網(wǎng)文檔 可以查詢這些過濾器工廠具體的使用方法:
這些過濾器工廠包括但不限于:
-
AddRequestHeader GatewayFilterFactory: 為請(qǐng)求添加頭信息。
filters: - AddRequestHeader=HeaderName, HeaderValue
-
AddResponseHeader GatewayFilterFactory: 為響應(yīng)添加頭信息。
filters: - AddResponseHeader=HeaderName, HeaderValue
-
RewritePath GatewayFilterFactory: 重寫請(qǐng)求路徑。
filters: - RewritePath=/foo/(?<segment>.*), /$\{segment}
-
SetPath GatewayFilterFactory: 設(shè)置請(qǐng)求路徑。
filters: - SetPath=/foo
-
RequestRateLimiter GatewayFilterFactory: 請(qǐng)求速率限制。
filters: - RequestRateLimiter=RateLimitKey, replenishRate, burstCapacity
以上是一些常用的過濾器工廠,通過合理配置這些過濾器工廠,我們可以實(shí)現(xiàn)對(duì)請(qǐng)求和響應(yīng)的靈活處理。在實(shí)際應(yīng)用中,可以根據(jù)具體的需求選擇相應(yīng)的過濾器工廠,并按需組合使用,以實(shí)現(xiàn)定制化的網(wǎng)關(guān)處理邏輯。
3.3 示例:添加過濾器工廠
在這個(gè)示例中,我們可以給所有進(jìn)入 userservice
服務(wù)的請(qǐng)求添加一個(gè)請(qǐng)求頭:Hello=Hello GatewayFilterFactory!
,注意實(shí)際上這是一個(gè)鍵值對(duì)。
具體的操作則是在gateway
服務(wù)中修改application.yml
文件,給userservice
的路由添加過濾器:
驗(yàn)證是否添加請(qǐng)求頭成功的方式則可以在 userservice
的請(qǐng)求用戶信息方法中新增一個(gè)參數(shù),例如:
@GetMapping("/{id}")
public User queryById(@PathVariable("id") Long id, @RequestHeader(value = "Hello", required = false) String hello) {
System.out.println("Hello: " + hello);
return userService.queryById(id);
}
在這個(gè) Controller 方法中新增了一個(gè) hello
參數(shù),其來(lái)源是 HTTP 的請(qǐng)求頭,使用 @RequestHeader
注解標(biāo)注,并且使用 required
指定了這個(gè)參數(shù)不是必傳的,然后重啟 userservice
并在瀏覽器中進(jìn)行訪問,在控制臺(tái)上成功打印了“Hello: Hello GatewayFilterFactory!” 日志:
3.4 默認(rèn)過濾器
通過上面的示例,我們知道了如何給某個(gè)微服務(wù)的請(qǐng)求加上過濾器,但是如果要給所有服務(wù)的請(qǐng)求都加上過濾器該如何操作呢?
可能我們首先會(huì)想到分別給各個(gè)微服務(wù)都加上過濾器的配置項(xiàng),但是這樣就顯得非常冗余了。此外,我們還可以使用默認(rèn)過濾器(default-filters
)的方式來(lái)給所有的微服務(wù)請(qǐng)求都加上相同的過濾器配置,例如:
此時(shí),我們可以嘗試把 userservice
中的過濾器配置給注釋掉,然后再次重啟服務(wù)并訪問,看看默認(rèn)過濾器配置是否生效。
此時(shí)便說明我們的默認(rèn)過濾器配置生效了。
四、Gateway 全局過濾器
在前文中,我們已經(jīng)了解了請(qǐng)求路由的過濾器工廠以及默認(rèn)過濾器 default-filters
的作用?,F(xiàn)在,讓我們深入研究另一個(gè)強(qiáng)大的概念——全局過濾器 GlobalFilter
。
4.1 全局過濾器的概念和作用
1. 概念
全局過濾器是 Spring Cloud Gateway 中的一種過濾器類型,它的作用是處理所有進(jìn)入網(wǎng)關(guān)的請(qǐng)求和從微服務(wù)返回的響應(yīng)。與局部過濾器(GatewayFilter)不同,全局過濾器的邏輯需要通過代碼
來(lái)實(shí)現(xiàn),而不是通過配置
來(lái)定義。
全局過濾器是在整個(gè)請(qǐng)求-響應(yīng)周期中起作用的過濾器。它可以執(zhí)行一些全局性的任務(wù),如認(rèn)證、授權(quán)、日志記錄、性能監(jiān)控等。全局過濾器不僅可以處理請(qǐng)求,還可以處理從微服務(wù)返回的響應(yīng),使其成為一個(gè)更為強(qiáng)大和靈活的過濾器類型。
2. 作用
-
統(tǒng)一處理全局任務(wù):
全局過濾器可以用于執(zhí)行與整個(gè)微服務(wù)架構(gòu)相關(guān)的任務(wù),而不僅僅是單個(gè)路由或微服務(wù)的請(qǐng)求處理。這使得它非常適合于執(zhí)行全局性的操作,例如在每個(gè)請(qǐng)求中執(zhí)行身份驗(yàn)證、記錄請(qǐng)求日志等。 -
修改請(qǐng)求和響應(yīng):
通過全局過濾器,可以在請(qǐng)求到達(dá)微服務(wù)之前或響應(yīng)返回客戶端之前修改請(qǐng)求或響應(yīng)。這種能力對(duì)于實(shí)施諸如請(qǐng)求重寫、響應(yīng)重寫、添加頭信息等操作非常有用。 -
集成外部服務(wù)
全局過濾器還可以用于集成外部服務(wù),例如在請(qǐng)求中調(diào)用身份驗(yàn)證服務(wù)或其他微服務(wù),以獲取必要的信息或執(zhí)行某些任務(wù)。
4.2 GlobalFilter 接口定義
public interface GlobalFilter {
/**
* 處理當(dāng)前請(qǐng)求,有必要的話通過 {@link GatewayFilterChain} 將請(qǐng)求交給下一個(gè)過濾器處理
*
* @param exchange 請(qǐng)求上下文,里面可以獲取 Request、Response 等信息
* @param chain 用來(lái)把請(qǐng)求委托給下一個(gè)過濾器
* @return {@code Mono<Void>} 返回標(biāo)識(shí)當(dāng)前過濾器業(yè)務(wù)結(jié)束
*/
Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain);
}
上述代碼展示了 GlobalFilter 接口的定義。其中關(guān)鍵部分的說明如下:
-
filter
: 這是 GlobalFilter 接口的核心方法,負(fù)責(zé)處理當(dāng)前請(qǐng)求。參數(shù)ServerWebExchange
提供了請(qǐng)求上下文,其中包含了請(qǐng)求和響應(yīng)的相關(guān)信息。參數(shù)GatewayFilterChain
則用于將請(qǐng)求委托給下一個(gè)過濾器處理。 -
返回類型
Mono<Void>
表示當(dāng)前過濾器的業(yè)務(wù)邏輯是否已經(jīng)處理完畢。如果返回的Mono
完成了,那么表示當(dāng)前過濾器的任務(wù)已完成,請(qǐng)求將繼續(xù)傳遞給下一個(gè)過濾器。如果Mono
未完成,請(qǐng)求將被阻塞。
4.3 示例:定義全局過濾器進(jìn)行用戶身份驗(yàn)證
在微服務(wù)架構(gòu)中,用戶身份驗(yàn)證是保護(hù)服務(wù)安全的重要一環(huán)。通過定義全局過濾器,我們可以在請(qǐng)求進(jìn)入網(wǎng)關(guān)時(shí)對(duì)用戶身份進(jìn)行驗(yàn)證,以確保只有合法用戶才能訪問服務(wù)。本示例將展示如何使用 Spring Cloud Gateway 實(shí)現(xiàn)一個(gè)簡(jiǎn)單的用戶身份驗(yàn)證全局過濾器。
1. 需求說明
我們的需求是攔截請(qǐng)求,判斷請(qǐng)求參數(shù)中是否包含 authorization
參數(shù),且其值是否為 “admin”。如果滿足條件,請(qǐng)求將被放行;否則,請(qǐng)求將被攔截。
2. 實(shí)現(xiàn)過程
首先,我們創(chuàng)建一個(gè)全局過濾器 AuthorizeFilter
,并實(shí)現(xiàn) GlobalFilter
接口:
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.ServerHttpRequest;
import org.springframework.util.MultiValueMap;
import reactor.core.publisher.Mono;
@Component
public class AuthorizeFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 1. 獲取請(qǐng)求參數(shù)
ServerHttpRequest request = exchange.getRequest();
MultiValueMap<String, String> params = request.getQueryParams();
// 2. 獲取參數(shù)中的 authorization 參數(shù)
String authorization = params.getFirst("authorization");
// 3. 判斷參數(shù)值是否等于 admin
if("admin".equals(authorization)){
// 4. 等于則放行,相當(dāng)于調(diào)用過濾器鏈中的下一個(gè)過濾器
return chain.filter(exchange);
}
// 5. 不等于則攔截
// 5.1 設(shè)置狀態(tài)碼
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
// 5.2 攔截請(qǐng)求
return exchange.getResponse().setComplete();
}
@Override
public int getOrder() {
return -1;
}
}
在這個(gè)過濾器中,我們獲取請(qǐng)求參數(shù)中的 authorization
參數(shù),并判斷其值是否為 “admin”。如果是,請(qǐng)求將繼續(xù)傳遞給過濾器鏈中的下一個(gè)過濾器;否則,設(shè)置響應(yīng)狀態(tài)碼為 UNAUTHORIZED,表示請(qǐng)求未被授權(quán),并終止請(qǐng)求處理。
3. 過濾器執(zhí)行順序
過濾器的執(zhí)行順序可以通過 @Order 注解的方式實(shí)現(xiàn),另外也可以通過實(shí)現(xiàn) Ordered 接口并重寫其中的 getOrder
方法。在上述代碼中,通過實(shí)現(xiàn) Ordered
接口,我們可以指定過濾器的執(zhí)行順序。在這個(gè)例子中,通過 getOrder
方法返回 -1
,表示這個(gè)過濾器的執(zhí)行順序較高,會(huì)在一般過濾器之前執(zhí)行。
4. 驗(yàn)證全局過濾器是否生效
重啟 gateway
服務(wù),首先直接訪問userservice
不帶任何參數(shù):
發(fā)現(xiàn)訪問失敗,并且響應(yīng)碼為 401
。
加上 authorization
參數(shù)再進(jìn)行訪問:
此時(shí)便可以成功訪問了,當(dāng)然,如果 authorization
參數(shù)的值不為 admin
也會(huì)訪問失敗:
通過定義這個(gè)簡(jiǎn)單的全局過濾器,我們實(shí)現(xiàn)了對(duì)用戶身份的基本驗(yàn)證。在實(shí)際應(yīng)用中,用戶身份驗(yàn)證通常會(huì)更為復(fù)雜,可能涉及調(diào)用認(rèn)證服務(wù)、檢查 token 簽名等操作。這個(gè)示例提供了一個(gè)基本的框架,可以根據(jù)實(shí)際需求進(jìn)行擴(kuò)展,確保微服務(wù)的安全性和可靠性。
五、過濾器鏈的執(zhí)行順序
在 Spring Cloud Gateway 中,過濾器鏈的執(zhí)行順序是確保請(qǐng)求經(jīng)過一系列過濾器并按照特定的規(guī)則執(zhí)行的關(guān)鍵。一般而言,請(qǐng)求進(jìn)入網(wǎng)關(guān)后會(huì)遇到三類過濾器:當(dāng)前路由的過濾器、DefaultFilter、GlobalFilter。
5.1 過濾器鏈執(zhí)行過程
請(qǐng)求路由后,當(dāng)前路由過濾器、DefaultFilter、GlobalFilter 會(huì)被合并到一個(gè)過濾器鏈(集合)中,并按照排序規(guī)則進(jìn)行排序,然后按照排序后的順序依次執(zhí)行。這個(gè)過程如下圖所示:
5.2 過濾器的排序規(guī)則
為了確保過濾器按照正確的順序執(zhí)行,遵循以下排序規(guī)則:
-
自定義過濾器: 每個(gè)自定義過濾器必須指定一個(gè)整數(shù)類型的
order
值,數(shù)值越小,優(yōu)先級(jí)越高,執(zhí)行順序越靠前。 -
GlobalFilter: 全局過濾器通過實(shí)現(xiàn)
Ordered
接口或添加@Order
注解來(lái)指定order
值,由開發(fā)者自行指定。值越小,優(yōu)先級(jí)越高。 -
路由過濾器和 DefaultFilter: 路由過濾器和 DefaultFilter 的
order
由 Spring 框架指定,默認(rèn)按照聲明順序從1遞增。 -
相同
order
值處理: 當(dāng)過濾器的order
值相同時(shí),執(zhí)行順序?yàn)?DefaultFilter > 路由過濾器 > GlobalFilter。具體實(shí)現(xiàn)可以查看一些關(guān)鍵類的源碼,如org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator#getFilters()
和org.springframework.cloud.gateway.handler.FilteringWebHandler#handle()
。
過濾器鏈的執(zhí)行順序非常關(guān)鍵,它保證了過濾器能夠按照開發(fā)者期望的順序執(zhí)行。通過合并路由過濾器、DefaultFilter 和 GlobalFilter,并按照指定的執(zhí)行順序排序,網(wǎng)關(guān)可以對(duì)請(qǐng)求進(jìn)行精細(xì)控制,實(shí)現(xiàn)各種定制化的業(yè)務(wù)邏輯。在實(shí)際開發(fā)中,了解和正確使用過濾器鏈的執(zhí)行規(guī)則是確保網(wǎng)關(guān)正常工作的基礎(chǔ)。
5.3 為什么三種不同的過濾器可以進(jìn)行排序形成過濾器鏈
首先對(duì)于路由過濾器和默認(rèn)過濾器中的AddRequestHeader
過濾器工廠來(lái)說,它們的底層都是由AddRequestHeaderGatewayFilterFactory
類實(shí)現(xiàn)的。
通過查看 AddRequestHeaderGatewayFilterFactory
類的源碼可以知道,它們最后最后轉(zhuǎn)化成一個(gè)相同類型的對(duì)象,即GatewayFilter
:
但是對(duì)于 GlobalFilter
和 GatewayFilter
這兩個(gè)接口來(lái)說,查看它們的源碼并沒有發(fā)現(xiàn)繼承關(guān)系:
那么是否就意味著它們之間沒有關(guān)系了呢?其實(shí)不然,在 FilteringWebHandler
這個(gè)類中有一個(gè)靜態(tài)內(nèi)部類GatewayFilterAdapter
:
可以發(fā)現(xiàn)這個(gè)類實(shí)現(xiàn)了GatewayFilter
接口,并且其中有一個(gè)屬性,其類型是GlobalFilter
??梢园l(fā)現(xiàn)可以類的作用是把GlobalFilter
適配成了GatewayFilter
。
因此,這三種過濾器最終都可以當(dāng)成GatewayFilter
,所有它們之間可以按照Ordered
接口、@Order
注解、默認(rèn)等指定的順序放在同一個(gè)過濾器鏈中。
六、跨域問題
6.1 什么是跨越問題
跨域問題是指在 web 應(yīng)用程序中,由于安全策略的限制,瀏覽器禁止在一個(gè)源(origin)的網(wǎng)頁(yè)應(yīng)用程序中發(fā)起對(duì)另一個(gè)源的 HTTP 請(qǐng)求。這個(gè)源包括協(xié)議、域名、端口的組合,只有當(dāng)兩個(gè)請(qǐng)求的源相同時(shí),瀏覽器才允許跨域請(qǐng)求。
跨域問題主要由以下情況引起:
-
不同域名: 當(dāng)兩個(gè)請(qǐng)求的域名不同,即使協(xié)議和端口相同,也會(huì)被認(rèn)為跨域。例如,
www.example.com
和api.example.com
。 -
不同端口: 即使在同一個(gè)域名下,如果請(qǐng)求的端口不同,也會(huì)被認(rèn)為跨域。例如,
example.com:8080
和example.com:3000
。 -
不同協(xié)議: 當(dāng)一個(gè)請(qǐng)求使用 HTTP 協(xié)議,而另一個(gè)使用 HTTPS 協(xié)議時(shí),也會(huì)被視為跨域。
跨域問題的存在是為了增加 web 安全性,防止惡意網(wǎng)站利用用戶的瀏覽器向其他網(wǎng)站發(fā)起惡意請(qǐng)求,竊取用戶信息等。
解決跨域問題的常見方式之一是使用 CORS(跨域資源共享)策略,它允許服務(wù)器在響應(yīng)中附加一些標(biāo)頭,告訴瀏覽器哪些域名允許跨域訪問資源。當(dāng)瀏覽器發(fā)起跨域請(qǐng)求時(shí),會(huì)先發(fā)送一個(gè)預(yù)檢請(qǐng)求(OPTIONS 請(qǐng)求)給服務(wù)器,服務(wù)器根據(jù)預(yù)檢請(qǐng)求的信息來(lái)判斷是否允許跨域訪問。如果服務(wù)器返回合適的響應(yīng)頭,瀏覽器才會(huì)允許跨域請(qǐng)求。這種方式可以有效地解決跨域問題,同時(shí)保障了網(wǎng)站的安全性。
6.2 演示跨越問題
為了演示跨域問題,我們首先創(chuàng)建一個(gè)簡(jiǎn)單的 HTML 文件 index.html
,其中包含了一個(gè)使用 Axios
發(fā)起的 AJAX 請(qǐng)求:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<pre>
spring:
cloud:
gateway:
globalcors: # 全局的跨域處理
add-to-simple-url-handler-mapping: true # 解決options請(qǐng)求被攔截問題
corsConfigurations:
'[/**]':
allowedOrigins: # 允許哪些網(wǎng)站的跨域請(qǐng)求
- "http://localhost:5500"
allowedMethods: # 允許的跨域ajax的請(qǐng)求方式
- "GET"
- "POST"
- "DELETE"
- "PUT"
- "OPTIONS"
allowedHeaders: "*" # 允許在請(qǐng)求中攜帶的頭信息
allowCredentials: true # 是否允許攜帶cookie
maxAge: 360000 # 這次跨域檢測(cè)的有效期
</pre>
</body>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>
axios.get("http://localhost:10010/user/1?authorization=admin")
.then(resp => console.log(resp.data))
.catch(err => console.log(err))
</script>
</html>
說明:
此時(shí),我們假設(shè) http://localhost:10010/user/1
是一個(gè)需要進(jìn)行跨域訪問的服務(wù)。然后,使用 VS Code 中的 Live Server 插件啟動(dòng) index.html
,然后通過瀏覽器的開發(fā)者工具查看控制臺(tái)的輸出內(nèi)容:
發(fā)現(xiàn)出現(xiàn)了跨越問題,導(dǎo)致不能正常訪問userservice
的服務(wù)。
6.3 使用 Gateway 解決跨域問題
6.3 使用 Gateway 解決跨域問題
為了解決跨域問題,我們可以在 gateway
服務(wù)的 application.yml
中新增如下的配置,使用 Spring Cloud Gateway 的跨域配置:
spring:
cloud:
gateway:
globalcors: # 全局的跨域處理
add-to-simple-url-handler-mapping: true # 解決options請(qǐng)求被攔截問題
corsConfigurations:
'[/**]':
allowedOrigins: # 允許哪些網(wǎng)站的跨域請(qǐng)求
- "http://localhost:5500"
allowedMethods: # 允許的跨域 ajax 的請(qǐng)求方式
- "GET"
- "POST"
- "DELETE"
- "PUT"
- "OPTIONS"
allowedHeaders: "*" # 允許在請(qǐng)求中攜帶的頭信息
allowCredentials: true # 是否允許攜帶 cookie
maxAge: 360000 # 這次跨域檢測(cè)的有效期
說明:
-
allowedOrigins
:允許跨域請(qǐng)求的源,這里設(shè)置為http://localhost:5500
。 -
allowedMethods
:允許的跨域 AJAX 請(qǐng)求方式,包括GET
、POST
、DELETE
、PUT
、OPTIONS
。 -
allowedHeaders
:允許在請(qǐng)求中攜帶的頭信息,設(shè)置為"*"
表示允許所有頭信息。 -
allowCredentials
:是否允許攜帶 Cookie。 -
maxAge
:這次跨域檢測(cè)的有效期,設(shè)置為360000
毫秒。
通過這樣的配置,我們告訴 Spring Cloud Gateway 允許指定的源(http://localhost:5500
)發(fā)起跨域請(qǐng)求,并指定了其他的一些跨域配置。
然后,重新訪問 localhost:5500
,發(fā)現(xiàn)控制臺(tái)成功輸出了訪問userservice
的結(jié)果了:文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-722862.html
文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-722862.html
到了這里,關(guān)于【Spring Cloud】深入探索統(tǒng)一網(wǎng)關(guān) Gateway 的搭建,斷言工廠,過濾器工廠,全局過濾器以及跨域問題的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!