GateWay
API网关,就是指系统的统一入口,它封装了应用程序的内部结构,为客户端提供统一服务,一些与业务本身功能无关的公共逻辑可以在这里实现,诸如认证、鉴权、监控、路由转发等等。
官方文档地址:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/
API网关可体统的功能举例:
- 关注稳定与安全
- 全局性流控
- 日志统计
- 防止SQL注入
- 防止Web攻击
- 屏蔽工具扫描
- 黑白IP名单
- 证书/加解密处理
- 提供更好的服务
- 服务级别流控
- 服务降级与熔断
- 路由与负载均衡、灰度策略
- 服务过滤、聚合与发现
- 权限验证与用户级别策略
- 业务规则与参数校验
- 多级缓存策略
Spring Cloud Gateway 是Spring Cloud官方推出的第二代网关框架,定位于取代 Netflix Zuul1.0。相比 Zuul 来说,Spring Cloud Gateway 提供更优秀的性能,更强大的有功能。
Spring Cloud Gateway 是由 WebFlux + Netty + Reactor 实现的响应式的 API 网关。它不能在传统的 servlet 容器中工作,也不能构建成 war 包。
Spring Cloud Gateway 旨在为微服务架构提供一种简单且有效的 API 路由的管理方式,并基于 Filter 的方式提供网关的基本功能,例如 说安全认证、监控、限流等等。
功能特征
- 基于Spring Framework 5, Project Reactor 和 Spring Boot 2.0 进行构建
- 动态路由:能够匹配任何请求属性
- 支持路径重写
- 集成 Spring Cloud 服务发现功能(Nacos、Eruka)
- 可集成流控降级功能(Sentinel、Hystrix)
- 可以对路由指定易于编写的 Predicate(断言)和 Filter(过滤器)
核心概念
- 路由(route) 路由是网关中最基础的部分,路由信息包括一个ID、一个目的URI、一组断言工厂、一组Filter组成。如果断言为真,则说明请求的URL和 配置的路由匹配。
- 断言(predicates) Java8中的断言函数,SpringCloud Gateway中的断言函数类型是Spring5.0框架中的ServerWebExchange。断言函数允许开发者去定义匹配Http request中的任何信息,比如请求头和参数等。
- 过滤器(Filter) SpringCloud Gateway中的filter分为Gateway FilIer和Global Filter。Filter可以对请求和响应进行处理。
工作原理
执行流程如下:
- Gateway Client向Gateway Server发送请求 。
- 请求首先会被HttpWebHandlerAdapter进行提取组装成网关上下文 。
- 然后网关的上下文会传递到DispatcherHandler,它负责将请求分发给RoutePredicateHandlerMapping 。
- RoutePredicateHandlerMapping负责路由查找,并根据路由断言判断路由是否可用 。
- 如果过断言成功,由FilteringWebHandler创建过滤器链并调用 。
- 请求会一次经过PreFilter–微服务–PostFilter的方法,最终返回响应 。
GateWay初体验
-
构建一个SpringBoot项目
-
导入依赖
#父工程中已经引入的 spring cloud 其他的依赖 <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </exclusion> </exclusions> </dependency>
-
添加配置文件
server: port: 8088 spring: application: name: api-gateway cloud: # gateway的配置 gateway: # 路由规则 routes: - id: order_route # 路由的唯一标识,路由到order uri: http://localhost:8010 # 需要转发的地址 # 断言规则 用于路由规则的匹配 predicates: - Path=/order-serv/** # http://localhost:8088/order-serv/order/add 路由到 ↓ # http://localhost:8010/order-serv/order/add filters: - StripPrefix=1 # 转发之前去掉一级路径 order-serv # http://localhost:8010/order/add
-
通过GateWay访问配置的规则
GateWay整合nacos
-
添加依赖
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency>
-
修改配置文件
server: port: 8088 spring: application: name: api-gateway cloud: # gateway的配置 gateway: # 路由规则 routes: - id: order_route # 路由的唯一标识,路由到order uri: lb://order-service # 需要转发的地址 # lb是从nacos中按照名称获取服务,并遵循负载均衡策略 # 断言规则 用于路由规则的匹配 predicates: - Path=/order-serv/** # http://localhost:8088/order-serv/order/add 路由到 ↓ # http://localhost:8010/order-serv/order/add filters: - StripPrefix=1 # 转发之前去掉一级路径 order-serv # http://localhost:8010/order/add # 添加nacos配置信息 nacos: server-addr: 192.168.2.7:8848 discovery: username: nacos password: nacos namespace: public
断言
当请求gateway的时候, 使用断言对请求进行匹配, 如果匹配成功就路由转发, 如果匹配失败就返回404 。
内置断言工厂
-
基于Datetime类型的断言工厂
此类型的断言根据时间做判断,主要有三个:-
AfterRoutePredicateFactory: 接收一个日期参数,判断请求日期是否晚于指定日期
- After=2017-01-20T17:42:47.789-07:00[America/Denver]
-
BeforeRoutePredicateFactory: 接收一个日期参数,判断请求日期是否早于指定日期
- Before=2017-01-20T17:42:47.789-07:00[America/Denver]
-
BetweenRoutePredicateFactory: 接收两个日期参数,判断请求日期是否在指定时间段内
- Between=2017-01-20T17:42:47.789-07:00[America/Denver], 2017-01-21T17:42:47.789-07:00[America/Denver]
-
-
基于远程地址的断言工厂
RemoteAddrRoutePredicateFactory:接收一个IP地址段,判断请求主机地址是否在地址段中‐ RemoteAddr=192.168.1.1/24
-
基于Cookie的断言工厂
CookieRoutePredicateFactory:接收两个参数,cookie 名字和一个正则表达式。 判断请求 。 Cookie是否具有给定名称且值与正则表达式匹配。‐Cookie=chocolate, ch.
-
基于Header的断言工厂
HeaderRoutePredicateFactory:接收两个参数,标题名称和正则表达式。 判断请求Header是否具有给定名称且值与正则表达式匹配。‐Header=X‐Request‐Id, \d+
-
基于Host的断言工厂
HostRoutePredicateFactory:接收一个参数,主机名模式。判断请求的Host是否满足匹配规则。‐Host=**.testhost.org
-
基于Method请求方法的断言工厂
MethodRoutePredicateFactory:接收一个参数,判断请求类型是否跟指定的类型匹配。‐Method=GET
-
基于Path请求路径的断言工厂
PathRoutePredicateFactory:接收一个参数,判断请求的URI部分是否满足路径规则。‐Path=/foo/{segment}
-
基于Query请求参数的断言工厂
QueryRoutePredicateFactory :接收两个参数,请求param和正则表达式, 判断请求参数是否具有给定名称且值与正则表达式匹配。‐Query=baz, ba.
-
基于路由权重的断言工厂
WeightRoutePredicateFactory:接收一个[组名,权重], 然后对于同一个组内的路由按照权重转发。routes: ‐id: weight_route1 uri: host1 predicates: ‐Path=/product/** ‐Weight=group3, 1 ‐id: weight_route2 uri: host2 predicates: ‐Path=/product/** ‐Weight= group3, 9
自定义断言工厂
- 自定义断言工厂必须交个spring管理
- 自定义断言工厂类名必须以RoutePredicateFactory结尾(约定大于配置的思想)
- 继承AbstractRoutePredicateFactory抽象类
- 自定义断言工厂内部声明静态内部类,声明属性用来接收配置文件中对应的断言信息
- 使用shortcutFieldOrder方法进行数据绑定
- 在applay内进行逻辑判断
@Component
public class MyRoutePredicateFactory extends AbstractRoutePredicateFactory<MyRoutePredicateFactory.Config> {
public MyRoutePredicateFactory() {
super(Config.class);
}
public Predicate<ServerWebExchange> apply(Config config) {
return new GatewayPredicate() {
public boolean test(ServerWebExchange serverWebExchange) {
return config.getName().equals("zhangsan");
}
public String toString() {
return String.format("MyRoute: param=%s", config.getName());
}
};
}
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;
}
}
}
过滤器
内置过滤器
Gateway 内置了很多的过滤器工厂,我们通过一些过滤器工厂可以进行一些业务逻辑处理器,比如添加剔除响应头,添加去除参数等。
官网中的过滤器都有示例,可参考官网
https://docs.spring.io/spring-cloud-gateway/docs/2.2.5.RELEASE/reference/html/#gatewayfilter-factories
过滤器工厂 | 作用 | 参数 |
---|---|---|
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 | 修改原始响应体的内容 | 修改后的响应体内容 |
自定义过滤器
自定义的过滤器和前面的自定义断言工厂流程差不多。
自定义过滤器对响应头进行了添加,MyGatewayFilter:test filter
代码如下:
@Component
public class MyGatewayFilterFactory extends AbstractGatewayFilterFactory<MyGatewayFilterFactory.MyConfig> {
public MyGatewayFilterFactory() {
super(MyGatewayFilterFactory.MyConfig.class);
}
@Override
public GatewayFilter apply(MyConfig config) {
return new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String name = exchange.getRequest().getQueryParams().getFirst("name");
if(StringUtils.isNotBlank(name)){
if (config.getValue().equals(name)){
exchange.getResponse().getHeaders().set("MyGatewayFilter","test filter");
return chain.filter(exchange);
}else{
exchange.getResponse().setStatusCode(HttpStatus.NOT_FOUND);
exchange.getResponse().setComplete();
}
}
//正常请求
return chain.filter(exchange);
}
};
}
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList("value","key");
}
public static class MyConfig{
private String value;
private String key;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
}
}
配置文件如下:
server:
port: 8088
spring:
application:
name: api-gateway
cloud:
gateway:
routes:
- id: order_route
uri: lb://order-service
predicates:
- Path=/order-serv/**
- My=zhangsan
filters:
- My=zhangsan
- StripPrefix=1
nacos:
server-addr: 192.168.2.7:8848
discovery:
username: nacos
password: nacos
namespace: public
查看效果:
全局过滤器
局部过滤器和全局过滤器的区别:
GlobalFilter接口和GatewayFilter 有一样的接口定义,但GlobalFilter 会作用于所有路由。
局部:针对某个路由,需要在所应用的路由中进行配置。
全局:针对所有路由请求,一旦定义就可以生效。
Gateway自带内置全局过滤器
自定义全局过滤器
实现GlobalFilter接口即可;
@Component
public class MyGlobalLogFilter implements GlobalFilter {
Logger log = LoggerFactory.getLogger(this.getClass());
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("访问路径为{}",exchange.getRequest().getPath().value());
return chain.filter(exchange);
}
}
Gateway整合sentinel
-
添加依赖
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId> </dependency>
-
添加配置文件
sentinel: transport: dashboard: 127.0.0.1:8080
-
启动项目、nacos、sentinel
-
查看效果
这个控制台和单纯的项目整合sentinel的页面有一些不一样的地方,大配置基本大同小异。一些流控规则可以参考我sentinel笔记