Spring Cloud Gateway
該項目提供了一個用于在
Spring Webflux
之上建構
API
網關的庫。
Spring Cloud Gateway
旨在提供一種簡單而有效的方式來路由到
API
并為它們提供交叉關注點,例如:安全、監控和彈性。
Spring Cloud Gateway
需要
Spring Boot
和
Spring Webflux
提供的
Netty
運作時環境。它不适用于傳統的
Servlet
容器或将應用建構為
WAR
包。如果強制使用傳統的
Servlet
來處理請求:
啟動應用時會報錯:
Spring Webflux is missing from the classpath, which is required for Spring Cloud Gateway at this time. Please add spring-boot-starter-webflux dependency.
Spring MVC found on classpath, which is incompatible with Spring Cloud Gateway at this time. Please remove spring-boot-starter-web dependency.
在
中缺少
classpath
,此時
Spring Webflux
需要它。請添加
Spring Cloud Gateway
spring-boot-starter-webflux
依賴項。
在
上發現
classpath
,此時與
Spring MVC
不相容。請删除
Spring Cloud Gateway
依賴項。
spring-boot-starter-web
重要概念:
- 路由(Route):路由是網關的基本子產品。它由
、目标ID
、URI
集合和Predicate
集合定義。如果聚合Filter
為真,則比對路由。Predicate
- 斷言(Predicate):輸入類型是
。它允許開發人員比對來自Spring Framework ServerWebExchange
請求的任何内容,例如請求頭或請求參數。HTTP
- 代碼注釋翻譯插件
:Translation
- 過濾器(Filter):使用特定工廠建構的
執行個體,可以在發送代理請求之前或之後修改請求和響應。Spring Framework GatewayFilter
Spring Cloud Gateway
特性:
- 基于
、Spring Framework 5
和Project Reactor
。Spring Boot 2.0
- 能夠比對任何請求屬性的路由。
- 特定于路由的斷言和過濾器。
- 內建
。Circuit Breaker
- 內建
。Spring Cloud DiscoveryClient
- 斷言和過濾器易于編寫。
- 請求速率限制。
- 路徑重寫。
Spring Cloud Gateway
工作方式(圖來自官網):
用戶端向
Spring Cloud Gateway
送出請求。如果
Gateway Handler Mapping
确定請求與路由比對,則将其發送到
Gateway Web Handler
。此處理程式通過特定于請求的過濾器鍊,将請求轉換成代理請求。過濾器被虛線分隔的原因是過濾器可能在發送代理請求之前或之後執行邏輯。執行所有
pre
過濾器邏輯(作用于請求),然後發出代理請求。代理請求得到響應後,執行所有
post
過濾器邏輯(作用于響應)。
搭建工程
一個父
module
和兩個子
module
(
nacos module
提供服務,
gateway module
實作網關)。
父
module
的
pom.xml
:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.kaven</groupId>
<artifactId>alibaba</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<description>Spring Cloud Alibaba</description>
<modules>
<module>nacos</module>
<module>gateway</module>
</modules>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<spring-cloud-version>Hoxton.SR9</spring-cloud-version>
<spring-cloud-alibaba-version>2.2.6.RELEASE</spring-cloud-alibaba-version>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.2.RELEASE</version>
</parent>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud-version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba-version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
nacos module
pom.xml
:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.kaven</groupId>
<artifactId>alibaba</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>nacos</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>
application.yml
:
server:
port: 8080
spring:
application:
name: nacos
cloud:
nacos:
discovery:
server-addr: 192.168.1.197:9000
接口定義:
package com.kaven.alibaba.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class MessageController {
@GetMapping("/message")
public String getMessage() {
return "hello kaven, this is nacos";
}
}
啟動類:
package com.kaven.alibaba;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class NacosApplication {
public static void main(String[] args) {
SpringApplication.run(NacosApplication.class);
}
}
gateway module
pom.xml
:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.kaven</groupId>
<artifactId>alibaba</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>gateway</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>
</project>
application.yml
:
server:
port: 8085
spring:
application:
name: gateway
cloud:
nacos:
server-addr: 192.168.1.197:9000
gateway:
routes:
- id: nacos
uri: http://localhost:8080
predicates:
- Path=/message
啟動類:
package com.kaven.alibaba;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class);
}
}
啟動這兩個
module
,
Nacos
的服務清單就會出現這兩個服務。
路由斷言工廠
Spring Cloud Gateway
比對路由作為
Spring Webflux HandlerMapping
基礎功能的一部分。
Spring Cloud Gateway
包括許多内置的路由斷言工廠,這些路由斷言工廠都比對
HTTP
請求的不同屬性,可以通過邏輯
and
來組合多個路由斷言工廠。
After
After
路由斷言工廠接受一個日期時間,該斷言比對該日期時間之後的請求。生成這個日期時間的示例代碼如下所示:
package com.kaven.alibaba;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
public class Test {
public static void main(String[] args) {
ZonedDateTime nowDateTime = LocalDateTime.now()
.atZone(ZoneId.systemDefault());
System.out.println(nowDateTime);
ZonedDateTime updateDateTime = LocalDateTime.now()
.plusMonths(1)
.minusDays(1).minusHours(1)
.atZone(ZoneId.systemDefault());
System.out.println(updateDateTime);
ZonedDateTime hardCodeDateTime = LocalDateTime.
of(2021, 12, 17, 18, 54, 17, 0)
.atZone(ZoneId.systemDefault());
System.out.println(hardCodeDateTime);
}
}
輸出:
2021-12-17T19:15:40.297+08:00[Asia/Shanghai]
2022-01-16T18:15:40.298+08:00[Asia/Shanghai]
2021-12-17T18:54:17+08:00[Asia/Shanghai]
表示年、月、日,時、分、秒、納秒(不是毫秒)以及時區。
- After=2021-12-17T19:30:00+08:00[Asia/Shanghai]
不在指定日期時間之後通路接口會直接響應
404
。
指定日期時間之後,接口就可以正常通路了。
Before
Before
路由斷言工廠接受一個日期時間。此斷言比對在該日期時間之前發生的請求。由于和
After
路由斷言工廠類似,這裡就不再示範。
- Before=2021-12-17T19:38:00.129+08:00[Asia/Shanghai]
Between
Between
路由斷言工廠接受兩個日期時間。此斷言比對發生在
datetime1
之後和
datetime2
之前的請求。
Between
路由斷言工廠與上面兩種路由斷言工廠類似,這裡也不再示範。
- Between=2021-12-17T19:38:00.129+08:00[Asia/Shanghai],2021-12-17T19:42:00.129+08:00[Asia/Shanghai]
datetime1
參數指定的日期時間必須在
datetime2
參數指定的日期時間之前。
Cookie
Cookie
路由斷言工廠接受兩個參數,即
Cookie
名稱和一個正規表達式。此斷言比對具有給定名稱且值與正規表達式比對的
Cookie
的請求。
- Cookie=kaven,*kaven*
使用
Postman
來測試,請求沒有
Cookie
:
請求沒有比對的
Cookie
:
請求有比對的
Cookie
:
路由的斷言配置是一個
List
類型,是以可以配置多個斷言,當聚合斷言(聚合在資訊科學中是指對有關的資料進行内容挑選、分析、歸類,最後分析得到人們想要的結果,看完這篇部落格就應該了解這裡使用聚合這個詞是比較合适的,因為斷言中有邏輯
and
也有邏輯
or
)為真時,才比對路由。
- Cookie=username,.*kaven.*
- Cookie=password,.*kaven.*
請求有所有比對的
Cookie
:
請求隻有部分比對的
Cookie
:
是以,當斷言清單中有互相沖突的斷言時,該路由就不可能比對成功。
Header
Header
路由斷言工廠接受兩個參數,
Header
名稱和一個正規表達式。此斷言與具有給定名稱且值與正規表達式比對的
Header
的請求比對。
- Header=gateway,.*kaven.*
Host
Host
路由斷言工廠接受一個參數:
Host
模式清單。該模式是一個
Ant
風格(請求路徑的一種比對方式)的模式,以
.
作為分隔符。此斷言比對
Host
滿足清單中任意模式的請求。
Ant
通配符如下圖所示:
- Host=**.kaven.com,**.kaven.top
*
和
?
通配符這裡就不示範了。
Method
Method
路由斷言工廠接受一個參數:要比對的
HTTP
方法。
- Method=GET
為了示範必須要滿足指定的
HTTP
方法才能比對路由,在
nacos module
中增加一個
POST
接口。
@PostMapping("/message")
public String updateMessage(String message) {
return message;
}
POST
方法不比對指定的
GET
方法,直接響應
404
。
指定多個要比對的
HTTP
方法,滿足其中一個就可以比對路由。
- Method=GET,POST
Path
Path
路由斷言工廠接受兩個參數:一個
Spring PathMatcher
模式清單和一個可選的标志
matchOptionalTrailingSeparator
(此參數為
true
時,如果模式沒有尾部斜杠,請求路徑有尾部斜杠也能成功比對,否則不能成功比對,該參數預設為
true
)。該模式比對也符合
Ant
風格,
Ant
通配符如下圖所示:
為了友善示範,在
nacos module
中增加幾個接口。
@GetMapping("/kaven")
public String kaven() {
return "hello kaven";
}
@GetMapping("/kaven/1")
public String kaven1() {
return "hello kaven, this is 1";
}
@GetMapping("/kaven/1/2")
public String kaven1_2() {
return "hello kaven, this is 1/2";
}
@GetMapping("/itkaven")
public String itkaven() {
return "hello itkaven";
}
@GetMapping("/itkaven/1")
public String itkaven1() {
return "hello itkaven, this is 1";
}
@GetMapping("/itkaven/1/2")
public String itkaven1_2() {
return "hello itkaven, this is 1/2";
}
- Path=/kaven/{path},/itkaven/**
/itkaven/**
表示路徑是否以
/itkaven
開頭(
**
表示後面有
0
個或者多個機關路徑,
*
和
?
通配符這裡就不示範了),如果是則比對。
/kaven/{path}
表示路徑是否以
/kaven
開頭并且後面有
1
個機關路徑,如果是則比對。
/kaven/2
這個路徑是比對的,但
nacos module
中沒有這個接口,仔細觀察可以知道,
/kaven/2
路徑的響應和
/kaven
路徑的響應是不一樣的(傳回響應的主體不同,前者的響應是
nacos module
傳回的,而後者的響應是
gateway module
傳回的)。
路由的斷言配置修改成如下所示,表示路徑是否以
/kaven
開頭并且後面有
2
個機關路徑(以此類推)。
- Path=/kaven/{path1}/{path2}
/kaven/1/2
路徑就可以比對成功了。
matchOptionalTrailingSeparator
為
false
。
- Path=/kaven/{path},false
路徑有尾部斜杠不能成功比對。
matchOptionalTrailingSeparator
為
true
(預設)。
- Path=/kaven/{path},true
路徑有尾部斜杠也能成功比對。
如果模式有尾部斜杠,請求路徑也必須有尾部斜杠,此時
matchOptionalTrailingSeparator
參數的值就不起作用了。
Query
Query
路由斷言工廠接受兩個參數:必需的
param
和可選的
regexp
。
- Query=kaven
- Query=kaven,.*itkaven.*
RemoteAddr
RemoteAddr
路由斷言工廠采用
CIDR
(用于解釋
IP
位址的标準)表示的
IPv4
或
IPv6
字元串清單(最少
1
個),例如
192.168.1.199/24
(其中
192.168.1.199
是
IP
位址,
24
是子網路遮罩位數)。
- RemoteAddr=192.168.1.1/24
在虛拟機中發送請求,虛拟機
IP
位址比對。
- RemoteAddr=192.168.2.1/24
虛拟機
IP
位址已經不比對了。