0%

Gateway详解:服务网关配置

前言

SpringCloudGateway是Spring官方基于Spring 5.0,SpringBoot 2.0和ProjectReactor等技术开发的网关,SpringCloudGateway旨在为微服务架构提供一种简单而有效的统一的API路由管理方式。在博客‘SpringCloudAlibaba微服务项目’中我们使用gateway实现了简单的网关转发服务,在本文中,我们来进一步学习gateway的其他功能。

简介

一、为什么需要网关服务

微服务架构中有多个服务,如果没有网关,客户端在调用接口时必须使用每个微服务的地址,当微服务数量很多时,这就是很大的工作量,还面临无法统一鉴权、跨域无法访问等问题。引入网关服务后,网关作为所有请求的唯一入口,这样一来,就简化了客户端工作,客户端只需要和网关交互,而不再需要关注项目中的各种微服务。

当然,这就又引入了一个问题,就是网关又成为了微服务体系中的瓶颈,如果网关宕机,那么整个微服务项目也就宕机了;解决这一问题的方案是将网关又分为流量网关和API网关,流量网关负责限制整体的流量控制等,而API网关负责与业务紧密结合的服务治理、身份认证、路由等。流量网关我们可以用nginx来担任,而在springcloud项目中,我们可以使用gateway来担任API网关。

img

