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地址已经不匹配了。

Gateway网关和路由断言工厂就介绍到这里,如果博主有说错的地方或者大家有不同的见解,欢迎大家评论补充。










