优质博文:IT-BLOG-CN
【1】Route
路由: Gateway
的基本构建模块,它由ID
、目标URL
、断言集合和过滤器集合组成。如果聚合断言结果为真,则匹配到该路由。
Route
路由-动态路由实现原理: 配置变化Apollo
+ 服务地址实例变化Nacos
。Spring Cloud Gateway
通过RouteDefinitionLocator
和RouteRefreshListener
等组件实现动态路由。
先看下配置信息,方便后面原理的理解:SpringCloudGateway bootstrap.yml
的配置如下:
spring:
application:
name: gateway-service
cloud:
nacos:
discovery:
server-addr: ${NACOS_SERVER_ADDR:localhost:8848}
apollo:
bootstrap:
enabled: true
meta: ${APOLLO_META:localhost:8080}
application.yml
的配置如下:
spring:
cloud:
gateway:
discovery:
locator:
enabled: true
lower-case-service-id: true
apollo:
bootstrap:
namespaces: application # 1、登录 Apollo 控制台。 2、创建一个新的配置,例如 application.yml。 3、内容就是上看配置的SpringCloud Gateway 配置的路由信息
1、RouteDefinitionLocator
:Spring Cloud Gateway
启动时,会通过RouteDefinitionLocator
从Apollo
加载初始的路由定义。
2、DiscoveryClientRouteDefinitionLocator
:使用Nacos
进行服务发现,从Nacos
获取动态路由定义。
3、RouteDefinitionRepository
:加载的路由定义会存储在RouteDefinitionRepository
中,供后续路由匹配使用。
4、RouteRefreshListener
:监听路由定义的变化事件(如配置更新、服务实例变化等)。当监听到路由定义变化事件时,触发路由刷新操作,更新网关的路由规则,重新加载并应用新的路由配置。
GatewayHandlerMapping
根据预先配置的路由信息和请求的属性(如路径、方法、头部信息等)来确定哪个路由与请求匹配。它使用谓词Predicates
来进行匹配判断。
【2】Predicate
断言: 这是一个Java 8 Function Predicate
。输入类型是Spring Framework ServerWebExchange
。允许开发人员匹配来自HTTP
请求的任何内容,例如Header
或参数。Predicate
接受一个输入参数,返回一个布尔值结果。Spring Cloud Gateway
内置了许多Predict
,这些Predict
的源码在org.springframework.cloud.gateway.handler.predicate
包中,如果读者有兴趣可以阅读一下。现在列举各种 Predicate如下图:
在上图中,有很多类型的Predicate
,比如说时间类型的 Predicated
[AfterRoutePredicateFactory BeforeRoutePredicateFactory BetweenRoutePredicateFactory
],当只有满足特定时间要求的请求会进入到此Predicate
中,并交由Router
处理;Cookie
类型的CookieRoutePredicateFactory
,指定的Cookie
满足正则匹配,才会进入此Router
。以及host
、method
、path
、querparam
、remoteaddr
类型的Predicate
,每一种Predicate
都会对当前的客户端请求进行判断,是否满足当前的要求,如果满足则交给当前请求处理。如果有很多个Predicate
,并且一个请求满足多个Predicate
,则按照配置的顺序第一个生效。
Predicate 断言配置:
server:
port: 8080
spring:
application:
name: api-gateway
cloud:
gateway:
routes:
- id: gateway-service
uri: https://www.baidu.com
order: 0
predicates:
- After=2017-01-20T17:42:47.789-07:00[America/Denver]
- Host=**.foo.org
- Path=/headers
- Method=GET
- Header=X-Request-Id, \d+
- Query=foo, ba.
- Query=baz
- Cookie=chocolate, ch.p
在上面的配置文件中,配置了服务的端口为8080
,配置spring cloud gateway
相关的配置,id
标签配置的是router
的id
,每个router
都需要一个唯一的id
,uri
配置的是将请求路由到哪里,本案例全部路由到https://www.baidu.com
。
Predicates
: After=2017-01-20T17:42:47.789-07:00[America/Denver]
会被解析成PredicateDefinition
对象name =After ,args= 2017-01-20T17:42:47.789-07:00[America/Denver]
。需要注意的是Predicates
的After
这个配置,遵循契约大于配置的思想,它实际被 AfterRoutePredicateFactory
这个类所处理,这个After
就是指定了它的Gateway web handler
类为AfterRoutePredicateFactory
,同理,其他类型的Predicate
也遵循这个规则。当请求的时间在这个配置的时间之后,请求会被路由到指定的URL
。跟时间相关的Predicates
还有 Before Route Predicate Factory
、Between Route Predicate Factory
,读者可以自行查阅官方文档,再次不再演示。
Query=baz
: Query
的值以键值对的方式进行配置,这样在请求过来时会对属性值和正则进行匹配,匹配上才会走路由。经过测试发现只要请求汇总带有baz
参数即会匹配路由[localhost:8080?baz=x&id=2
],不带baz
参数则不会匹配。
Query=foo, ba.
:这样只要当请求中包含foo
属性并且参数值是以 ba
开头的长度为三位的字符串才会进行匹配和路由。使用curl
测试,命令行输入:curl localhost:8080?foo=bab
测试可以返回页面代码,将foo
的属性值改为babx
再次访问就会报404
,证明路由需要匹配正则表达式才会进行路由。
Header=X-Request-Id
, \d+
:使用curl
测试,命令行输入:curl http://localhost:8080 -H "X-Request-Id:88"
则返回页面代码证明匹配成功。将参数-H "X-Request-Id:88"改为-H "X-Request-Id:spring"
再次执行时返回404
证明没有匹配。
【3】Filter
过滤器:方案一:写死在代码中
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
//openapi路由转发
.route("openapi_route", p -> p.path( "/openapi/**").filters(f->f.removeRequestHeader("Expect"))
.uri("lb://order-openapi-service"))
.build();
}
方案二:配置文件yml
# gateway 的配置形式
routes:
- id: order-service #路由ID,没有规定规则但要求唯一,建议配合服务名。
uri: lb://order-service
predicates:
- Path=/order/**
filters:
- ValidateCodeGatewayFilter
Filter
过滤器:Filter
按处理顺序Pre Filter / Post Filter
Filter
按作用范围分为: GlobalFilter
全局过滤器。GatewayFilter
指定路由的过滤器。
Filter
过滤器-扩展自定义Filter
: Filter
支持通过spi
扩展。实现GatewayFilter
,Ordered
接口。
Filter
方法: 过滤器处理逻辑。getOrder
:定义优先级,值越大优先级越低。
过滤器的名称只需要写前缀,过滤器命名必须是
xxxGatewayFilterFactory
(包括自定义)。
全局过滤器示例: 创建一个全局过滤器类,这也是一个前置过滤器,实现GlobalFilter
接口:
public class TokenFilter implements GlobalFilter, Ordered {
Logger logger=LoggerFactory.getLogger( TokenFilter.class );
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String token = exchange.getRequest().getQueryParams().getFirst("token");
if (token == null || token.isEmpty()) {
logger.info( "token is empty..." );
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange); // 先执行业务逻辑,在执行exchange,是前置过滤器
}
@Override
public int getOrder() {
// // 过滤器的执行顺序,值越小优先级越高
return -100;
}
}
自定义路由过滤器示例: 创建自定义的路由过滤器,可以实现GatewayFilter
接口:
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Component
public class MyCustomFilter extends AbstractGatewayFilterFactory<MyCustomFilter.Config> {
public MyCustomFilter() {
super(Config.class);
}
@Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
// 前置过滤逻辑
System.out.println("Custom Pre Filter executed");
return chain.filter(exchange).then(Mono.fromRunnable(() -> { // 限制性exchange再执行过滤器业务逻辑,是后期处理器。
// 后置过滤逻辑
System.out.println("Custom Post Filter executed");
}));
};
}
public static class Config {
// 配置属性
}
}
在配置文件中使用自定义过滤器:
spring:
cloud:
gateway:
routes:
- id: my_route
uri: http://httpbin.org:80
predicates:
- Path=/get
filters:
- name: MyCustomFilter