springcloud 提供了便于我们编写网关组件,分别是zuul和gateway;在zuul1的通信模型是BIO而zuul2的通信模型采用了NIO;spring gateway的IO模型使用的是NIO;但是从netflix发布zuul2后spring已经 开始 不集成zuul组件了。spring gateway的架构是基于WebFlux基础开发的。它依赖springboot2.x,spring5和netty
在spring MVC是通过handlermapping解析请求连接然后根据请求连接找到执行请求的controller类;而gateway采用同样的方式,用handlerMapping 堆请求连接进行解析匹配对应的Route,然后有对应的filter做对应的请求转发。整个流程用户请求先通过dispatchHandler找到对应的GateWayHandlerMapping ,再由GateWayHandlerMapping解析匹配对应的handler;handler处理完成后再经过filter链处理最终分发到proxied service。
HandlerMapping和Ordered接口主要定义了获取getHandler和当前handler的加载顺序。AbstractHandlerMapping 在getHandler封装了CORS处理,因为所有的handler都会涉及到CORS处理,抽象到AbstractHandlerMapping处理,再提供getHandlerInternal让子类实现具体的查找方法在这里插入代码片。
/**
*获取handler方法
**/
public Mono<Object> getHandler(ServerWebExchange exchange) {
return this.getHandlerInternal(exchange).map((handler) -> {
if (CorsUtils.isCorsRequest(exchange.getRequest())) {
CorsConfiguration configA = this.globalCorsConfigSource.getCorsConfiguration(exchange);
CorsConfiguration configB = this.getCorsConfiguration(handler, exchange);
CorsConfiguration config = configA != null ? configA.combine(configB) : configB;
if (!this.getCorsProcessor().process(config, exchange) || CorsUtils.isPreFlightRequest(exchange.getRequest())) {
return REQUEST_HANDLED_HANDLER;
}
}
return handler;
});
}
//抽象方法,让子类实现具体查找handler的
protected abstract Mono<?> getHandlerInternal(ServerWebExchange var1);
我们可以通过代码学习学习到如果所有的接口的实现类都需要在接口定义方法或者后执行一些共性操作,我们可以通过抽象类实现共性的操作,再通过定义抽象方法,让子类实现特有的实现。这种模式叫做模板模式。
RoutePredicateHandlerMapping是处理获取handler。RoutePredicateHandlerMapping中的RouteLocator存储gateway启动时加载的路由对象信息。获取路由的时候,调用RoutePredicateHandlerMapping的getHandlerInternal方法,从routeLocator获取路由存放在WebServerExchange中,返回Filter。
private final RouteLocator routeLocator;
@override
public Mono<?> getHandlerExternal(ServerWebExchange exchange){
exchange.getAttributes().put(GATEWAY_HANDLER_MAPPER_ATTR, getClass().getSimpleName());
return lookupRoute(exchange)
// .log("route-predicate-handler-mapping", Level.FINER) //name this
.flatMap((Function<Route, Mono<?>>) r -> {
exchange.getAttributes().remove(GATEWAY_PREDICATE_ROUTE_ATTR);
if (logger.isDebugEnabled()) {
logger.debug("Mapping [" + getExchangeDesc(exchange) + "] to " + r);
}
exchange.getAttributes().put(GATEWAY_ROUTE_ATTR, r);
return Mono.just(webHandler);
}).switchIfEmpty(Mono.empty().then(Mono.fromRunnable(() -> {
exchange.getAttributes().remove(GATEWAY_PREDICATE_ROUTE_ATTR);
if (logger.isTraceEnabled()) {
logger.trace("No RouteDefinition found for [" + getExchangeDesc(exchange) + "]");
}
})));
}
protected Mono<Route> lookupRoute(ServerWebExchange exchange) {
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());
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.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());
}*/
RoutePredicateHandlerMapping在创建的时候制定了WebHandler和RouteLocator实例。webhandler 封装了globalfilter而routelocator保存了所有的route对象。
routelocator主要作用是提供获取路由的类型。分析RoutePredicateHandlerMapping的时候,制动RoutePredicateHandlerMapping由routeLocator提供;下面分析一下RouteLocator加载路由。
最总的RouteLocator是CachingRoutingLocator ,加载过程是自上而下进行创建的。
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder){
RouteLocator locator=builder.routes()
.route("get",r->r.path("security/**").uri("lb://security-server"))
.builder();
}
spring:
cloud:
gateway:
routes:
- id: cookie_route
uri: http://example.org
predicates:
- Cookie=chocolate, ch.p
public class InMomeryRouteDefinitionRepository implements RouteDefintionRepository{
private final Map<String,RouteDefinition> routes=synchronizedMap(new LinkedHashMap<String, RouteDefinition>());
@Override
public Mono<Void> save(Mono<RouteDefinition> route) {
return route.flatMap( r -> {
routes.put(r.getId(), r);
return Mono.empty();
});
}
@Override
public Mono<Void> delete(Mono<String> routeId) {
return routeId.flatMap(id -> {
if (routes.containsKey(id)) {
routes.remove(id);
return Mono.empty();
}
return Mono.defer(() -> Mono.error(new NotFoundException("RouteDefinition not found: "+routeId)));
});
}
@Override
public Flux<RouteDefinition> getRouteDefinitions() {
return Flux.fromIterable(routes.values());
}
}
Filter分为全局Filter和RouteFilter
在转发过程中,最终由Proxy Filter进行请求Proxy Service的,而这个proxy filter就是nettyroutefilter。通过下面代码我们可以看到在
**proxyRequest.setHeaders().send(request.getBody().map(dataBuffer->((NettyBuffer)dataBuffer).getNativeBuffers()));**中请求Proxy Service。
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
URI requestUrl = exchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR);
String scheme = requestUrl.getScheme();
if (isAlreadyRouted(exchange) || (!"http".equals(scheme) && !"https".equals(scheme))) {
return chain.filter(exchange);
}
setAlreadyRouted(exchange);
ServerHttpRequest request = exchange.getRequest();
final HttpMethod method = HttpMethod.valueOf(request.getMethod().toString());
final String url = requestUrl.toString();
HttpHeaders filtered = filterRequest(this.headersFilters.getIfAvailable(),
exchange);
final DefaultHttpHeaders httpHeaders = new DefaultHttpHeaders();
filtered.forEach(httpHeaders::set);
String transferEncoding = request.getHeaders().getFirst(HttpHeaders.TRANSFER_ENCODING);
boolean chunkedTransfer = "chunked".equalsIgnoreCase(transferEncoding);
boolean preserveHost = exchange.getAttributeOrDefault(PRESERVE_HOST_HEADER_ATTRIBUTE, false);
Mono<HttpClientResponse> responseMono = this.httpClient.request(method, url, req -> {
final HttpClientRequest proxyRequest = req.options(NettyPipeline.SendOptions::flushOnEach)
.headers(httpHeaders)
.chunkedTransfer(chunkedTransfer)
.failOnServerError(false)
.failOnClientError(false);
if (preserveHost) {
String host = request.getHeaders().getFirst(HttpHeaders.HOST);
proxyRequest.header(HttpHeaders.HOST, host);
}
return proxyRequest.sendHeaders() //I shouldn't need this
.send(request.getBody().map(dataBuffer ->
((NettyDataBuffer) dataBuffer).getNativeBuffer()));
});
if (properties.getResponseTimeout() != null) {
responseMono.timeout(properties.getResponseTimeout(),
Mono.error(new TimeoutException("Response took longer than timeout: " +
properties.getResponseTimeout())));
}
return responseMono.doOnNext(res -> {
ServerHttpResponse response = exchange.getResponse();
// put headers and status so filters can modify the response
HttpHeaders headers = new HttpHeaders();
res.responseHeaders().forEach(entry -> headers.add(entry.getKey(), entry.getValue()));
if (headers.getContentType() != null) {
exchange.getAttributes().put(ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR, headers.getContentType());
}
HttpHeaders filteredResponseHeaders = HttpHeadersFilter.filter(
this.headersFilters.getIfAvailable(), headers, exchange, Type.RESPONSE);
response.getHeaders().putAll(filteredResponseHeaders);
HttpStatus status = HttpStatus.resolve(res.status().code());
if (status != null) {
response.setStatusCode(status);
} else if (response instanceof AbstractServerHttpResponse) {
// https://jira.spring.io/browse/SPR-16748
((AbstractServerHttpResponse) response).setStatusCodeValue(res.status().code());
} else {
throw new IllegalStateException("Unable to set status code on response: " +res.status().code()+", "+response.getClass());
}
// Defer committing the response until all route filters have run
// Put client response as ServerWebExchange attribute and write response later NettyWriteResponseFilter
exchange.getAttributes().put(CLIENT_RESPONSE_ATTR, res);
}).then(chain.filter(exchange));
}