什么是Spring Cloud Gateway
网关作为流量的入口,常用的功能包括路由转发,权限校验,限流等。
Spring Cloud Gateway 是Spring Cloud官方推出的第二代网关框架,定位于取代 Netflix Zuul。相比 Zuul 来说,Spring Cloud Gateway 提供更优秀的性能,更强大的有功能。
SpringCloud Gateway 是基于 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技术开发的网关。为了提升网关的性能,SpringCloud Gateway 是基于 WebFlux 框架实现的,而 WebFlux 框架底层则使用了高性能的 Reactor 模式通信框架 Netty。
Spring Cloud Gateway 是由 WebFlux 实现的响应式的 API 网关。它不能在传统的 servlet 容器中工作,也不能构建成 war 包。
Spring Cloud Gateway 旨在为微服务架构提供一种简单且有效的 API 路由的管理方式,并基于 Filter 的方式提供网关的基本功能,例如说安全认证、监控、限流等等。
官网文档:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gateway-request-predicates-factories
核心概念
路由(route)
路由是网关中最基础的部分,路由信息包括一个ID、一个目的URI、一组断言工厂、一组Filter组成。如果断言为真,则说明请求的URL和配置的路由匹配。
断言(predicates)
Java8中的断言函数,SpringCloud Gateway中的断言函数类型是Spring5.0框架中的ServerWebExchange。断言函数允许开发者去定义匹配Http request中的任何信息,比如请求头和参数等。
过滤器(Filter)
SpringCloud Gateway中的filter分为Gateway FilIer和Global Filter。Filter可以对请求和响应进行处理。
工作原理
Spring Cloud Gateway 的工作原理跟 Zuul 的差不多,最大的区别就是 Gateway 的 Filter 只有 pre 和 post 两种。
客户端向 Spring Cloud Gateway 发出请求。
如果 Gateway Handler Mapping 找到与请求相匹配的路由,将其发送到 Gateway Web Handler。
Handler 再通过指定的 过滤器链 来将请求发送到我们实际的服务执行业务逻辑,然后返回。
过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前(“pre”)或之后(“post”)执行业务逻辑。
Spring Cloud Gateway快速开始
环境搭建
- 引入依赖
<!-- gateway网关 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- nacos服务注册与发现 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
注意:会和spring-webmvc的依赖冲突,需要排除spring-webmvc
2) 编写yml配置文件
server:
port: 8877
spring:
application:
name: ahs-gateway
cloud:
# nacos注册中心
nacos:
discovery:
server-addr: 127.0.0.1:8848
gateway:
discovery:
locator:
enabled: false #默认为false,设为true开启通过微服务创建路由的功能,即可以通过微服务名访问服务
lower-case-service-id: true
enabled: true # 是否开启网关
routes:
- id: ahs-user-route
uri: lb://ash-user
#uri: http://www.baidu.com
predicates:
- Path=/user/test123
filters:
# ??
# ??????
- StripPrefix=1
路由断言工厂(Route Predicate Factories)配置
官方文档:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gateway-request-predicates-factories
gateway自身提供的路有断言工厂
时间匹配
可以用在限时抢购的一些场景中。
spring:
cloud:
gateway:
routes:
- id: after_route #全局唯一自己随便定义
uri: https://example.org:8080 #目标微服务请求的地址和端口
# 测试url:https://example.org:8080/test
predicates:
# - After=2017-01-20T17:42:47.789-07:00[America/Denver] # 美国的时区
# 在时间之后 当然也有- Before
- After=2017-01-20T17:42:47.789-07:00[Asia/Shanghai] # 亚洲上海市区
请求的时间必须在after配置的之后
before时间断言工厂配置
spring:
cloud:
gateway:
routes:
- id: before_route
uri: https://example.org
predicates:
- Before=2017-01-20T17:42:47.789-07:00[America/Denver]
请求的时间必须在Before配置的之前
betwen断言工厂配置
spring:
cloud:
gateway:
routes:
- id: between_route
uri: https://example.org
predicates:
- Between=2017-01-20T17:42:47.789-07:00[America/Denver], 2017-01-21T17:42:47.789-07:00[America/Denver]
请求的时间必须在betwen配置时间之间
Cookie断言共产匹配
spring:
cloud:
gateway:
routes:
- id: cookie_route
uri: https://example.org
predicates:
- Cookie=chocolate, ch.p # k v 健值对
就是一个键值对!请求的Cookie必须要有一个键值对chocolate:ch.p。满足我就给你访问
Header匹配
spring:
cloud:
gateway:
routes:
- id: header_route
uri: https://example.org
predicates:
# Header匹配 请求中带有请求头名为 x-request-id,其值与 \d+ 正则表达式匹配
- Header=X-Request-Id, \d+
这个路由规则匹配Header中包含X-Request-Id并且值为纯数字的请求。
主机路有断言匹配
spring:
cloud:
gateway:
routes:
- id: host_route
uri: https://example.org
predicates:
- Host=**.somehost.org,**.anotherhost.org
请求http://localhost:8080 -H “Host: www.somehost.org” 可以请求通过
2.2.4 路径匹配
routes:
- id: provider_routh
uri: lb://SPRINGCLOUD-PROVIDER-DEPT #目标微服务名!而且是以负载均衡方式去获取!
predicates: #断言
- Path=/testGateWay01
请求的路中必须包含testGateWay01
Method 路由断言
routes:
- id: provider_routh
uri: lb://SPRINGCLOUD-PROVIDER-DEPT #目标微服务名!而且是以负载均衡方式去获取!
predicates: #断言
- Path=/testGateWay01
- Method=GET,POST
这个路由匹配所有的GET POST请求,并且请求的前缀是testGateWay01。
.Query 查询路由断言
routes:
- id: provider_routh
uri: lb://SPRINGCLOUD-PROVIDER-DEPT #目标微服务名!而且是以负载均衡方式去获取!
predicates: #断言
- Path=/testGateWay01
- Query=green
- Query=red, gree.
1.如果请求中包含green的参数,则匹配成功。
2.如果一个请求中包含参数red并且值匹配·gree.·这个正则,那么路由匹配。比如:green和greet。
2.2.5 自定义路由断言工厂
自定义路由断言工厂需要继承 AbstractRoutePredicateFactory 类,重写 apply 方法的逻辑。在 apply 方法中可以通过 exchange.getRequest() 拿到 ServerHttpRequest 对象,从而可以获取到请求的参数、请求方式、请求头等信息。
注意: 命名需要以 RoutePredicateFactory 结尾
1、 必须spring组件 bean
2、类必须加上RoutePredicateFactory作为结尾
3、必须继承AbstractRoutePredicateFactory
4、必须声明静态内部类 声明属性来接收 配置文件中对应的断言的信息
5、 需要结合shortcutFieldOrder进行绑定
6、通过apply进行逻辑判断 true就是匹配成功 false匹配失败
@Component
@Slf4j
public class CheckAuthRoutePredicateFactory extends AbstractRoutePredicateFactory<CheckAuthRoutePredicateFactory.Config> {
public CheckAuthRoutePredicateFactory() {
super(Config.class);
}
@Override
public Predicate<ServerWebExchange> apply(Config config) {
return new GatewayPredicate() {
@Override
public boolean test(ServerWebExchange serverWebExchange) {
log.info("调用CheckAuthRoutePredicateFactory" + config.getName());
if(config.getName().equals("www")){
return true;
}
return false;
}
};
}
/**
* 快捷配置
* @return
*/
@Override
public List<String> shortcutFieldOrder() {
return Collections.singletonList("name");
}
public static class Config {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}
yml中配置
spring:
cloud:
gateway:
#设置路由:路由id、路由到微服务的uri、断言
routes:
- id: order_route #路由ID,全局唯一
uri: lb://test-service #目标微服务的请求地址和端口
predicates:
# 测试路径 http://localhost:8080/test123/api/change
- Path=/test123 #Path路径匹配
# 自定义CheckAuth断言工厂
- CheckAuth=www
filters:
- StripPrefix=1 # 转发之前去掉1层路径 变成 -> http://localhost:8080/api/change
过滤器工厂( GatewayFilter Factories
GatewayFilter是网关中提供的一种过滤器,可以对进入网关的请求和微服务返回的响应做处理
SpringCloudGateway 内置了很多的过滤器工厂,我们通过一些过滤器工厂可以进行一些业务逻辑处理器,比如添加剔除响应头,添加去除参数等
spring:
cloud:
gateway:
routes: # 网关路由配置
- id: user-service
uri: lb://userservice
predicates:
- Path=/user/**
filters: # 过滤器
- AddRequestHeader=info, AddRequestHeader! # 添加请求头
userservice项目中controller中的请求方法上获取Info的内容
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/{id}")
public User queryByid(@PathVariable("id") Long id,
@RequestHeader(value = "Info",required = false) String info) {
System.out.println(info);
return userService.queryById(id);
}
}
默认过滤器
如果要对所有的路由都生效,则可以将过滤器工厂写到default下。格式如下
spring:
gateway:
routes: # 网关路由配置
- id: user-service # 路由id,自定义,只要唯一即可
# uri: http://127.0.0.1:8081 # 路由的目标地址 http就是固定地址
uri: lb://userservice # 路由的目标地址 lb就是负载均衡,后面跟服务名称
predicates: # 路由断言,也就是判断请求是否符合路由规则的条件
- Path=/user/** # 这个是按照路径匹配,只要以/user/开头就符合要求
#filters: # 过滤器
# - AddRequestHeader=info, AddRequestHeader! # 添加请求头
- id: feign-order-service
uri: lb://feignorderservice
predicates:
- Path=/order/**
filters:
- PrefixPath=/mall-order # 添加前缀 对应微服务需要配置context-path
default-filters: # 默认过滤器,会对所有的路由请求都生效
- AddRequestHeader=Info,'default filers request header!' # 添加请求头
为匹配的路由统一添加前缀
spring:
gateway:
routes: # 网关路由配置
- id: user-service # 路由id,自定义,只要唯一即可
# uri: http://127.0.0.1:8081 # 路由的目标地址 http就是固定地址
uri: lb://userservice # 路由的目标地址 lb就是负载均衡,后面跟服务名称
predicates: # 路由断言,也就是判断请求是否符合路由规则的条件
- Path=/user/** # 这个是按照路径匹配,只要以/user/开头就符合要求
- id: feign-order-service
uri: lb://feignorderservice
predicates:
- Path=/order/**
filters:
- PrefixPath=/api # 添加前缀 对应微服务需要配置context-path
对应为服务中需要票配置
server:
servlet:
context-path: /api
重定向操作
spring:
cloud:
gateway:
#设置路由:路由id、路由到微服务的uri、断言
routes:
- id: user-service #路由ID,全局唯一
uri: lb://userservice #目标微服务的请求地址和端口
#配置过滤器工厂
predicates:
- Path=/order/**
filters:
- RedirectTo=302, https://www.baidu.com/ #重定向到百度
自定义过滤器工厂
继承AbstractNameValueGatewayFilterFactory且我们的自定义名称必须要以GatewayFilterFactory结尾并交给spring管理。
@Component
@Slf4j
public class CheckAuthGatewayFilterFactory extends AbstractNameValueGatewayFilterFactory {
@Override
public GatewayFilter apply(NameValueConfig config) {
return (exchange, chain) -> {
log.info("调用CheckAuthGatewayFilterFactory==="
+ config.getName() + ":" + config.getValue());
return chain.filter(exchange);
};
}
}
配置自定义的过滤器工厂
spring:
cloud:
gateway:
#设置路由:路由id、路由到微服务的uri、断言
routes:
- id: user-service #路由ID,全局唯一
uri: lb://userservice #目标微服务的请求地址和端口
#配置过滤器工厂
predicates:
- Path=/order/**
#配置过滤器工厂
filters:
- CheckAuth=www,女
全局过滤器(Global Filters)配置
GlobalFilter 接口和 GatewayFilter 有一样的接口定义,只不过, GlobalFilter 会作用于所有路由。
官方声明:GlobalFilter的接口定义以及用法在未来的版本可能会发生变化。
LoadBalancerClientFilter
LoadBalancerClientFilter 会查看exchange的属性 ‘ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR 的值(一个URI),如果该值的scheme是 lb,比如:lb://myservice ,它将会使用Spring Cloud的LoadBalancerClient 来将 myservice 解析成实际的host和port,并替换掉 ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR 的内容。
其实就是用来整合负载均衡器Ribbon的
需求:定义全局过滤器,拦截请求,判断请求的参数是否满足下面条件:
参数中是否有authorization,
authorization参数值是否为admin
如果同时满足则放行,否则拦截
自定义全局过滤器
/@Order(-1) //过滤器执行顺序
@Component //spring容器管理
public class AuthorizationFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 1.获取请求参数
ServerHttpRequest request = exchange.getRequest();
MultiValueMap<String, String> params = request.getQueryParams();
// 2.获取参数中的 authorization 参数
String auth = params.getFirst("authorization");
// 3.判断参数值是否等于 admin
if ("admin".equals(auth)) {
// 4.是,放行
return chain.filter(exchange);
}
// 5.否,拦截
// 5.1.设置状态码
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
// 5.2.拦截请求
return exchange.getResponse().setComplete();
}
@Override
public int getOrder() {
return -1;
}
}
注意:Order注解和implements Ordered作用是完全一样的,二选一即可。
过滤器加载流程
org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator#getFilters()方法是先加载defaultFilters,然后再加载某个route的filters,然后合并。
org.springframework.cloud.gateway.handler.FilteringWebHandler#handle()方法会加载全局过滤器,与前面的过滤器合并后根据order排序,组织过滤器链