二、Gateway三大核心

  • 路由(Route:网关的基本构建块。它由 ID、目标 URI、断言集合和过滤器集合定义。如果断言为真,则路由匹配。
  • 断言(Predicate:这是一个Java 8 函数谓词。输入类型是Spring 框架ServerWebExchange。这允许您匹配 HTTP 请求中的任何内容,例如标头(headers)或参数。
  • 过滤器(Filter:这些是使用特定工厂构建的Spring FrameworkGatewayFilter实例。在这里,您可以在发送下游请求之前或之后修改请求和响应。

img

gateway服务转发示意图

三、Gateway工作流程

客户端向Spring Cloud Gateway发出请求。然后在Gateway Handler Mapping中找到与请求相匹配的路由,将其发送到Gateway Web Handler。Handler再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前(’pre’)或之后(’post’)执行业务逻辑。

img

Gateway工作流程示意图

功能详解

一、路由功能

1. 自定义路由

我们在之前的demo中已经实现了一个路由,我们来看看其中的配置,这是我们配置在nacos中的gateway.yaml文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
server:
port: 8088

spring:
cloud:
# 网关配置
gateway:
# 配置路由规则
routes:
# 路由唯一 id 名
- id: order_route
# 匹配成功时,要将请求转发到的服务
uri: lb://service-order
# 定义断言,即 地址匹配 规则
predicates:
# 当访问请求以‘/order’开头,后面跟随的内容不限制,就匹配成功(断言为true);例如:‘http://localhost:8088/order/add?productId=1’
- Path=/order/**

从配置文件中可以看到,配置的‘uri’指向‘service-order’服务,这一服务我们已经注册到了nacos中,nacos会自动帮我们解析地址并转发;而根据配置的断言‘predicates’,当请求路径中以‘/order’开头,则匹配成功,可以转发;我们来简单测试

image-20250223104014433

自定义路由测试

image-20250223135153807

自定义路由测试结果

可以看到我们访问gateway服务,它自动帮我们转发到了‘service-order’服务

2. 自动路由

gateway的自动路由功能,可以从服务注册中心(如 Eureka、Consul、Nacos 等)中的服务列表,为每个服务生成默认的路由规则;路由的 Path/serviceId/**,其中 serviceId 是服务的名称(通常是小写形式);例如,如果注册中心有一个服务名为 user-service,Gateway 会自动生成一个路由规则,将 /user-service/** 的请求转发到 user-service 服务。

在gateway服务的配置文件中添加如下内容:

1
2
3
4
5
6
7
8
# 其他内容保持不变
spring:
cloud:
gateway:
# 开启服务列表发现并拥有自动路由功能
discovery:
locator:
enabled: true

重新启动gateway服务以后我们来做简单测试

image-20250223150511819

自动路由测试结果1

image-20250223150356524

自动路由测试结果2

可以看到,虽然我们没有添加关于‘service-account’的路由设置,但是我们配置了自动路由,所以我们的请求被转发到了‘service-account’服务

3. 在代码中实现路由功能

我们除了在yaml文件中进行路由配置,也可以在代码中实现路由配置

1
2
3
4
5
6
7
8
9
10
11
@Configuration
public class GatewayConfig {
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder) {
return routeLocatorBuilder.routes()
//可以使用通配符,例如:/provider/test/**
.route("provider-route", r -> r.path("/provider/test/sendMessage")
.uri("lb://service-provider"))
.build();
}
}

二、断言功能

断言(Predicate)也翻译为谓词,是一个函数式接口,通常用于表示一个返回值为布尔值的函数(即返回 truefalse 的函数)。它常用于条件判断、过滤、匹配等场景。

1. Path

这个谓词即通过请求路径匹配,可以使用正则表达式,多个请求路径可以使用逗号进行分隔;这个谓词我们在自定义路由中已经使用过了,这里就不赘述

1
2
3
4
5
6
7
8
9
10
# 示例
spring:
cloud:
gateway:
routes:
- id: host_route
uri: https://example.org
predicates:
# /foo/1 或 /foo/bar 或 /bar/baz
- Path=/foo/{segment},/bar/{segment},/order/**,account/*
  • 注:** 用于多层路径匹配,适合递归匹配整个目录树,* 用于单层路径匹配,适合匹配当前目录的文件或目录。
2. After
(1)谓词介绍

​ 该谓词匹配在指定日期时间之后发生的请求

(2)添加配置

​ 在‘gateway’的yaml文件中添加配置

1
2
3
4
5
6
7
8
9
10
11
12
# 这里只给出了部分配置
spring:
cloud:
gateway:
routes:
# 谓词after测试
- id: after-predicates-route
uri: lb://service-account
predicates:
- Path=/gatewayTest/after
# 匹配所有时间在2025年2月22日23点59分59秒以后的请求,多个谓词之间必须都符合才能通过
- After=2025-02-22T23:59:59.255+08:00[Asia/Shanghai]

时间戳可以用下面的代码生成

1
2
3
4
5
6
public class ZonedDateTimeDemo {
public static void main(String[] args) {
ZonedDateTime zonedDateTime = ZonedDateTime.now();
System.out.println(zonedDateTime);
}
}
(3)在‘service-account’中添加代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Slf4j
@RestController
@RequestMapping("/gatewayTest")
public class GatewayTestController {
/**
* 谓词after测试
*
* @return
*/
@GetMapping("/after")
public Result afterTest() {
log.info("com.qiuli.account.controller.TestController.afterTest");
return Result.ok().message("Predicates After Test");
}
}
(4)重启服务后进行测试

image-20250223175112695

image-20250223175157825

从测试结果我们可以看到我们是在23日进行的测试,而我们设置的指定日期是在22日23点59分59秒后,是符合条件的,所以成功转发;

我们可以设置一个还没有到的时间,比如就将日期延后一天

image-20250223174351925

重启服务后再次测试

image-20250223175353576

可以看到,因为设置的时间还没到,所以这次的转发是失败的

3. Before
(1)谓词介绍

​ 该谓词匹配在指定日期时间之前发生的请求

(2)添加配置

​ 在‘gateway’的yaml文件中添加配置

1
2
3
4
5
6
7
8
9
10
11
12
# 这里只给出了部分配置
spring:
cloud:
gateway:
routes:
# 谓词after测试
- id: after-predicates-route
uri: lb://service-account
predicates:
- Path=/gatewayTest/before
# 匹配所有时间在2025年2月23日23点59分59秒之前的请求,多个谓词之间必须都符合才能通过
- After=2025-02-23T23:59:59.255+08:00[Asia/Shanghai]
(3)在‘service-account’中添加代码
1
2
3
4
5
6
7
8
9
10
/**
* 谓词before测试
*
* @return
*/
@GetMapping("/before")
public Result beforeTest() {
log.info("com.qiuli.account.controller.GatewayTestController.beforeTest");
return Result.ok().message("Predicates Before Test");
}
(4)重启服务后进行测试

image-20250223200345458

image-20250223200312481

可以看到,我们在23日测试,设置条件为23日23点59分59秒之前,所以请求成功转发 了;我们可以修改指定时间为22日23点59分59秒

image-20250223201138319

再次进行测试

image-20250223201216477

可以看到,因为已经超过了测试时间,所以这次的转发请求失败了

4. Between
(1)谓词介绍

​ 该谓词匹配在指定日期时间之间发生的请求

(2)添加配置

​ 在‘gateway’的yaml文件中添加配置

1
2
3
4
5
6
7
8
9
10
11
12
# 这里只给出了部分配置
spring:
cloud:
gateway:
routes:
# 谓词between测试
- id: between-predicates-route
uri: lb://service-account
predicates:
- Path=/gatewayTest/between
# 匹配所有时间在2025年2月22日23点59分59秒到2025年2月23日23点59分59秒之间的请求,多个谓词之间必须都符合才能通过
- Between=2025-02-22T23:59:59.255+08:00[Asia/Shanghai],2025-02-23T23:59:59.255+08:00[Asia/Shanghai]
(3)在‘service-account’中添加代码
1
2
3
4
5
6
7
8
9
10
/**
* 谓词between测试
*
* @return
*/
@GetMapping("/between")
public Result betweenTest() {
log.info("com.qiuli.account.controller.GatewayTestController.betweenTest");
return Result.ok().message("Predicates Between Test");
}
(4)重启服务后进行测试

image-20250223202454840

image-20250223202535152

我们的测试时间为23日,设置时间为22日23点59分59秒到23日23点59分59秒,所以请求成功转发;但如果我们设置时间到一天之前,21日23点59分59秒到22日23点59分59秒,那么现在的时间就不在范围内

image-20250223202858467

我们再次测试

image-20250223202929789

可以看到,这次请求没有成功转发

(1)谓词介绍

cookie是一个键值对,而本谓词有两个参数,一个是 Cookie name , 一个是正则表达式;路由规则会通过获取对应的 Cookie 值和正则表达式去匹配,如果匹配上就会执行路由,如果没有匹配上则不执行

(2)添加配置

​ 在‘gateway’的yaml文件中添加配置

1
2
3
4
5
6
7
8
9
10
11
#这里只给出了局部配置,其他配置保持不变
spring:
cloud:
gateway:
routes:
- id: cookie-predicates-route
uri: lb://service-account
predicates:
- Path=/gatewayTest/cookie
# 你的请求必须带有Cookie,并且其中有Cookie的名称叫password,Cookie的值会跟你自己定义的Java正则匹配,这里的\d+,代表密码必须为数字
- Cookie=password,\d+
(3)在项目‘service-account’中添加代码
1
2
3
4
5
6
7
8
9
/**
* 谓词cookie测试
* @return
*/
@GetMapping("/cookie")
public Result cookieTest() {
log.info("com.qiuli.account.controller.GatewayTestController.cookieTest");
return Result.ok().message("Predicates Cookie Test");
}
(4)重启项目后进行测试

首先我们新建一个request,填写基本测试信息后点击‘save’旁的‘Cookies’按钮,来到Cookie管理页面;先添加目标域名(我们在本地测试,所以填localhost),再在目标域名下添加一个cookie,根据gateway中配置的路由,我们添加如下cookie

1
password=123; Path=/; Expires=Tue, 24 Feb 2026 01:26:38 GMT;

点击保存,然后再发送请求

image-20250224093755510

请求中包含的cookie

image-20250224093852361

测试结果1

image-20250224093959538

测试结果2

可以看到,我们配置了符合路由规则的cookie后,请求已经正确转发了;如果我们将cookie的值改为字符,再次测试,请求将转发失败

image-20250224145912428

将cookie的值改为字母

image-20250224150011381

转发失败
6. Header
(1)谓词介绍

​ Header谓词和Cookie谓词一样,也是一个键值对,一个 header 中属性名称和一个正则表达式,这个属性值和正则表达式匹配则执行

(2)添加配置
1
2
3
4
5
6
7
8
9
10
11
#这里只给出了局部配置,其他配置保持不变
spring:
cloud:
gateway:
routes:
- id: header-predicates-route
uri: lb://service-account
predicates:
- Path=/gatewayTest/header
# 你的请求必须带有Header,并且其中有Header的名称叫Authorization,Header的值会跟你自己定义的Java正则匹配,这里的\d+,代表值必须为数字
- Header=Authorization,\d+
(3)在项目‘service-account’中添加代码
1
2
3
4
5
6
7
8
9
10
/**
* 谓词header测试
*
* @return
*/
@GetMapping("/header")
public Result headerTest() {
log.info("com.qiuli.account.controller.GatewayTestController.headerTest");
return Result.ok().message("Predicates Header Test");
}
(4)重启服务后进行测试

​ 我们在postman中新建一个request,填写基本信息后在Headers栏目中添加‘Authorization:123’,然后发送请求

image-20250224112534485

image-20250224112625053

可以看到,我们根据路由规则在header中配置了相应参数,请求成功转发;如果我们将header中的参数删除,再次发送请求,请求将转发失败

image-20250224145658273

转发失败
7. Host
(1)谓词介绍

​ Host谓词接收一组参数,一组匹配的域名列表,多个域名之间使用逗号分隔,它通过参数中的主机地址作为匹配规则

(2)添加配置
1
2
3
4
5
6
7
8
9
10
11
#这里只给出了局部配置,其他配置保持不变
spring:
cloud:
gateway:
routes:
- id: host-predicates-route
uri: lb://service-account
predicates:
- Path=/gatewayTest/host
# 如果请求的Host标头值为www.somehost.org或beta.somehost.org或此路由匹配www.anotherhost.org。匹配通过才可路由
- Host=**.somehost.org,**.anotherhost.org
(3)在项目‘service-account’中添加代码
1
2
3
4
5
6
7
8
9
10
/**
* 谓词host测试
*
* @return
*/
@GetMapping("/host")
public Result hostTest() {
log.info("com.qiuli.account.controller.GatewayTestController.hostTest");
return Result.ok().message("Predicates Host Test");
}
(4)重启服务后进行测试

​ 在postman新建一个request,填写基本信息后在headers栏目添加参数‘Host:www.somehost.org',发送请求进行测试

image-20250224161034544

添加Host参数后发送请求

image-20250224161418061

成功收到转发请求

可以看到请求成功转发;我们再修改Host参数为’Host:www.baidu.com‘,再次发送请求

image-20250224161521165

请求转发失败

可以看到,当Host参数与设置的值不匹配,请求转发失败

8. Method
(1)谓词介绍

​ 该谓词可以通过你请求的方式(GET、POST、PUT、DELETE、…)来进行匹配,只有指定方式的请求才可以匹配成功

(2)添加配置
1
2
3
4
5
6
7
8
9
10
11
#这里只给出了局部配置,其他配置保持不变
spring:
cloud:
gateway:
routes:
- id: method-predicates-route
uri: lb://service-account
predicates:
- Path=/gatewayTest/method
# 如果请求类型是GET或者POST请求才能匹配成功,其他请求类型均失败,失败就会404
- Method=GET,POST
(3)在项目’service-account‘中添加代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 谓词method测试
*
* @return
*/
@GetMapping("/method")
public Result methodTest1() {
log.info("com.qiuli.account.controller.GatewayTestController.methodTest1");
return Result.ok().message("Predicates Method Test1");
}

/**
* 未测method测试
*
* @return
*/
@PostMapping("/method")
public Result methodTest2() {
log.info("com.qiuli.account.controller.GatewayTestController.methodTest2");
return Result.ok().message("Predicates Method Test2");
}
(4)重启服务后进行测试

​ 我们在postman新建一个request,先用get请求类型测试

image-20250224170403046

GET请求类型测试结果

image-20250224170929226

成功接到转发请求

可以看到请求成功,然后再用POST请求类型测试

image-20250224170638639

POST请求类型测试结果

image-20250224171041675

成功接到转发请求

可以看到请求转发成功;我们再用PUT类型发送请求

image-20250224171211897

PUT请求类型测试结果

可以看到,如果请求类型不匹配,请求转发失败

9. Query
(1)谓词介绍

​ 这个谓词是根据请求中的参数进行匹配,该谓词也支持传入两个值,一个是属性名一个是属性值,属性值可以是正则表达式

(2)添加配置
1
2
3
4
5
6
7
8
9
10
11
#这里只给出了局部配置,其他配置保持不变
spring:
cloud:
gateway:
routes:
- id: query-predicates-route
uri: lb://service-account
predicates:
- Path=/gatewayTest/query
# 如果请求参数中包含number参数,并且number参数必须符合逗号后边的正则表达式的值(这里必须为数字),只有匹配成功才可以路由
- Query=number,\d+
(3)在项目’service-account‘中添加代码
1
2
3
4
5
6
7
8
9
10
11
/**
* 谓词query测试
*
* @param number
* @return
*/
@GetMapping("/query")
public Result queryTest(@RequestParam Integer number) {
log.info("com.qiuli.account.controller.GatewayTestController.queryTest");
return Result.ok().message("Predicates Query Test");
}
(4)重启服务后进行测试

​ 在postman中新建一个request,请求中添加参数number=123,然后进行测试

image-20250224173430866

添加参数number进行测试

image-20250224200357972

成功接收到转发的请求

可以看到,添加了符合设置的参数,请求转发成功;我们将参数改为字符串,再次测试

image-20250224210450614

修改参数后进行测试

可以看到,我们传递的参数不符合设置的正则表达式,请求转发失败

10. RemoteAddr
(1)谓词介绍
1
该谓词通过ip和子网掩码进行匹配,支持设置某个 ip 区间号段,支持单个 ip 地址,还支持接受 cidr 符号 (IPv4 或 IPv6) 字符串的列表(最小大小为 1),例如 192.168.0.1/16 (其中 192.168.0.1 是客户端发送请求的 ip 地址,16指二进制16位数字,即子网掩码(255.255.0.0),24位即子网掩码(255.255.255.0))
(2)添加配置
1
2
3
4
5
6
7
8
9
10
11
12
#这里只给出了局部配置,其他配置保持不变
spring:
cloud:
gateway:
routes:
- id: remote-addr-predicates-route
uri: lb://service-account
predicates:
- Path=/gatewayTest/remoteAddr
# 请求必须是192.168.124.12,并且子网掩码为255.255.255.0的地址发出的才可以路由
# 注:我们在本地测试,这里配置的就是本机ip,本机的ip通过’ipconfig /all‘查看
- RemoteAddr=192.168.124.12/24
(3)在项目’service-account‘中添加代码
1
2
3
4
5
6
7
8
9
10
/**
* 谓词RemoteAddr测试
*
* @return
*/
@GetMapping("/remoteAddr")
public Result remoteAddrTest() {
log.info("com.qiuli.account.controller.GatewayTestController.remoteAddrTest");
return Result.ok().message("Predicates Remote Addr Test");
}
(4)重启服务后进行测试

​ 在postman新建一个request,设置请求url为’http://192.168.124.12:8088/gatewayTest/remoteAddr‘,进行测试

image-20250225103423607

设置ip后进行测试

image-20250225103556768

测试结果

可以看到,我们使用设置的ip发送请求,请求被成功转发;我们可以再修改ip发送请求

image-20250225103814415

修改ip后的测试结果

可以看到,我们没有使用设置的ip发送(localhost是回环地址,即127.0.0.1),请求转发失败

11. Weight
(1)谓词介绍
1
2
3
4
5
6
7
8
该谓词用于实现基于权重的路由分发。它允许你将流量按指定的权重比例分发到不同的服务实例或路由上。这对于实现灰度发布、A/B 测试或负载均衡的场景非常有用。

谓词的配置需要两个参数:
group:权重组的名称。同一组的路由会共享权重分配逻辑。
weight:当前路由的权重值。权重值越高,分配到该路由的流量比例越大。
# 示例
- Weight=group, weight
# 注:只有group名称相同(即在同一组中的路由),权重配置才会生效;
(2)添加配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#这里只给出了局部配置,其他配置保持不变
spring:
cloud:
gateway:
routes:
- id: weight-1-predicates-route
uri: lb://service-account
predicates:
- Path=/gatewayTest/weight
- Weight=group-test, 80
- id: weight-2-predicates-route
uri: lb://service-order
predicates:
- Path=/gatewayTest/weight
- Weight=group-test, 20

​ 根据该配置,80%的请求会转发到服务service-account,20%的请求会转发到服务service-order

(3)在项目中添加代码

​ 在’service-account‘中添加代码

1
2
3
4
5
6
7
8
9
10
/**
* 谓词weight测试
*
* @return
*/
@GetMapping("/weight")
public Result weightTest() {
log.info("com.qiuli.account.controller.GatewayTestController.weightTest");
return Result.ok().message("Predicates Weight Test");
}

​ 在’service-order‘中添加代码

1
2
3
4
5
6
7
8
9
10
/**
* 谓词weight测试
*
* @return
*/
@GetMapping("/weight")
public Result weightTest() {
log.info("com.qiuli.order.controller.GatewayTestController.weightTest");
return Result.ok().message("Predicates Weight Test");
}
(4)在 pom.xml 中添加 Actuator 依赖
1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
(5)启用 Actuator 端点

​ 在 项目的yaml文件中配置 Actuator 端点

1
2
3
4
5
management:
endpoints:
web:
exposure:
include: "*"
(6)重启服务后进行测试

​ 我们首先用postman测试接口是否正常

image-20250225160650987

接口测试

可以看到接口是可以正常访问的;接下来我们使用JMeter发送批量请求,新建一个测试计划,添加线程组,设置10个线程,每个线程循环请求10次

image-20250225165022453

测试计划1

image-20250225173304050

测试计划2

然后我们用引入的Actuator组件来查看统计信息

1
2
3
4
5
6
7
8
9
10
# Actuator的使用方式
# 访问 /actuator/metrics 查看所有指标。
# 访问 /actuator/metrics/http.server.requests 查看 HTTP 请求的统计信息。
# 使用 tag 参数过滤特定接口的流量
# curl http://localhost:8080/actuator/metrics/http.server.requests?tag=uri:/api/test

# 我们先查看’service-account‘服务的访问情况(端口号为8001),用浏览器访问如下链接
http://localhost:8001/actuator/metrics/http.server.requests?tag=uri:/gatewayTest/weight
# 查看’service-order‘服务的访问情况(端口号为8003),用浏览器访问如下链接
http://localhost:8001/actuator/metrics/http.server.requests?tag=uri:/gatewayTest/weight

image-20250225173756291

’service-account‘访问数据

image-20250225173858998

’service-order‘访问数据

可以看到,服务’service-account‘有81次访问,服务’service-order‘有21次访问,符合我们在gateway配置文件中的设置

三、过滤功能

Gateway的过滤功能根据作用域划分为GatewayFilter(网关过滤器)和GlobalFilter(全局过滤器)两类,每类过滤器又分为系统内置的和自定义的两种。内置过滤器种类很多,这里不一一列举,仅举例说明,也不再测试。

1. 系统内置的全局过滤器

官方网址:https://docs.spring.io/spring-cloud-gateway/docs/2.2.6.RELEASE/reference/html/#global-filters

img

全局过滤器的种类
过滤器名称 默认顺序 作用
NettyWriteResponseFilter -1 将后端服务的响应写回客户端。
RouteToRequestUrlFilter 10000 将路由的 URI 转换为实际请求的 URL。
LoadBalancerClientFilter 10100 使用负载均衡器选择后端服务实例。
ForwardRoutingFilter 50000 将请求转发到指定的 URI。
NettyRoutingFilter 2147483647 使用 Netty 客户端将请求转发到后端服务。

全局过滤器的执行顺序由默认顺序决定,值越小,优先级越高。

官方说明:

1
2
GlobalFilter:全局过滤器,不需要在配置文件中配置,作用在所有的路由上,最终通过GatewayFilterAdapter包装成GatewayFilterChain可识别的过滤器,它为请求业务以及路由的URI转换为真实业务服务请求地址的核心过滤器,不需要配置系统初始化时加载,并作用在每个路由上。
- 也就是说全局过滤器是默认生效的。
2. 自定义的全局过滤器

​ 自定义网关过滤器需要实现两个接口:GlobalFilterOrdered

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
@Slf4j
@Component
public class MyGlobalFilter implements GlobalFilter, Ordered {

/**
* 重写过滤器逻辑
*
* @param exchange
* @param chain
* @return
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 获取请求
ServerHttpRequest request = exchange.getRequest();

// 打印请求路径
log.info("Request Path: " + request.getPath());

// 修改请求头(示例)
ServerHttpRequest modifiedRequest = request.mutate()
.header("X-Custom-Header", "CustomValue")
.build();

// 继续执行过滤器链
return chain.filter(exchange.mutate().request(modifiedRequest).build());
}

/**
* 过滤器链执行顺序,数值越小,执行顺序越靠前
*
* @return
*/
@Override
public int getOrder() {
return 0;
}
}
3. 系统内置的网关过滤器

官方网址:https://docs.spring.io/spring-cloud-gateway/docs/2.2.6.RELEASE/reference/html/#gatewayfilter-factories

(1)RewritePath

​ 作用:重写请求路径,将客户端请求的路径重写为后端服务所需的路径,从而实现路径映射或路径转换的功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
filters:
- RewritePath=<正则表达式>, <替换后的路径>

# 这里只给出了局部配置,其他配置保持不变
spring:
cloud:
gateway:
routes:
- id: RewritePath-predicates-route
uri: lb://service
predicates:
- Path=/api/**
- RewritePath=/api/(?<segment>/?.*), /$\{segment}

/api/(?<segment>.*) 表示匹配 /api/ 开头的路径,并将剩余部分捕获到 segment 组中,例如:/api/user会被重写为/user

(2)StripPrefix

​ 作用:去除请求路径中的指定数量的前缀部分,将剩余部分作为新的路径转发给后端服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
filters:
- StripPrefix=<要去除的前缀部分数量>

# 这里只给出了局部配置,其他配置保持不变
spring:
cloud:
gateway:
routes:
- id: StripPrefix-predicates-route
uri: lb://service
predicates:
- Path=/api/**
filters:
- StripPrefix=1

​ 客户端请求 /api/user 会被去掉 /api 前缀(去除1级前缀),最终转发给后端服务的路径是 /user

(3)PrefixPath

​ 作用:在客户端请求的路径前添加指定的前缀,将新的路径转发给后端服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
filters:
- PrefixPath=<要添加的前缀>

# 这里只给出了局部配置,其他配置保持不变
spring:
cloud:
gateway:
routes:
- id: PrefixPath-predicates-route
uri: lb://service
predicates:
- Path=/**
filters:
- PrefixPath=/api

​ 在所有请求前面加上/api再转发给后端服务,如/user变为/api/user

(4)SetPath

​ 作用:将客户端请求的路径替换为指定的路径,将新的路径转发给后端服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
filters:
- SetPath=<替换后的路径>

# 这里只给出了局部配置,其他配置保持不变
spring:
cloud:
gateway:
routes:
- id: SetPath-predicates-route
uri: lb://service
predicates:
- Path=/user
filters:
- SetPath=/order

​ 将/user替换为/order

(5)SetStatus

​ 作用:设置 HTTP 响应的状态码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
filters:
- SetStatus=<状态码>

# 这里只给出了局部配置,其他配置保持不变
spring:
cloud:
gateway:
routes:
- id: SetPath-predicates-route
uri: lb://service
predicates:
- Path=/user
filters:
- SetStatus=403

​ 访问/user的请求返回状态码403

(6)AddRequestParameter

​ 作用:向请求中添加指定的查询参数,将修改后的请求转发给后端服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
filters:
- AddRequestParameter=<键>,<值>

# 这里只给出了局部配置,其他配置保持不变
spring:
cloud:
gateway:
routes:
- id: AddRequestParameter-predicates-route
uri: lb://service
predicates:
- Path=/user/{id}
filters:
- AddRequestParameter=userId,{id}

​ 为/user请求添加参数’userId={id}‘

(7)AddRequestHeader

​ 作用:向请求中添加指定的请求头,将修改后的请求转发给后端服务

​ 使用场景:

  • 认证信息传递:当后端服务需要认证信息(如 JWT Token)时,可以使用 AddRequestHeader 添加这些信息。

  • 自定义请求头:当后端服务需要某些自定义请求头时,可以使用 AddRequestHeader 添加这些请求头。

  • 动态请求头生成:结合模板变量(如 {segment}),可以根据请求动态生成请求头。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 键:指定要添加的请求头的键
# 值:指定要添加的请求头的值,值可以是静态值或动态模板变量。
filters:
- AddRequestHeader=<键>,<值>

# 这里只给出了局部配置,其他配置保持不变
spring:
cloud:
gateway:
routes:
- id: AddRequestHeader-predicates-route
uri: lb://service
predicates:
- Path=/user/{id}
filters:
- AddRequestHeader=X-user-id,{id}
- AddRequestHeader=X-Version,v1

​ 向请求头中添加键值对’[X-user-id:{id}, X-Version:version ]‘

(8)AddResponseHeader

​ 作用:向响应中添加指定的响应头

​ 使用场景:

- 跨域支持:添加 `Access-Control-Allow-Origin` 等响应头以支持跨域请求
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
filters:
- AddResponseHeader=<键>,<值>

# 这里只给出了局部配置,其他配置保持不变
spring:
cloud:
gateway:
routes:
- id: AddRequestHeader-predicates-route
uri: lb://service
predicates:
- Path=/user/{id}
filters:
- AddResponseHeader=X-user-id,{id}
- AddResponseHeader=X-Version,version

​ 向响应头中添加键值对’[X-user-id:{id}, X-Version:version ]‘

4. 自定义的网关过滤器
(1)创建一个类并实现 GatewayFilterFactory 接口,重写 apply 方法,编写自定义逻辑
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@Slf4j
public class MyGatewayFilter extends AbstractGatewayFilterFactory<MyGatewayFilter.Config> {
@Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
// 打印自定义配置
log.info("Custom Config Value: " + config.getValue());

// 修改请求头(示例)
ServerHttpRequest modifiedRequest = exchange.getRequest().mutate()
.header("X-Route-Filter", "Applied")
.build();

// 继续执行过滤器链
return chain.filter(exchange.mutate().request(modifiedRequest).build());
};
}

public static class Config {
private String value;

public String getValue() {
return value;
}

public void setValue(String value) {
this.value = value;
}
}
}
(2)在配置文件中为特定路由配置该过滤器
1
2
3
4
5
6
7
8
spring:
cloud:
gateway:
routes:
- id: custom_route
uri: http://example.com
filters:
- CustomRouteFilter=CustomValue # 使用自定义过滤器并传递配置

四、处理跨域问题

​ gateway中可以处理跨域问题,在yaml文件中添加配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
spring:
cloud:
gateway:
globalcors:
cors-configurations:
'[/**]':
# 允许跨域的源(网站域名/ip),设置*为全部
allowed-origins: "*"
# 允许跨域的method, 默认为GET和OPTIONS,设置*为全部
allowed-methods: GET, POST, PUT, DELETE
# 允许跨域请求里的head字段,设置*为全部
allowed-headers: "*"
# 请求最大时间
max-age: 3600

五、整合sentinel实现限流及降级

由于篇幅问题,这部分内容我放在别的文章中。

六、实现统一鉴权

关于网关访问时必须要统一验证请求是否有效时,有很多种办法,我这里给出SpringBoot+Spring Security+JWT实现单点登录的解决思路来实现网关统一验证。由于篇幅问题,这部分内容我放在别的文章中。