了解一下网关的机制; 了解一下怎么样实现一个动态路由的网关
SpringCloudGateway 的文档上明确写了, 项目是基于Spring Boot 2.x, Spring WebFlux进行构建. SpringBoot项目在项目启动的时候会主动探测 deduceFromClasspath
当前容器的环境并进行构建,WebFlux和SpringMvc的功能和结构很类似, 但是由于响应式编程和传统的同步阻塞式的编程差异较大, 因此WebFlux重复造了一些轮子, 如:
在gateway中常用的类如下:
只要使用了SpringBoot项目,对这个服务监控的依赖应该或多或少有一定的接触, 在SpringCloud文档的第15章中有介绍, 但是我是先浏览了一遍官方文档的, 所以将这个提前,便于学习的时候能够了解网关内部的一些数据, 作为demo,直接将所有监控数据通过web接口暴露应该不过分吧,引入依赖加配置:
management:
endpoints:
web:
exposure:
include: '*'
加入配置之后,可以在 GatewayControllerEndpoint
这个类查看多暴露出的诸多web接口;
路由,gateway的基本构成块,内部持有了该路由的断言集合和过滤器集合, 其中断言必须全部命中才允许通过,通过后会执行所有的过滤器以及目的URI
断言,入参是ServerWebExchange,也就是说我们可以取出Request和Response进行判断 比如:请求的时间,请求的header,请求的参数,请求路径 等;
过滤器,入参是ServerWebExchange和FilterChain, 我们可以在这个链中对请求的入参和响应进行调整 比如加Header,加Cookie,直接修改URI,基于注册中心时基于一定规则路由到不同的服务中 等;
下面的配置可以将所有请求时间在2022-01-25
spring:
cloud:
gateway:
routes:
- id: after_route
uri: https://example.org
predicates:
- After=2022-01-25T07:42:47.789-07:00[America/Denver]
手动造一个和上面效果一样的轮子,从 PredicateSpec
类中参考原生的实现写一个
public class BeforeRouteDemoPredicateFactory extends AbstractRoutePredicateFactory<BeforeRouteDemoPredicateFactory.Config> {
private final static DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
public static final String CONFIG_KEY = "before";
public BeforeRouteDemoPredicateFactory() {
super(Config.class);
}
public Predicate<ServerWebExchange> apply(Config config) {
return serverWebExchange -> {
LocalDate now = LocalDate.now();
return now.isAfter(config.getLocalDate());
};
}
public static class Config {
private LocalDate localDate;
public LocalDate getLocalDate() {
return localDate;
}
public void setLocalDate(String localDate) {
this.localDate = LocalDate.parse(localDate, DATE_TIME_FORMATTER);
}
}
}
/**
* 在返回的response上加一个cookie
*/
public class AddResponseCookiesDemoFilterFactory extends AbstractGatewayFilterFactory<AbstractGatewayFilterFactory.NameConfig> {
@Override
public GatewayFilter apply(Consumer<NameConfig> consumer) {
return apply(new NameConfig());
}
@Override
public GatewayFilter apply(NameConfig config) {
return (exchange, chain) -> chain.filter(exchange)
.then(Mono.fromRunnable(()->{
ServerHttpResponse response = exchange.getResponse();
ResponseCookie cookie = ResponseCookie.from("custom.demo", "gateway-demo").build();
response.addCookie(cookie);
}));
}
}
路由是通过构造者模式产生,直接自动装配中进行装配即可
@Configuration
@EnableConfigurationProperties(DemoAutoConfigProperties.class)
public class RouteExampleConfiguration {
@Bean
public BeforeRouteDemoPredicateFactory beforeRouteDemoPredicateFactory() {
return new BeforeRouteDemoPredicateFactory();
}
@Bean
public AddResponseCookiesDemoFilterFactory addResponseCookiesDemoFilterFactory() {
return new AddResponseCookiesDemoFilterFactory();
}
@Bean
public RouteLocator testRouteLocator(RouteLocatorBuilder locatorBuilder, DemoAutoConfigProperties properties) {
BeforeRouteDemoPredicateFactory predicateFactory = beforeRouteDemoPredicateFactory();
AddResponseCookiesDemoFilterFactory filterFactory = addResponseCookiesDemoFilterFactory();
Predicate<ServerWebExchange> exchangePredicate = predicateFactory.apply(config -> config.setLocalDate(properties.getBefore()));
GatewayFilter gatewayFilter = filterFactory.apply(config -> {
});
return locatorBuilder.routes()
.route("demo.route",
r -> r.predicate(exchangePredicate)
.filters(f -> f.filter(gatewayFilter))
.uri(properties.getUri())
).build();
}
}
东西要全套进行熟悉,简单写了两个需要配置的变量
demo:
gateway:
before: 2022-01-24
uri: https://example.com
手写例子之后,感觉gateway也就是三板斧的事情: route, predicate, filter
因此简单梳理下gateway的请求流程:
WebFlux的中央控制器,和SpringMvc的DispatcherServlet一样,协调所有请求; GateWay是基于WebFlux的,因此入口就是这里
WebFlux定义了WebHandler以开放扩展,GateWay通过实现该类来实现内部的流程; 可以说是GateWay内部小天地的入口; 具体实现类: FilteringWebHandler
HandlerMapping的功能和SpringMvc一样; 拓展了一点功能 内部持有了 RouteLocator
, 也就是网关所有的路由功能都可以获取到; 当进行请求映射的时候,通过RouteLocator类找到适合的Route ; 然后丢到ServerWebExchange中; 就是将Route的信息丢到了全局上下文中便于使用
类的注释上有描述:
对Web请求进行拦截式、链式处理的契约,
这些请求可用于实现横切的、与应用程序无关的需求,如安全性、超时等。
我们的route都需要配置uri ; 并且必须有schema,比如:http/https/lb/forward ; 他们的实现原理就是通过GlobalFilter来实现的;
不同于Filter和Predicate,uri的模式是统一的,是直接设置为全局的Filter, 所有请求都执行一下,看看应该走哪一种规则就可以了,因此不需要工厂 如: ForwardPathFilter / ReactiveLoadBalancerClientFilter / RouteToRequestUrlFilter , 不过uri应该在最后执行,所以也实现了Ordered接口,保证执行的优先级低
用来生成GateWay内部的拦截器的工厂类,阅读 GatewayFilterSpec
类可以多进行了解
用来生成断言的工厂类, 阅读 PredicateSpec
类可以多进行了解
组合Filter、Predicate和uri ; 这个Route在HandlerMapping阶段就已经放到了上下文中; 便于后续流程中使用;又重要又简单的类
RoutePredicateHandlerMapping中有提到这个接口; 这个接口就是route的定位器; 持有了所有的Route规则, 通常我们要新增一个Route也是在RouteLocatorBuilder 的基础上,进行增加; 这个接口对外暴露只有一个功能; 就是返回所有的路由
按照官方网站测了好多demo,但是都是启动的时候直接将所有的路由进行加载的, 实际使用过程中肯定是需要给网关加配置的;
前面actuator的时候提到了 GatewayControllerEndpoint, 通过这个web端点,GateWay已经贴心的支持了这些事情 ;
基本流程只有两个步骤:
我们直接注入RouteDefinitionRepository进行调整也行
@Component
public class CustomDynamic {
//默认 InMemoryRouteDefinitionRepository
@Autowired
RouteDefinitionRepository routeDefinitionRepository;
// 这样子引入的是: CompositeRouteDefinitionLocator; 组合了多个定位器
@Autowired
RouteDefinitionLocator routeDefinitionLocator;
public void save(RouteDefinition routeDefinition) {
Mono<RouteDefinition> definitionMono = Mono.just(routeDefinition);
routeDefinitionRepository.save(definitionMono).block();
}
public Flux<RouteDefinition> list() {
return routeDefinitionLocator.getRouteDefinitions();
}
public void delete(String routeId) {
routeDefinitionRepository.delete(Mono.just(routeId)).block();
}
}
@Component
public class DynamicRunner implements CommandLineRunner {
@Autowired
CustomDynamic customDynamic;
@Autowired
ApplicationEventPublisher publisher;
@Override
public void run(String... args) throws Exception {
Flux<RouteDefinition> list = customDynamic.list();
list.subscribe(System.out::println);
customDynamic.save(buildSimpleRouteDefinition());
TimeUnit.SECONDS.sleep(30);
publisher.publishEvent(new RefreshRoutesEvent(this));
}
public RouteDefinition buildSimpleRouteDefinition() {
RouteDefinition routeDefinition = new RouteDefinition();
routeDefinition.setId("dynamic.demo");
routeDefinition.setOrder(0);
routeDefinition.setUri(URI.create("https://example.com"));
PredicateDefinition predicateDefinition = new PredicateDefinition();
predicateDefinition.setName("After");
Map<String, String> map = new HashMap<>();
map.put("datetime", "2012-01-24T17:42:47.789-07:00[America/Denver]");
predicateDefinition.setArgs(map);
List<PredicateDefinition> definitionArrayList = new ArrayList<>();
definitionArrayList.add(predicateDefinition);
routeDefinition.setPredicates(definitionArrayList);
return routeDefinition;
}
}
原以为很复杂,但是仿照了 AbstractGatewayControllerEndpoint
后, 这个功能反而显得很简单,只要监听远成配置或者定时任务代替runner就是动态的路由调整了
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。