Spring Cloud Gateway 是 Spring 官方基于 Spring 5.0、Spring Boot 2.0 和 Project Reactor 等技术开发的网关,Spring Cloud Gateway 旨在为微服务架构提供一种简单有效的、统一的 API 路由管理方式。
Spring Cloud Gateway 作为 Spring Cloud 生态系中的网关,其目标是替代 Netflix Zuul,它不仅提供统一的路由方式,并且基于 Filter 链的方式提供了网关基本的功能,例如:安全、监控/埋点和限流等。
Spring Cloud Gateway 依赖 Spring Boot 和 Spring WebFlux,基于 Netty 运行。它不能在传统的 servlet 容器中工作,也不能构建成 war 包。
1、核心
在 Spring Cloud Gateway 中有如下几个核心概念需要我们了解:
(1)Route
Route 是网关的基础元素,由 ID、目标 URI、断言、过滤器组成。当请求到达网关时,由 Gateway Handler Mapping 通过断言进行路由匹配(Mapping),当断言为真时,匹配到路由。
(2)Predicate
Predicate 是 Java 8 中提供的一个函数。输入类型是 Spring Framework ServerWebExchange。它允许开发人员匹配来自 HTTP 的请求,例如请求头或者请求参数。简单来说它就是匹配条件。
(3)Filter
Filter 是 Gateway 中的过滤器,可以在请求发出前后进行一些业务上的处理。
Spring Cloud Gateway 工作原理
客户端向 Spring Cloud Gateway 发出请求,如果请求与网关程序定义的路由匹配,则该请求就会被发送到网关 Web 处理程序,此时处理程序运行特定的请求过滤器链。
过滤器之间用虚线分开的原因是过滤器可能会在发送代理请求的前后执行逻辑。所有 pre 过滤器逻辑先执行,然后执行代理请求;代理请求完成后,执行 post 过滤器逻辑。核心就是路由转发和过滤链
Spring Cloud Gateway与zuul的区别
1、Zuul 1.x是一个基于阻塞I/O的API Gateway
2、Zuul 1.x 基于servlet 2.5使用阻塞架构它不支持任何长连接(如WebSocket)Zuul的设计模式和Nginx较像,每次I/O操作都是从工作中选择一个执行,请求线程呗阻塞到工作线程完成,但是差别是Ngnix用C++实现,Zuul用java实现,而JVM本身会有第一次加载较慢的情况,使得Zuul的性能相当较差。
3、Zuul 2.x理念更先进,想基于Netty非阻塞和支持长连接,但springcloud目前还没有整合。
4、Spring cloud gateway建立在Spring Framework 5、Project Reactor 和 Spring boot 2之上,使用非阻塞API.
5.Spring cloud gateway还支持WebSocket,并且与Spring紧密集成拥有更好的开发体验。
1、工程下创建 springcloud-gateway-9527 模块,导入pom依赖
<!--gateway-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--eureka-client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
2、启动类就按 Spring Boot 的方式即可,添加erueka client注解。代码如下所示。
@SpringBootApplication
@EnableEurekaClient
public class GateWayMain9527 {
public static void main(String[] args) { SpringApplication.run(GateWayMain9527.class,args);
}
}
3.配置 yml 文件,为了实现简单的路由网关的转发
server:
port: 9527
spring:
application:
name: cloud-gateway
cloud:
gateway:
routes:
- id: user_routh #路由的ID,没有固定规则但要求唯一,建议配合服务名
uri: http://localhost:8001 #匹配后提供服务的路由地址
predicates:
- Path=/user/get/** # 断言,路径相匹配的进行路由
- id: user_routh2 #路由的ID,没有固定规则但要求唯一,建议配合服务名
uri: http://localhost:8001 #匹配后提供服务的路由地址
predicates:
- Path=/user/list/** # 断言,路径相匹配的进行路由
eureka:
instance:
hostname: cloud-gateway-service
client:
fetch-registry: true #向注册中心注册
register-with-eureka: true
service-url:
defaultZone: http://localhost:7001/eureka/
当你访问 http://localhost:9527/user/get/1 的时候就会转发到 http://localhost:8001/user/get/ 1
如果请求路径为/user/list/xxx,则此路由将匹配。也可以使用正则,例如 /user/list/ ** 来匹配 /user/list开头的多级 URI。后面加上两个 * 号表示多级匹配
http://localhost:8001/user/get/ 1是你服务提供者的地址,你须得在提供者有这样的接口,然后通过gateway实现的路由转发,这样就不会暴露你的真实的接口信息
如,我的提供者接口如下,才进行如上的配置
整合 Eureka 动态路由
从以上的yml配置来看,这样配置我们只能访问某个微服务的地址如 http://localhost:8001 这个微服务地址,这样显然不合理。
现在我们结合eureka实现通过微服务名实现动态路由转发
其他基本不变,只需要更改yml的配置
cloud:
gateway:
discovery:
locator:
enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由
lower-case-service-id: true #开启小写,服务的名称必须小写,不开启默认大写
routes:
- id: user_routh #路由的ID,没有固定规则但要求唯一,建议配合服务名
uri: lb://springcloud-provider-user #匹配后提供服务的微服务名称
predicates:
- Path=/user/get/** # 断言,路径相匹配的进行路由
注意:uri 以lb://开头(lb 代表从注册中心获取服务),后面接的就是你需要转发到的服务名称,这个服务名称必须跟 Eureka 中的对应,否则会找不到服务 ,错误代码如下:
org.springframework.cloud.gateway.support.NotFoundException: Unable to find instance for springcloud-provider-user
开启之后我们就可以通过地址去访问服务了,格式如下:
http://网关地址/服务名称(小写)/**
http://localhost:9527/springcloud-provider-user/user/get/1
这样我们就可以通过微服务名,访问多个微服务的地址了
Spring Cloud Gateway 内置了许多路由断言工厂,可以通过配置的方式直接使用,也可以组合使用多个路由断言工厂。
当我们启动dateway网关微服务,在console中可以看到这些断言工厂。可以理解为:当满足这种条件后才会被转发,如果是多个,那就是都满足的情况下被转发。
接下来为大家介绍几个常用的路由断言工厂类。
(1)Path 路由断言工厂
path 路由断言工厂接收一个参数,根据 Path 定义好的规则来判断访问的 URI 是否匹配。
spring:
cloud:
gateway:
routes:
- id: user_routh
uri: http://localhost:8001
predicates:
- Path=/user/get/** # 断言,路径相匹配的进行路由
如果请求路径为/user/list/xxx,则此路由将匹配。也可以使用正则,例如 /user/list/ ** 来匹配 /user/list开头的多级 URI。后面加上两个 * 号表示多级匹配
(2) Before Between 和 After 路由断言工厂
Before Between 和 After 路由断言工厂接收一个参数,分别代表是在什么匹配之前,之间,之后匹配
spring:
cloud:
gateway:
routes:
- id: user_routh
uri: http://localhost:8001
predicates:
- Path=/user/get/** # 断言,路径相匹配的进行路由
- After=2020-12-07T22:21:04.053+08:00[Asia/Shanghai] #在2020-12-07 22:21:01之后进行路由
#- Before =2020-12-07T22:21:04.053+08:00[Asia/Shanghai] #在这个时间之前
#- Between=2020-12-07T22:21:04.053+08:00[Asia/Shanghai],2020-12-20T22:21:04.053+08:00[Asia/Shanghai]#在这个时间之减
时间必须对应,官方给的时间是美国时间,如果你想获取你当地的时间可以通过这个方法获取
import java.time.ZonedDateTime;
ZonedDateTime zonedDateTime=ZonedDateTime.now();
System.out.println(zonedDateTime);
(3)Cookie 路由断言工厂
Cookie 路由断言工厂接收两个参数,一个是cookie name,一个是正则表达式,路由规则会通过获取对应的cookie name值和正则表达式去匹配,如果匹配上就会执行路由,反则不执行。
spring:
cloud:
gateway:
routes:
- id: user_routh
uri: http://localhost:8001
predicates:
- Path=/user/get/** # 断言,路径相匹配的进行路由
- Cookie=username,zzyy
在访问http://localhost:9527/user/get/1 时必须带上 指定的cookie 如http://localhost:9527/user/get/1 --cookie “username=zzyy”
(4)Header 路由断言工厂
Header 路由断言工厂接收两个参数,分别是请求头名称和正则表达式。
spring:
cloud:
gateway:
routes:
- id: user_routh
uri: http://localhost:8001
predicates:
- Header=X-Request-Id, \d+
如果请求中带有请求头名为 x-request-id,其值与 \d+ 正则表达式匹配(值为一个或多个数字),则此路由匹配。
(5)Host 路由断言工厂
Spring Cloud Gateway可以根据Host主机名进行匹配转发,如果我们的接口只允许**.yuqiyu.com域名进行访问
spring:
cloud:
gateway:
routes:
- id: user_routh
uri: http://localhost:8001
predicates:
- Host=**.yuqiyu.com
(6)Query 路由断言工厂
Query 路由断言工厂接收两个参数,一个必需的参数和一个可选的正则表达式。
spring:
cloud:
gateway:
routes:
- id: user_routh
uri: http://localhost:8001
predicates:
- Query=foo, ba
# - Query=username,\d+ #如要带参数名username并且值还要是整数才能路由
如果请求包含一个值与 ba 匹配的 foo 查询参数,则此路由将匹配。bar 和 baz 也会匹配,因为第二个参数是正则表达式。
例如:- Query=username,\d+ 代表要带参数名username并且值还要是整数才能路由 \d+ 代表是整数
(7)Method 路由断言工厂
Method 路由断言工厂接收一个参数,即要匹配的 HTTP 方法。如 Post,Get
spring:
cloud:
gateway:
routes:
- id: user_routh
uri: http://localhost:8001
predicates:
- Method=GET
GatewayFilter Factory 是 Spring Cloud Gateway 中提供的过滤器工厂。Spring Cloud Gateway 的路由过滤器允许以某种方式修改传入的 HTTP 请求或输出的 HTTP 响应,只作用于特定的路由。
Spring Cloud Gateway 中内置了很多过滤器工厂,直接采用配置的方式使用即可,同时也支持自定义 GatewayFilter Factory 来实现更复杂的业务需求。
官方有两种Filter:一种是 GatewayFilter Factories 和 Global Filters
接下来为大家介绍几个常用的过滤器工厂类。
1. AddRequestHeader 过滤器工厂
通过名称我们可以快速明白这个过滤器工厂的作用是添加请求头。
符合规则匹配成功的请求,将添加 X-Request-Foo:bar 请求头,将其传递到后端服务中,后方服务可以直接获取请求头信息。代码如下所示。
spring:
cloud:
gateway:
routes:
- id: add_request_header_route
uri: http://localhost:8001
filters:
- AddRequestHeader=X-Request-Foo, Bar
2. RemoveRequestHeader 过滤器工厂
RemoveRequestHeader 是移除请求头的过滤器工厂,可以在请求转发到后端服务之前进行 Header 的移除操作。
spring:
cloud:
gateway:
routes:
- id: add_request_header_route
uri: http://localhost:8001
filters:
- RemoveRequestHeader=X-Request-Foo
3. SetStatus 过滤器工厂
SetStatus 过滤器工厂接收单个状态,用于设置 Http 请求的响应码。它必须是有效的 Spring Httpstatus(org.springframework.http.HttpStatus)。它可以是整数值 404 或枚举类型 NOT_FOUND。
spring:
cloud:
gateway:
routes:
- id: add_request_header_route
uri: http://localhost:8001
filters:
- SetStatus=401
4. RedirectTo过滤器工厂
RedirectTo 过滤器工厂用于重定向操作,比如我们需要重定向到百度。
spring:
cloud:
gateway:
routes:
- id: add_request_header_route
uri: http://localhost:8001
filters:
- RedirectTo=302, http://baidu.com
官方文档给的过滤器工厂有31种,有兴趣的可以一一去了解,这里就不一一介绍了
文档链接: https://docs.spring.io/spring-cloud-gateway/docs/2.2.5.RELEASE/reference/html/#gatewayfilter-factories
自定义Spring Cloud Gateway全局过滤器(GlobalFilter)
全局过滤器作用于所有的路由,不需要单独配置,我们可以用它来实现很多统一化处理的业务需求,比如权限认证、IP 访问限制等。
Spring Cloud Gateway 自带的 GlobalFilter 实现类有很多,如下图
有转发、路由、负载等相关的 GlobalFilter,感兴趣的朋友可以去看下源码自行了解。我们如何通过定义 GlobalFilter 来实现我们的业务逻辑?
链接地址:https://docs.spring.io/spring-cloud-gateway/docs/2.2.5.RELEASE/reference/html/#global-filters
接下来我们进行一些简单的配置去实现全局过滤配置
首先单独定义只需要实现 GlobalFilter、Ordered 两个接口就可以了,具体代码如下所示。
/*
* gateway全局过滤器
* */
@Component //必须要加否则扫描不到
@Slf4j
public class MyLogGatewayFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("******** come in MyLogGatewayFilter"+new Date());
//请求必须带着uname才能访问
String uname=exchange.getRequest().getQueryParams().getFirst("uname");
if (uname==null){
log.info("*********用户名为null,非法用户*****");
exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0;
}
}
过滤的使用虽然比较简单,但作用很大,可以处理很多需求,上面讲的 IP 认证拦截只是冰山一角,更多的功能需要我们自己基于过滤器去实现。
如果你感兴趣可以继续学习:
springcloud(七) --config配置中心使用和搭建