目录
Gateway作为SpringCloud体系中的网关,可以用来进行鉴权,安全控制,日志统一处理,易于监控,限流等。
一、自定义断言工厂
Gateway官方给我们能提供了很多种断言的方式,比如说通过路径、请求方法、Cookie等等一系列的断言方式,我们可以参考一下官方的写法,然后就可以写出我们自己需要断言方式。
首先我们来到AbstractRoutePredicateFactory,这是断言父类接口的抽象实现类,因为我们需要自己定义断言的条件,所以在yml中解析到的参数会放到一个配置类当中,那这个构造方法其实就是将定义的配置类传到上面,这个抽象类的实现类其实就是各种断言工厂
首先我们定义一个类叫AccessTokenRoutePredicateFactory,因为遵循着约定大于配置的思想,所以我们在yml中配置的就会是 - AccessToken 开头的,并且我们需要继承AbstractRoutePredicateFactory并重写它的几个方法即可,下面是完整的代码
@Slf4j
@Component
public class AccessTokenRoutePredicateFactory extends AbstractRoutePredicateFactory<AccessTokenRoutePredicateFactory.Config> {
private static final String Name = "headerName";
private static final String Value = "value";
public AccessTokenRoutePredicateFactory() {
super(Config.class);
}
@Override
public Predicate<ServerWebExchange> apply(Config config) {
return new Predicate<ServerWebExchange>() {
@Override
public boolean test(ServerWebExchange exchange) {
HttpHeaders headers = exchange.getRequest().getHeaders();
List<String> list = headers.getOrDefault(config.headerName, Collections.emptyList());
if(list.size() != 1){
return false;
}
String value = list.get(0);
return value.equalsIgnoreCase(config.getValue());
}
};
}
@Override
public ShortcutType shortcutType() {
return ShortcutType.DEFAULT;
}
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList(Name, Value);
}
@Data
public static class Config {
private String headerName;
private String value;
}
}
shortcutFieldOrder方法设置Config配置类的属性,会通过shortcutType设置的规则来进行设置,其中有好几种规则,DEFAULT是按照顺序依次赋值。
由于发送请求时存储在请求头之中,所以我们简单的获取比较一下即可完成逻辑
二、自定义过滤器
Gateway中的过滤器分为GatewayFilter(单一路由过滤器,也可以设置成全局)和GlobalFilter(全局过滤器)
1.自定义GatewayFilter
跟上面自定义断言工厂一样,需要创建一个遵循约定大于配置的过滤器实现AbstractGatewayFilterFactory,还是一样的设置shortcutType以及shortcutFieldOrder,下面是完成代码以及配置文件参数
@Slf4j
@Component
public class MyFilterGatewayFilterFactory extends AbstractGatewayFilterFactory<MyFilterGatewayFilterFactory.Config> {
private static String Name = "name";
private static String Value = "value";
public MyFilterGatewayFilterFactory() {
super(Config.class);
}
@Override
public GatewayFilter apply(Config config) {
return new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("MyFilterGatewayFilterFactory执行了,参数1={},参数2={}", config.getName(), config.getValue());
return chain.filter(exchange);
}
};
}
@Override
public ShortcutType shortcutType() {
return ShortcutType.DEFAULT;
}
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList(Name, Value);
}
@Data
public static class Config {
private String name;
private String value;
}
}
#路由断言
predicates:
- Path=/driver/**
- AccessToken=token,yxl
- Method=post
filters:
- MyFilter=yxl,continue
2.自定义GlobalFilter
定义全局过滤器需要继承和实现GlobalFilter(负责拦截处理),Ordered(负责进行排序,也就是优先级)
@Component
public class RouterFilter implements GlobalFilter,Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String token = exchange.getRequest().getQueryParams().getFirst("abo");
// 如果为空
if(StringUtils.isEmpty(token)){
// 设置状态
exchange.getResponse().setStatusCode(HttpStatus.PAYLOAD_TOO_LARGE);
// 返回
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
// 设置优先级
return 0;
}
}
二、Gateway源码剖析
Gateway的源码我们不细讲,因为底层相对复杂,我们着重于它的处理流程,首先就是它的构成 ,我们着重于gateway这一层
我们首先找到入口,在DispatcherHandler的handle()方法中我们可以看到处理请求的大体流程
- 通过上下文获取handler,其实就是匹配断言的过程
- 匹配断言成功之后,在invokeHandler()中就是经过各种各样的过滤器,包括我们配置的全局、当前路由等等都会去执行
- 处理结果
@Override
public Mono<Void> handle(ServerWebExchange exchange) {
if (this.handlerMappings == null) {
return createNotFoundError();
}
return Flux.fromIterable(this.handlerMappings)
.concatMap(mapping -> mapping.getHandler(exchange))
.next()
.switchIfEmpty(createNotFoundError())
.flatMap(handler -> invokeHandler(exchange, handler))
.flatMap(result -> handleResult(exchange, result));
}
我们先来看获取handlerMapping,可以看到总共获取了六个,我们着重看下RoutePredicateHandlerMapping(),因为我们配置的就是断言,另外Flux.fromIterable看作遍历就行了
来到RoutePredicateHandlerMapping中,得先拿到所有的我们在配置文件中配置的断言,然后进行匹配,通过看predicate中的参数就可以看到每个路由配置的断言
protected Mono<Route> lookupRoute(ServerWebExchange exchange) {
// 拿到所有的route
return this.routeLocator.getRoutes()
// individually filter routes so that filterWhen error delaying is not a
// problem
.concatMap(route -> Mono.just(route).filterWhen(r -> {
// add the current route we are testing
exchange.getAttributes().put(GATEWAY_PREDICATE_ROUTE_ATTR, r.getId());
// 进行路由断言 Predicate
return r.getPredicate().apply(exchange);
})
// instead of immediately stopping main flux due to error, log and
// swallow it
.doOnError(e -> logger.error(
"Error applying predicate for route: " + route.getId(),
e))
.onErrorResume(e -> Mono.empty()))
// .defaultIfEmpty() put a static Route not found
// or .switchIfEmpty()
// .switchIfEmpty(Mono.<Route>empty().log("noroute"))
.next()
// TODO: error handling
.map(route -> {
if (logger.isDebugEnabled()) {
logger.debug("Route matched: " + route.getId());
}
validateRoute(route, exchange);
return route;
});
/*
* TODO: trace logging if (logger.isTraceEnabled()) {
* logger.trace("RouteDefinition did not match: " + routeDefinition.getId()); }
*/
}
断言匹配成功之后,拿到相对应的handler,开始进行invoke,在FilteringWebHandler中可以看到先是获取了所有的全局过滤器,包括我们之前配置的也在,也是因为我们已经知道要走那个路由了,所以再获取当前路由的过滤器,并把它们整合到一起进行排序
排序完之后可以看到我们配置的主要放在了中间,也是根据我们设置的优先级进行排序,我们着重看下最后几个过滤器
另外看下它是如何设置这个责任链的,用了一个构造方法,index默认为0,每次取出filter,重新构造一下,并且index+1
在RouteToRequestUrlFilter中主要就是将我们的请求前面的域名和端口转换成我们要调用的服务,并且判断了是否需要进行负载均衡,然后传到下一个过滤器
在LoadBalancerClientFilter中会通过ribbon选择出相对应的实例,拿到实例的ip和端口就会将前面拼装好的url转换成ip+端口+请求路径以及参数的形式
在NettyRoutingFilter中会通过reactor-netty将数据进行发送
到这里,gateway的处理流程就大体讲完了。