概述
Gateway
简介
Spring Cloud Gateway基于Spring 5.0、SpringBoot 2.0和Project Reactor等技术开发 旨在为微服务架构提供一种简单有效的、统一的API路由管理方式,并为微服务架构提供安全、监控、指标和弹性等功能 其目标是替代Zuul
特点
易于编写谓词和过滤器,其Predicates和Filters可作用于特定路由 支持路径重写 支持动态路由 集成了Spring Cloud DiscoveryClient
底层原理
简介
Spring Cloud Gateway 用"Netty + Webflux"实现,不要加入Web依赖,否则会报错,它需要加入Webflux依赖
Netty
Netty 是一个基于NIO的客户、服务器端的编程框架。 提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序
WebFlux
1.Webflux模式替换了旧的Servlet线程模型。用少量的线程处理request和response io操作,这些线程称为Loop线程, 而业务交给响应式编程框架处理,响应式编程是非常灵活的,用户可以将业务中阻塞的操作提交到响应式框架的work线程中执行, 而不阻塞的操作依然可以在Loop线程中进行处理,大大提高了Loop线程的利用率 2.Webflux虽然可以兼容多个底层的通信框架,但是一般情况下,底层使用的还是Netty,毕竟,Netty是目前业界认可的最高性能的通信框架。 而Webflux的Loop线程,正好就是著名的Reactor模式IO处理模型的Reactor线程,如果使用的是高性能的通信框架Netty
三大核心概念
路由
路由是 Gateway 的⼀个基本单元 这是网关的基本构建块,它由一个ID,一个目标URI,一组断言和一组过滤器定义,如果断言为真,则路由匹配
断言
也称谓词,实际上是路由的判断规则,一个路由中可以添加多个谓词的组合
输入类型是一个ServerWebExchange。我们可以使用它来匹配来自HTTP请求的任何内容,例如headers或参数
过滤
可以在请求被路由前或者之后对请求进行修改
Gateway 组件使用了⼀种 FilterChain的模式对请求进行处理,每⼀个服务请求(Request)在发送到目标标服务之前都要被⼀串FilterChain处理。
同理,在 Gateway接收服务响应的过程中也会被 FilterChain 处理⼀把
搭建【cloud-gateway9527】
依赖
java
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
application.yml
java
server:
port: 9527
spring:
application:
name: cloud-gateway9527
cloud:
gateway:
discovery:
locator:
enabled: false #不开启服务注册和发现的功能
lower-case-service-id: true #请求路径上的服务名称转换为小写
#路由配置
routes:
#cloud-eureka-pro接口路由
- id: cloud-eureka-pro
uri: lb://cloud-eureka-pro
predicates:
- Path=/eureka/pro/**
#跨域配置
globalcors:
# 解决options请求被拦截问题
add-to-simple-url-handler-mapping: true
cors-configurations:
# 拦截的请求
'[/**]':
# 允许跨域的请求
#allowedOrigins: "*" # spring boot2.4以前的配置
allowedOriginPatterns: "*" # spring boot2.4以后的配置
# 允许请求中携带的头信息
allowedHeaders: "*"
# 运行跨域的请求方式
allowedMethods: "*"
# 是否允许携带cookie
allowCredentials: true
# 跨域检测的有效期,单位s
maxAge: 36000
logging:
pattern:
console: '%d{MM/dd HH:mm:ss.SSS} %clr(%-5level) --- [%-15thread] %cyan(%-50logger{50}):%msg%n'
启动类
java
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@Slf4j
@SpringBootApplication
public class Main {
public static void main(String[] args) {
SpringApplication.run(Main.class, args);
log.info("********** Gateway网关 服务启动成功 *********");
}
}
相关接口
1.搭建eureka客户端【test-provider8001】
路由
配置路由
配置文件
java
spring:
cloud:
#路由配置
routes:
#cloud-eureka-pro接口路由
- id: cloud-eureka-pro
uri: lb://cloud-eureka-pro
predicates:
- Path=/eureka/pro/**
配置类
java
/**
* JAVA API构建路由规则
*/
@Configuration
public class GatewayConfig {
@Bean//http://localhost:9527/guonei
public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder){
RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
//设置路由 路由ID(随意取,不重复即可)|路径匹配规则|跳转的路径
routes.route("path_rote",r -> r.path("/guonei").uri("https://www.baidu.com/guonei")).build();
return routes.build();
}
}
uri的写法
直接写法
java
- id: route_example
uri: http://example.com
predicates:
- Path=/api/example/**
表示将 `/api/example/**` 的请求路由到 `http://example.com`
转发片段写法
java- id: route_example uri: forward:/internal-service predicates: - Path=/api/example/**
表示将/api/example/**的请求转发到网关内部的/internal-service服务
重定向写法
java- id: route_example uri: redirect:http://new-url.com predicates: - Path=/old-url/**
表示将/old-url/**的请求重定向到http://new-url.com
使用 lb:// 前缀实现负载均衡
java- id: route_example uri: lb://service-name predicates: - Path=/api/example/**
表示将 /api/example/**的请求通过负载均衡策略转发到名为service-name的服务实例
使用 DiscoveryClient 做动态路由
java- id: route_example uri: lb://service-name predicates: - Path=/api/example/** filters: - RewritePath=/api/example/(?<path>.*), /$\{path}
表示将 `/api/example/**` 的请求通过DiscoveryClient进行动态路由,并使用RewritePath过滤器进行路径重写
动态路由
简介
默认情况下Gateway会根据注册中心的服务列表,以注册中心上微服务名为路径创建动态路由进行转发,从而实现动态路由的功能
添加Eureka依赖并修改配置文件
我们需要把网关注册到eureka上,然后才能找到注册中心的服务列表
实现思路
搭建两个生产者,但是他们的服务名是一致的,正在配置网关的时候uri写服务名,此时会轮询去调用这两个生产者服务
配置文件示例
java
#cloud-eureka-pro/cloud-eureka-pro81【动态路由-测试负载均衡】
- id: cloud-eureka-pro-dotailuyou
uri: lb://cloud-eureka-pro
predicates:
- Path=/eureka/pro/port
断言
时间方面
注意
需要用UTC时间格式的时间参数
UTC时间格式的时间参数时间生成方法
java
public static void main(String[] args) {
ZonedDateTime now = ZonedDateTime.now();
System.out.println(now);
}
After
在该日期时间之后发生的请求都将被匹配
java
predicates:
- Path=/payment/**
- After=2030-02-15T14:54:23.317+08:00[Asia/Shanghai]
Before
在该日期时间之前发生的请求都将被匹配
java
predicates:
- Path=/payment/**
- Before=2030-02-15T14:54:23.317+08:00[Asia/Shanghai]
Between
在datetime1和datetime2之间的请求将被匹配
java
predicates:
- Path=/payment/**
- Between=2030-02-15T14:54:23.317+08:00[Asia/Shanghai],2030-02-15T14:54:23.317+08:00[Asia/Shanghai]
请求方面
域名
根据域名断言,是这个域名则通过,不是则不匹配 Host文件新增配置 127.0.0.1 itbaizhan
javapredicates: - Path=/payment/** - Host=itbaizhan
请求方法
这个断言是专门验证HTTP Method的
javapredicates: - Path=/payment/** - Method=GET
请求参数
会从ServerHttpRequest中的Parameters列表中查询指定的属性
javapredicates: - Path=/payment/** - Query=username,\d+
Cookie
顾名思义,Cookie验证的是Cookie中保存的信息,Cookie断言和上面介绍的两种断言使用方式大同小异, 唯一的不同是它必须连同属性值一同验证,不能单独只验证属性是否存在
javapredicates: - Path=/payment/** - Cookie=username,zzyy
请求头
这个断言会检查Header中是否包含了响应的属性,通常可以用来验证请求是否携带了访问令牌
javapredicates: - Path=/payment/** #请求头要有X-Request-Id属性并且值为整数的正则表达式 - Header=X-Request-Id, \d+
网关过滤器
网关过滤器【内置】
简介
应用在单个路由或者一个分组的路由上
示例:SetStatus过滤器
filters: - SetStatus=250 # 修改原始响应的状态码
内置的网关过滤器
AddRequestHeader 为原始请求添加Header Header的名称及值 AddRequestParameter 为原始请求添加请求参数 参数名称及值 AddResponseHeader 为原始响应添加Header Header的名称及值 DedupeResponseHeader 剔除响应头中重复的值 需要去重的Header名称及去重策略 Hystrix 为路由引入Hystrix的断路器保护 HystrixCommand的名称 FallbackHeaders 为fallbackUri的请求头中添加具体的异常信息 Header的名称 PrefixPath 为原始请求路径添加前缀 前缀路径 PreserveHostHeader 为请求添加一个preserveHostHeader=true的属性,路由过滤器会检查该属性以决定是否要发送原始的Host 无 RequestRateLimiter 用于对请求限流,限流算法为令牌桶 keyResolver、rateLimiter、statusCode、denyEmptyKey、emptyKeyStatus RedirectTo 将原始请求重定向到指定的URL http状态码及重定向的url RemoveHopByHopHeadersFilter 为原始请求删除IETF组织规定的一系列Header 默认就会启用,可以通过配置指定仅删除哪些Header RemoveRequestHeader 为原始请求删除某个Header Header名称 RemoveResponseHeader 为原始响应删除某个Header Header名称 RewritePath 重写原始的请求路径 原始路径正则表达式以及重写后路径的正则表达式 RewriteResponseHeader 重写原始响应中的某个Header Header名称,值的正则表达式,重写后的值 SaveSession 在转发请求之前,强制执行WebSession::save操作 无 secureHeaders 为原始响应添加一系列起安全作用的响应头 无,支持修改这些安全响应头的值 SetPath 修改原始的请求路径 修改后的路径 SetResponseHeader 修改原始响应中某个Header的值 Header名称,修改后的值 SetStatus 修改原始响应的状态码 HTTP 状态码,可以是数字,也可以是字符串 StripPrefix 用于截断原始请求的路径 使用数字表示要截断的路径的数量 Retry 针对不同的响应进行重试 retries、statuses、methods、series RequestSize 设置允许接收最大请求包的大小。如果请求包大小超过设置的值,则返回 413 Payload Too Large 请求包大小,单位为字节,默认值为5M ModifyRequestBody 在转发请求之前修改原始请求体内容 修改后的请求体内容 ModifyResponseBody 修改原始响应体的内容 修改后的响应体内容 Default 为所有路由添加过滤器 过滤器工厂名称及值
网关过滤器【自定义】
需求
通过过滤器,配置是否在控制台输出日志信息,以及是否记录日
实现步骤
1.类名必须叫做XxxGatewayFilterFactory,注入到Spring容器后使用时的名称就叫做Xxx。 2.创建一个静态内部类Config, 里面的属性为配置文件中配置的参数, - 过滤器名称=参数1,参数2… 3.类必须继承 AbstractGatewayFilterFactory,让父类帮实现配置参数的处理。 4.重写shortcutFieldOrder()方法,返回List参数列表为Config中属性集合 5.无参构造方法中super(Config.class) 6.编写过滤逻辑 public GatewayFilter apply(Config config)
自定义局部过滤器
java
@Component
public class LogGatewayFilterFactory extends AbstractGatewayFilterFactory<LogGatewayFilterFactory.Config> {
public LogGatewayFilterFactory(){
super(Config.class);
}
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList("consoleLog");
}
@Override
public GatewayFilter apply(Config config) {
return ((exchange, chain) -> {
if (config.consoleLog) {
System.out.println("console日志已开启...");
}
return chain.filter(exchange);
});
}
//过滤器使用的配置内容,如 - Log=true中的true会传到这里
@Data
public static class Config{private boolean consoleLog;}
}
配置使用
java
filters:
# 控制日志是否开启
- Log=true
全局过滤器
全局过滤器【内置】
简介
应用在所有的路由上
内置的全局过滤器
路由过滤器(Forward) 路由过滤器(LoadBalancerClient) Netty路由过滤器 Netty写响应过滤器(Netty Write Response F) RouteToRequestUrl 过滤器 路由过滤器 (Websocket Routing Filter) 网关指标过滤器(Gateway Metrics Filter) 组合式全局过滤器和网关过滤器排序(Combined Global Filter and GatewayFilter Ordering) 路由(Marking An Exchange As Routed)
全局过滤器【自定义】
需求
对于验证用户是否已经登录及鉴权的过程,可以在网关统一校验 自定义一个GlobalFIlter,去校验所有请求的请求参数中是否包含“token”,如果不包含请求参数“token”则不转发路由,否则执行正常逻辑
自定义全局过滤器
java
package com.itbaizhan.filter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
* 自定义全局过滤器,需要实现GlobalFilter和Ordered接口
*/
@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String token = exchange.getRequest().getQueryParams().getFirst("token");
if (StringUtils.isEmpty(token)) {
System.out.println("鉴权失败。确少token参数。");
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
if (!"jack".equals(token)) {
System.out.println("token无效...");
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
// 继续执行filter链
return chain.filter(exchange);
}
/**
* 顺序,数值越小,优先级越高
* @return
*/
@Override
public int getOrder() {
return 0;
}
}