SpringCloud gateway已经出来很长时间,现在正好要做一个动态网关,落个笔录备忘。现在微博上很多网关在项目上的使用性比较差,本文会给出一个完整的方案及中间版本。
目录
网关核心功能
SpringCloud gateway
Gateway包含哪些功能呢
Gateway的工作方式
Predicate
Filter
如何动态加载路由
首先看如何加载的
加载顺序
在这里,只对springcloud gateway做梳理讲解。
Springcloud gateway是基于Rective Streams 的 WebFlux框架。其主要优势、原理等不在本文阐述。
客户端向 Spring Cloud Gateway 发出请求。然后在 Gateway Handler Mapping 中找到与请求相匹配的路由(路由转发,即请求和路由进行匹配,predicate),将其发送到 Gateway Web Handler。Handler 再通过指 定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前(“pre”)或之后(“post”)执行业务逻辑。
Filter在“pre”类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等,
在“post”类型的过滤器中可以做响应内容、响应头的修改,日志的输出,流量监控等有着非常重要的作用。
2. 先看如下配置:
请求通过predicates映射,符合此时间规则,路由到lb://enn-dfs:6002服务,并且在请求服务之前,执行AddRequestParameter过滤器。
重点讲下predicate,我现在基于springCloud Finchley.RELEASE版本开发。
在此版本中,gateway实现了11种不同的predicate路由。
RoutePredicateFactory 接口继承自 ShortcutConfigurable 接口,ShortcutConfigurable 接口在多个实现类中都有出现,根据传入的具体 RouteDefinitionLocator 获取路由定义对象时,就用到了该接口中的默认方法。路由谓词的种类很多,不同的谓词需要的配置参数不一样,所以每种 路由谓词(断言)和过滤器的实现都会实现 ShortcutConfigurable 接口,来指定自身参数的个数和顺序。下述predicate的实现继承了抽象类AbstractRoutePredicateFactory,而此类实现了RoutePredicateFactory。在此不粘贴源码,自己去看。
1. 请求时间校验条件谓语,分别是请求时间满足在配置时间之后、之前、之间。命中后映射请求
"After" -> [AfterRoutePredicateFactory@ configClass = AfterRoutePredicateFactory.Config]
"Before" -> [BeforeRoutePredicateFactory@ configClass = BeforeRoutePredicateFactory.Config]
"Between" -> [BetweenRoutePredicateFactory@ configClass = BetweenRoutePredicateFactory.Config]
2. 请求Cookie校验条件谓语,cookie正则匹配
"Cookie" -> [CookieRoutePredicateFactory@ configClass = CookieRoutePredicateFactory.Config]
3. 请求header校验条件谓语,正则匹配
"Header" -> [HeaderRoutePredicateFactory@ configClass = HeaderRoutePredicateFactory.Config]
4. 请求Host,即根据host匹配,命中映射请求roteUrl
"Host" -> [HostRoutePredicateFactory@ configClass = HostRoutePredicateFactory.Config]
5. 根据方法名称
"Method" -> [MethodRoutePredicateFactory@ configClass = MethodRoutePredicateFactory.Config]
6. 根据路径,此方式应该是最常用的方式,通过requestMapping路径映射。
"Path" -> [PathRoutePredicateFactory@ configClass = PathRoutePredicateFactory.Config]
7. 通过查询参数
"Query" -> [QueryRoutePredicateFactory@ configClass = QueryRoutePredicateFactory.Config]
"ReadBodyPredicateFactory" -> [ReadBodyPredicateFactory@ configClass = ReadBodyPredicateFactory.Config]
8. 通过地址
"RemoteAddr" -> [RemoteAddrRoutePredicateFactory@ configClass = RemoteAddrRoutePredicateFactory.Config]
9. 基于路由权重
"Weight" -> [WeightRoutePredicateFactory@ configClass = WeightConfig]
基于权重的稍微难理解些,后边做解释。
其他只对path进行解释,因为现今只使用了path
基于Path断言详述
根据路径断言相对来说比较好理解,看个例子就明白了,后续的动态路由中配置数据,也是基于Path,可以具体看下面。
spring:
cloud:
gateway:
routes:
- id: host_route
uri: http://example.org
predicates:
- Path=/foo/{segment}
基于路由权重断言详述
WeightRoutePredicateFactory 实现了路由权重的功能,按照路由权重选择 同一个分组中的路由。
spring:
cloud:
gateway:
locator:
enabled: true
routes:
-id: weight_route1
uri: lb://enn-dfs:6002
order: 6000
predicates:
-Weight= group3, 1
-Path=/weight/**
filters:
-StripPrefix= 2
-id: weight_route2
uri: lb://enn-dfs:6003
order: 6000
predicates:
-Path=/weight/**
-Weight= group3, 9
filters:
-StripPrefix=1
weight_ route1 权重为 1, weight_ route2 权重为9。 对于10个访问/ weight/** 路径的请求来说,将会有9个路由到 weight_ route2,1个路由到 weight_ route1。
WeightRoutePredicateFactory 权重算法实现过程
参考:https://www.cnblogs.com/liukaifeng/p/10055866.html
weight_route1: group3, 1
weight_ route2: group3, 9
实现过程为:
1)构造 weights(group3)数组:weights=[ 1, 9]
2)normalize: weights= weights/ sum( weights)=[ 0. 1, 0. 9]
3)计算区间范围: ranges= weights. collect( 0,( s, w)-> s+ w)=[ 0, 0. 1, 1. 0]
4)生成 随机数: r= random()
5)搜索随机数所在的区间: i= integer s. t. r>= ranges[ i]&& r< ranges[ i+ 1]
6)选择相应的路由: routes[ i]
网关应用服务在启动时会发布 WeightDefinedEvent,在 WeightCalculatorWebFilter 过滤器中定义了该时间监听器,当接收到时间 WeightDefinedEvent 时,会自动添加 WeightConfig 到权重配置中。请求在经过 WeightCalculatorWebFilter 时会 生成 一个 随机数, 根据随机数所在的区间选择对应分组的路由。
// WeightRoutePredicateFactory.java
@Override
public Predicate apply(Config config) {
List sources = convert(config.sources);
return exchange -> {
InetSocketAddress remoteAddress = config.remoteAddressResolver.resolve(exchange);
if(remoteAddress != null) {
String hostAddress = remoteAddress.getAddress().getHostAddress();
String host = exchange.getRequest().getURI().getHost();
if(log.isDebugEnabled() && !hostAddress.equals(host)) {
log.debug("Remote addresses didn't match " + hostAddress + " != " + host);
}
for(IpSubnetFilterRule source : sources) {
if(source.matches(remoteAddress)) {
return true;
}
}
}
return false;
};
}
当应用到配置的路由断言 WeightRoutePredicate 时,会根据 ServerWebExchange 中的 WEIGHT_ ATTR 值,判断当前的 routeId 与对应分组的 routeId 是否一致。
我们在上面解释了“pre””post”的作用,那么也可以看到filter的整个生命周期。
过滤器部分参考:https://www.cnblogs.com/bjlhx/p/9786478.html博客
Spring-Cloud-Gateway的过滤器接口分为两种:
过滤器允许以某种方式修改传入的HTTP请求或传出的HTTP响应。过滤器可以限定作用在某些特定请求路径上。 Spring Cloud Gateway包含许多内置的GatewayFilter工厂。
下面是springCloud gateway 实现的filter列表
下面看看具体的过滤器实现
GatewayFilter
网关路由过滤器,与下述GatewayFilterChain一样只有一个filter接口,执行当前过滤器,并在此方法中决定过滤器链表是否继续往下执行。
它由上述几个过滤器实现。
GlobalFilter
为请求业务以及路由的URI转换为真实业务服务的请求地址的核心过滤器,不需要配置,模式系统初始化时加载,并作用在每个路由上。通过GatewayAutoConfiguration自动创建初始化加载。
FilteringWebHandler:将全局过滤器包装成GatewayFilter,并作用于每个路由上
GatewayFilterChain: 用于过滤器的链式调用,只提供一个链表启动调用入口方法,过滤链表。
DefaultGatewayFilterChain:过滤器链表接口 GatewayFilterChain的默认实现,其中定义的List
由上述DefaultGatewayFilterChain可以看出GatewayFilterChain的执行顺序
Filter配置
这里只讲用到的filter
PrefixPathGatewayFilterFactory
针对请求url前缀进行处理的filter工厂,用户添加prefix。
spring:
cloud:
gateway:
routes:
- id: enn-dfs
uri: lb://enn-dfs:6002
filters:
- PrefixPath=/file
请求/upload,最后转发到目标服务的路径变为/file/upload
StripPrefixGatewayFilterFactory
针对请求url前缀进行处理的filter工厂,用于去除prefix
spring:
cloud:
gateway:
routes:
- id: enn-dfs
uri: lb://enn-dfs:6002
predicates:
- Path=/file/**
filters:
- StripPrefix=1
如请求路径为/file/upload去除掉前面1个前缀之后,最后转发到目标服务的路径为/upload
因为StripPrefix是直接操作Path,对于本版本来说,使用StripPrefix无效。
可以使用RewritePath,把映射的/file去除,可看下面配置。
通过RewritePath实现。代码查询Git源码。
"filters": [{
"name": "RewritePath",
"args": {
"regexp": "/file/(?.*)",
"replacement": "/${remaining}"
}
}]
Gateway 通过RouteDefinition来转换生成具体的路由信息,通过路由定义信息加载器RouteDefinitionLocator负责读取路由配置。看下子类实现。
RouteDefinitionLocator接口有且仅有一个方法getRouteDefinitions,此方法获取RouteDefinition的核心方法,返回Flux
在这里要注意一个配置:spring.cloud.gateway.discovery.locator.enabled=true,它的启停比较关键,如果你想只加载自定义的路由配置,那么要把此配置设置为false。因为他会默认加载CompositeRouteDefinitionLocator(InMemoryRouteDefinitionRepository、PropertiesRouteDefinitionLocator、DiscoveryClientRouteDefinitionLocator三个RouteDefinitionLocator),把注册中心等由自动加载的路由都配置进去。
可以查看AutoConfiguration里面的Primary信息,就知道加载配置信息的顺序
PropertiesRouteDefinitionLocator-->|配置文件加载初始化
RouteDefinitionRepository-->|存储器中加载初始化
DiscoveryClientRouteDefinitionLocator-->|注册中心加载初始化
下面讲动态路由实现,由于时间关系,待续。