基础知识:
在使用Spring Cloud Gateway的时候需要理解三个模块,即
Route:
即一套路由规则,是集URI、predicate、filter等属性的一个元数据类。
Predicate:
这是Java8函数式编程的一个方法,这里可以看做是满足什么条件的时候,route规则进行生效。
Filter:
filter可以认为是Spring Cloud Gateway最核心的模块,熔断、安全、逻辑执行、网络调用都是filter来完成的,其中又细分为gateway filter和global filter,区别在于是具体一个route规则生效还是所有route规则都生效。
以下代码实例
@RequestMapping("/paramTest")
public Object paramTest(@RequestParam Map<String,Object> param) {
return param.get("name");
}
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("path_route", r ->
r.path("/get")
.filters(f -> f.addRequestParameter("name", "value"))
.uri("forward:///paramTest"))
.build();
}
.route方法代表的就是一个路由规则;
.path方法代表的就是一个predicate,背后的现实是PathRoutePredicateFactory,在这段代码的含义即当路径包含/get的时候,当前规则生效。
.filters方法的意思即给当前路由规则添加一个增加请求参数的filter,每次请求都对参数里添加 name:value 的键值对;
.uri 方法的含义即最终路由到哪里去,这里的forward前缀会将请求交给spring mvc的DispatcherHandler进行路由,进行本机的逻辑调用,除了forward以外还可以使用http、https前缀进行http调用,lb前缀可以在配置注册中心后进行rpc调用。(常用lb,需要在服务中配置后,注册到注册中心,然后可以通过网关路由到对应的服务)
下图是Spring Cloud Gateway官方文档给出的一个工作原理图,Spring Cloud Gateway 接收到请求后进行路由规则的匹配,然后交给web handler 进行处理,web handler 会执行一系列的filter逻辑。
流程分析:
- 接受请求
Spring Cloud Gateway的底层框架是netty,接受请求的关键类是ReactorHttpHandlerAdapter,做的事情很简单,就是将netty的请求、响应转为http的请求、响应并交给一个http handler执行后面的逻辑
以下为源码:
public Mono<Void> apply(HttpServerRequest reactorRequest, HttpServerResponse reactorResponse) {
NettyDataBufferFactory bufferFactory = new NettyDataBufferFactory(reactorResponse.alloc());
try {
//转换请求
ReactorServerHttpRequest request = new ReactorServerHttpRequest(reactorRequest, bufferFactory);
ServerHttpResponse response = new ReactorServerHttpResponse(reactorResponse, bufferFactory);
if (request.getMethod() == HttpMethod.HEAD) {
response = new HttpHeadResponseDecorator((ServerHttpResponse)response);
}
return this.httpHandler.handle(request, (ServerHttpResponse)response).doOnError((ex) -> {
logger.trace(request.getLogPrefix() + "Failed to complete: " + ex.getMessage());
}).doOnSuccess((aVoid) -> {
logger.trace(request.getLogPrefix() + "Handling completed");
});
} catch (URISyntaxException var6) {
if (logger.isDebugEnabled()) {
logger.debug("Failed to get request URI: " + var6.getMessage());
}
reactorResponse.status(HttpResponseStatus.BAD_REQUEST);
return Mono.empty();
}
}
-
WEB过滤器链
http handler做的事情第一是将request 和 response转为一个exchange,这个exchange非常核心,是各个filter之间参数流转的载体,该类包含request、response、attributes(扩展字段),接着做的事情就是web filter链的执行,其中的逻辑主要是监控。
其中WebfilterChainParoxy 又会引出新的一条filter链,主要是安全、日志、认证相关的逻辑,由此可见Spring Cloud Gateway的过滤器设计是层层嵌套,扩展性很强。(网关大部分核心处理逻辑可以在此filter链实现)
-
寻找路由规则
核心类是RoutePredicateHandlerMapping,逻辑也非常简单,就是把所有的route规则的predicate遍历一遍看哪个predicate能够命中,核心代码是:protected Mono<Route> lookupRoute(ServerWebExchange exchange) { return this.routeLocator.getRoutes().concatMap((route) -> { return Mono.just(route).filterWhen((r) -> { exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_PREDICATE_ROUTE_ATTR, r.getId()); return (Publisher)r.getPredicate().apply(exchange); }).doOnError((e) -> { this.logger.error("Error applying predicate for route: " + route.getId(), e); }).onErrorResume((e) -> { return Mono.empty(); }); }).next().map((route) -> { if (this.logger.isDebugEnabled()) { this.logger.debug("Route matched: " + route.getId()); } this.validateRoute(route, exchange); return route; }); }
PathRoutePredicateFactory路由规则
public Predicate<ServerWebExchange> apply(Config config) { final ArrayList<PathPattern> pathPatterns = new ArrayList<>(); synchronized (this.pathPatternParser) { pathPatternParser.setMatchOptionalTrailingSeparator( config.isMatchOptionalTrailingSeparator()); config.getPatterns().forEach(pattern -> { PathPattern pathPattern = this.pathPatternParser.parse(pattern); pathPatterns.add(pathPattern); }); } return new GatewayPredicate() { @Override public boolean test(ServerWebExchange exchange) { PathContainer path = parsePath( exchange.getRequest().getURI().getRawPath()); PathPattern match = null; for (int i = 0; i < pathPatterns.size(); i++) { PathPattern pathPattern = pathPatterns.get(i); if (pathPattern.matches(path)) { match = pathPattern; break; } } if (match != null) { traceMatch("Pattern", match.getPatternString(), path, true); PathMatchInfo pathMatchInfo = match.matchAndExtract(path); putUriTemplateVariables(exchange, pathMatchInfo.getUriVariables()); return true; } else { traceMatch("Pattern", config.getPatterns(), path, false); return false; } } @Override public String toString() { return String.format("Paths: %s, match trailing slash: %b", config.getPatterns(), config.isMatchOptionalTrailingSeparator()); } }; }
因为我这里用的是path进行过滤,所以背后的逻辑是PathRoutePredicateFactory来完成的,除了PathRoutePredicateFactory还有很多predicate规则。
-
核心过滤器链执行
找到路由规则后下一步就是执行了,这里的核心类是FilteringWebHandler,其中的源码为
public Mono<Void> handle(ServerWebExchange exchange) { Route route = exchange.getRequiredAttribute(GATEWAY_ROUTE_ATTR); List<GatewayFilter> gatewayFilters = route.getFilters(); List<GatewayFilter> combined = new ArrayList<>(this.globalFilters); combined.addAll(gatewayFilters); // TODO: needed or cached? AnnotationAwareOrderComparator.sort(combined); if (logger.isDebugEnabled()) { logger.debug("Sorted gatewayFilterFactories: " + combined); } return new DefaultGatewayFilterChain(combined).filter(exchange); }
做的事情很简单:
- 获取route级别的过滤器
- 获取全局过滤器
- 两种过滤器放在一起并根据order进行排序
- 执行过滤器链
因为我的配置里包含了一个添加请求参数的逻辑,所以红线箭头处就是我配置的gateway filter名为 AddRequestParameterGatewayFilterFactory,其余全是Gloabl Filter,这些过滤器的功能主要是url解析,请求转发,响应回写等逻辑,因为我们这里用的是forward schema,所以请求转发会由ForwardRoutingFilter进行执行。
-
请求转发
ForwardRoutingFilter做的事情也很简单,直接复用了spring mvc的能力,将请求提交给dispatcherHandler进行处理,dispatcherHandler会根据path前缀找到需要目标处理器执行逻辑。
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { URI requestUrl = exchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR); String scheme = requestUrl.getScheme(); if (isAlreadyRouted(exchange) || !"forward".equals(scheme)) { return chain.filter(exchange); } // TODO: translate url? if (log.isTraceEnabled()) { log.trace("Forwarding to URI: " + requestUrl); } return this.getDispatcherHandler().handle(exchange); }
-
响应回写
响应回写的核心类是NettyWriteResponseFilter,但是大家可以注意到执行器链中NettyWriteResponseFilter的排序是在最前面的,按道理这种响应处理的类应该是在靠后才对,这里的设计比较巧妙。大家可以看到chain.filter(exchange).then(),意思就是执行到我的时候直接跳过下一个,等后面的过滤器都执行完后才执行这段逻辑,这种行为控制的方法值得学习。public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { // NOTICE: nothing in "pre" filter stage as CLIENT_RESPONSE_CONN_ATTR is not added // until the NettyRoutingFilter is run // @formatter:off return chain.filter(exchange) .doOnError(throwable -> cleanup(exchange)) .then(Mono.defer(() -> { Connection connection = exchange.getAttribute(CLIENT_RESPONSE_CONN_ATTR); if (connection == null) { return Mono.empty(); } if (log.isTraceEnabled()) { log.trace("NettyWriteResponseFilter start inbound: " + connection.channel().id().asShortText() + ", outbound: " + exchange.getLogPrefix()); } ServerHttpResponse response = exchange.getResponse(); // TODO: needed? final Flux<DataBuffer> body = connection .inbound() .receive() .retain() .map(byteBuf -> wrap(byteBuf, response)); MediaType contentType = null; try { contentType = response.getHeaders().getContentType(); } catch (Exception e) { if (log.isTraceEnabled()) { log.trace("invalid media type", e); } } return (isStreamingMediaType(contentType) ? response.writeAndFlushWith(body.map(Flux::just)) : response.writeWith(body)); })).doOnCancel(() -> cleanup(exchange)); // @formatter:on }