一、Spring Cloud Gateway
1.1、概述
Spring Cloud全家桶中有个很重要的组件就是网关,在1.x版本中采用的是Zuul网关,但是在2.x版本中,由于Zuul的升级一直跳票,Spring Cloud最后自己研发了一个网关替代Zuul,即:Spring Cloud Gateway。简单点讲Gateway就是原Zuul1.x版的替代品。
1.2、 Zuul1.x官网
https://github.com/Netflix/zuul/wiki
1.3、Gateway官网
https://spring.io/projects/spring-cloud-gateway/#overview
1.4、Gateway源码架构
1.5、已经有了Zuul为什么又出现了Gateway
(一)neflix不太靠谱,zuul2.0一直跳票,迟迟不发布;
一方面因为Zuul1.0已经进入了维护阶段,而Gateway是Spring Cloud团队研发的,是亲儿子产品,值得信赖。而且很多功能Zuul都没有Gateway使用起来简单便捷,Gateway是基于异步非阻塞模型上进行开发的,性能方面不需要担心。虽然Netflix早就发布了最新的 Zuul 2.x,但 Spring Cloud 貌似没有整合计划。而且Netflix相关组件都宣布进入维护期;不知前景如何?
多方面综合考虑Gateway是很理想的网关选择。(二)Spring Cloud Gateway具有如下特性:
(1)基于Spring Framework 6, Project Reactor 和 Spring Boot 3.0 进行构建;
(2)动态路由:能够匹配任何请求属性;
(3)可以对路由指定 Predicate(断言)和 Filter(过滤器);
(4)集成Hystrix的断路器功能;
(5)集成 Spring Cloud 服务发现功能;
(6)易于编写的 Predicate(断言)和 Filter(过滤器);
(7)请求限流功能;
(8)支持路径重写。(三)Spring Cloud Gateway 与 Zuul的区别:
在SpringCloud Finchley 正式版之前,Spring Cloud 推荐的网关是 Netflix 提供的Zuul:
(1)Zuul 1.x,是一个基于阻塞 I/ O 的 API Gateway;
(2)Zuul 1.x 基于Servlet 2. 5使用阻塞架构,它不支持任何长连接(如 WebSocket) ,Zuul 的设计模式和Nginx较像,每次 I/ O 操作都是从工作线程中选择一个执行,请求线程被阻塞到工作线程完成,但是差别是Nginx 用C++ 实现,Zuul 用 Java 实现,而 JVM 本身会有第一次加载较慢的情况,使得Zuul 的性能相对较差;
(3)Zuul 2.x理念更先进,想基于Netty非阻塞和支持长连接,但Spring Cloud目前还没有整合。 Zuul 2.x的性能较 Zuul 1.x 有较大提升。在性能方面,根据官方提供的基准测试, Spring Cloud Gateway 的 RPS(每秒请求数)是Zuul 的 1. 6 倍。
(4)Spring Cloud Gateway 建立 在 Spring Framework 5、 Project Reactor 和 Spring Boot 2 之上, 使用非阻塞 API;
1.6、常用功能
反向代理、鉴权、流量控制、熔断、日志监控...
1.7、微服务架构中网关的位置
1.8、三大核心概念
1.8.1、路由(Route)
路由是构建网关的基本模块,它由ID,目标URI,一系列的断言和过滤器组成,如果断言为true则匹配该路由。
1.8.2、断言(Predicate)
断言参考的是Java8的java.util.function.Predicate,开发人员可以匹配HTTP请求中的所有内容(例如请求头或请求参数),如果请求与断言相匹配则进行路由。
1.8.3、过滤器(Filter)
指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前或者之后对请求进行修改。
1.9、工作流程
1.9.1、核心逻辑
路由转发 + 执行过滤器链
1.9.2、工作流程
官网:How It Works :: Spring Cloud Gateway
客户端向 Spring Cloud Gateway 发出请求。然后在 Gateway Handler Mapping 中找到与请求相匹配的路由,将其发送到 Gateway Web Handler。Handler 再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前(“pre”)或之后(“post”)执行业务逻辑。Filter在“pre”类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等,在“post”类型的过滤器中可以做响应内容、响应头的修改,日志的输出,流量监控等有着非常重要的作用。
1.10、断言工厂配置(Route Predicate Factories)
1.10.1、概述
当请求Gateway的时候,使用断言对请求进行匹配,如果匹配成功就路由转发,如果匹配失败就返回404。Spring Cloud中的Gateway分为两大类,即:内置的和自定义的,下面详细介绍!
官网:Route Predicate Factories :: Spring Cloud Gateway
1.10.2、(内置)基于Datetime类型的断言工厂
Datetime类型的断言工厂用于断言时间,断言为真则路由,否则404,主要有3个,如下:
注意事项:这里的时间是带时区的时间,可以通过ZonedDateTime.now()获取
案例:
1.10.3、(内置)基于远程地址的断言工厂
案例:
1.10.4、(内置)基于Cookie的断言工厂
案例:
1.10.5、(内置)基于Header的断言工厂
案例:
1.10.6、(内置)基于Host的断言工厂
案例:
1.10.7、(内置)基于Method请求方法的断言工厂
案例:
1.10.8、(内置)基于Path请求路径的断言工厂
案例:
1.10.9、(内置)基于Query请求参数的断言工厂
案例:
1.10.10、(内置)基于路由权重的断言工厂
案例:
1.10.11、(自定义)基于权限的断言工厂
概述:
上述内置的断言工厂已经能够满足我们日常工作的绝大部分场景,但是当内置的断言工厂不满足我们的需求时,那么我们可以通过自定义断言工厂的方式进行扩展,具体过程如下:
自定义断言工厂的步骤:
(1)创建一个类并纳入Spring管理,添加@Component注解;
(2)类必须以RoutePredicateFactory作为结尾,约定大于配置!
(3)类必须继承自AbstractRoutePredicateFactory;
(4)必须声明静态内部类、声明属性来接收配置文件中对应的断言信息;
(5)结合shortcutFieldOrder进行绑定;
(6)通过apply方法进行逻辑判断,如果为true则匹配成功,否则匹配失败;
/**
* @Author : 一叶浮萍归大海
* @Date: 2024/1/4 9:38
* @Description: 自定义断言工厂
* 功能:校验用户的权限,如果用户是已授权用户则放行,否则失败!
*/
@Component
public class AuthRoutePredicateFactory extends AbstractRoutePredicateFactory<AuthRoutePredicateFactory.Config> {
public static final String AUTHED_USER = "admin";
public AuthRoutePredicateFactory() {
super(AuthRoutePredicateFactory.Config.class);
}
public List<String> shortcutFieldOrder() {
return Collections.singletonList("name");
}
public Predicate<ServerWebExchange> apply(final AuthRoutePredicateFactory.Config config) {
return new GatewayPredicate() {
public boolean test(ServerWebExchange serverWebExchange) {
if (AUTHED_USER.equals(config.getName())) {
return true;
}
return false;
}
};
}
@Data
@AllArgsConstructor
@NoArgsConstructor
@Validated
public static class Config {
private String name;
}
}
1.11、过滤器工厂配置(局部)
1.11.1、内置
https://docs.spring.io/spring-cloud-gateway/reference/spring-cloud-gateway/gatewayfilter-factories.html
1.11.2、自定义
概述:
1.7.1官网提供的内置过滤器已经能够满足我们日常工作的绝大部分场景,但是当内置的过滤器不满足我们的需求时,那么我们可以通过自定义过滤器的方式进行扩展,具体过程如下:
自定义过滤器的步骤:
(1)创建一个类并纳入Spring管理,添加@Component注解;
(2)类必须以GatewayFilterFactory作为结尾,约定大于配置!
(3)类必须继承自AbstractGatewayFilterFactory;
(4)必须声明静态内部类、声明属性来接收配置文件中对应的断言信息;
(5)结合shortcutFieldOrder进行绑定;
(6)通过apply方法进行逻辑判断,如果为true则匹配成功,否则匹配失败;
/**
* @Author : 一叶浮萍归大海
* @Date: 2024/1/4 10:28
* @Description: 自定义过滤器
*/
@Component
public class AuthGatewayFilterFactory extends AbstractGatewayFilterFactory<AuthGatewayFilterFactory.Config> {
public AuthGatewayFilterFactory() {
super(AuthGatewayFilterFactory.Config.class);
}
public List<String> shortcutFieldOrder() {
return Arrays.asList("value");
}
public GatewayFilter apply(final AuthGatewayFilterFactory.Config config) {
return new GatewayFilter(){
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
/**
* (1)name参数为空:成功
* (2)name参数不为空:获取name参数,值不等于value就失败,否则成功
*/
String name = exchange.getRequest().getQueryParams().getFirst("name");
if (StringUtils.isNotBlank(name)) {
if (name.equals(config.getValue())) {
return chain.filter(exchange);
} else {
exchange.getResponse().setStatusCode(HttpStatus.NOT_FOUND);
return exchange.getResponse().setComplete();
}
}
return chain.filter(exchange);
}
};
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class Config {
private String value;
}
}
1.12、全局过滤器(Global Filter)配置
1.12.1、概览
1.12.2、局部过滤器 vs 全局过滤器
局部过滤器:针对某个路由,需要在路由中进行配置
全局过滤器:针对所有路由请求,一旦定义就会投入使用,无需配置
GlobalFilter和GatewayFilter有着一样的定义,只不过前者会作用于所有的路由;
1.13、统一处理跨域
1.13.1、官网(配置方式)
https://docs.spring.io/spring-cloud-gateway/reference/spring-cloud-gateway/cors-configuration.html
1.13.2、代码方式
/**
* @Author : 一叶浮萍归大海
* @Date: 2024/1/4 12:53
* @Description:
*/
@Configuration
public class MyCorsConfig {
@Bean
public CorsWebFilter corsWebFilter() {
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.addAllowedMethod("*");
corsConfiguration.addAllowedOrigin("*");
corsConfiguration.addAllowedHeader("*");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**",corsConfiguration);
return new CorsWebFilter(source);
}
}