Spring Cloud Alibaba:Gateway网关 & 路由断言工厂
Spring Cloud Gateway该项目提供了一个用于在Spring WebFlux之上构建API网关的库。Spring Cloud Gateway旨在提供一种简单而有效的方式来路由到API并为它们提供交叉关注点,例如:安全性、监控和弹性。Spring Cloud Gateway需要Spring Boot和Spring Webflux提供的Netty运行时环境。它不适用于传统的Servlet
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
网关和路由断言工厂就介绍到这里,如果博主有说错的地方或者大家有不同的见解,欢迎大家评论补充。
更多推荐
所有评论(0)