spring cloud gateway 作为新一代的微服务网关已经发布了一段时间,我从7月份开始使用到现在已经4个月了。但是我一直处于一种只会使用,原理一知半解的水平。我们小组作为公司微服务产品的实践者,我自己作为组中一员完成了spring cloud gateway的开发,也解决了很多棘手的问题,却对它的原理和启动流程一知半解,好几次就是因为不了解它的启动流程,导致开发受挫,进度缓慢。现在正值闲时,正好看一下相关的源码,理解他的启动流程。本文基于spring cloud Finchley.RELEASE版本,最新的SR2版本一些内容有改变,但总体改变不大。
首先是网关的包结构
其中actuate中定义了一个叫GatewayControllerEndpoint的类,这个类提供一些对外的接口,可以获取网关的一些信息,比如路由的信息,改变路由地址等等。
config中定义了一些启动时去加载的类,配置路由信息和读取你的配置文件就在这里完成。
discovery中定义了注册中心相关的内容,包括注册中心的路由等。
event定义了一些事件他们都继承自ApplicationEvent,对事件发布不了解的可以去看看spring的代码。
filter中定义了spring cloud gateway实现的一些过滤器。
handler中定义了很多Predicate相关的Factory
route就是我们路由的相关
support是工具包等。
下面就是gateway启动流程的分析了
config包
网关启动第一步加载的就是去加载config包下的几个类。
这几个类就定义了网关需要加载的配置项。
在我第一次做网关开发的时候我引入了spring-boot-starter-web的依赖,这样是会报错的,因为gateway是基于spring-webflux开发的,他依赖的DispatcherHandler就和我们web里的DispatcherServlet一样的功能。
这里的GatewayClassPathWarningAutoConfiguration这个类,就指明了我们需要什么不需要什么,他加载于GatewayAutoConfiguration之前,如果DispatcherServlet存在,就会给与警告,同样的DispatcherHandler不存在也会警告。
@Configuration
@AutoConfigureBefore(GatewayAutoConfiguration.class)
public class GatewayClassPathWarningAutoConfiguration {
private static final Log log = LogFactory.getLog(GatewayClassPathWarningAutoConfiguration.class);
private static final String BORDER = "\n\n**********************************************************\n\n";
@Configuration
@ConditionalOnClass(name = "org.springframework.web.servlet.DispatcherServlet")
protected static class SpringMvcFoundOnClasspathConfiguration {
public SpringMvcFoundOnClasspathConfiguration() {
log.warn(BORDER+"Spring MVC found on classpath, which is incompatible with Spring Cloud Gateway at this time. "+
"Please remove spring-boot-starter-web dependency."+BORDER);
}
}
@Configuration
@ConditionalOnMissingClass("org.springframework.web.reactive.DispatcherHandler")
protected static class WebfluxMissingFromClasspathConfiguration {
public WebfluxMissingFromClasspathConfiguration() {
log.warn(BORDER+"Spring Webflux is missing from the classpath, which is required for Spring Cloud Gateway at this time. "+
"Please add spring-boot-starter-webflux dependency."+BORDER);
}
}
}
它加载完成之后加载的是GatewayLoadBalancerClientAutoConfiguration这个是gateway负载均衡的过滤器实现的加载,他将LoadBalancerClientFilter 注入到了容器中,这个过滤器后面再说。
@Configuration
@ConditionalOnClass({LoadBalancerClient.class, RibbonAutoConfiguration.class, DispatcherHandler.class})
@AutoConfigureAfter(RibbonAutoConfiguration.class)
public class GatewayLoadBalancerClientAutoConfiguration {
// GlobalFilter beans
@Bean
@ConditionalOnBean(LoadBalancerClient.class)
public LoadBalancerClientFilter loadBalancerClientFilter(LoadBalancerClient client) {
return new LoadBalancerClientFilter(client);
}
}
之后便是我们的GatewayAutoConfiguration正式加载了,这个里面定义了非常多的内容,我们大部分用到的过滤器,过滤器工厂都是在这里构建的。包括之前的gatewayControllerEndpoint也是在这里注入容器中的。这个类的定义很长,我就不再这里都放了,列举几个。
@Configuration
//开启网关,不写默认为true
@ConditionalOnProperty(name = "spring.cloud.gateway.enabled", matchIfMissing = true)
@EnableConfigurationProperties
@AutoConfigureBefore(HttpHandlerAutoConfiguration.class)
@AutoConfigureAfter({GatewayLoadBalancerClientAutoConfiguration.class, GatewayClassPathWarningAutoConfiguration.class})
@ConditionalOnClass(DispatcherHandler.class)
public class GatewayAutoConfiguration {
@Configuration
@ConditionalOnClass(HttpClient.class)
protected static class NettyConfiguration {
@Bean
@ConditionalOnMissingBean
public HttpClient httpClient(@Qualifier("nettyClientOptions") Consumer<? super HttpClientOptions.Builder> options) {
return HttpClient.create(options);
}
... //还有很多 这里不列举了。我们都知道spring cloud 是基于Netty实现的,这里他这个静态内部类就是初始化netty需要的东西。
}
//初始化了加载配置文件的对象,建立route
@Bean
public GatewayProperties gatewayProperties() {
return new GatewayProperties();
}
//初始化请求转发 过滤器
@Bean
@ConditionalOnProperty(name = "spring.cloud.gateway.forwarded.enabled", matchIfMissing = true)
public ForwardedHeadersFilter forwardedHeadersFilter() {
return new ForwardedHeadersFilter();
}
//最后初始化gatewayControllerEndpoint 这里注意只有引入spring-boot-starter-actuator他才会加载
@Configuration
@ConditionalOnClass(Health.class)
protected static class GatewayActuatorConfiguration {
@Bean
@ConditionalOnEnabledEndpoint
public GatewayControllerEndpoint gatewayControllerEndpoint(RouteDefinitionLocator routeDefinitionLocator, List<GlobalFilter> globalFilters,
List<GatewayFilterFactory> GatewayFilters, RouteDefinitionWriter routeDefinitionWriter,
RouteLocator routeLocator) {
return new GatewayControllerEndpoint(routeDefinitionLocator, globalFilters, GatewayFilters, routeDefinitionWriter, routeLocator);
}
}
这里注意我们通过注册中心发现的路由不是在config包下定义的而是在discovery包下GatewayDiscoveryClientAutoConfiguration实现了从注册中心发现内容
@Configuration
@ConditionalOnProperty(name = "spring.cloud.gateway.enabled", matchIfMissing = true)
@AutoConfigureBefore(GatewayAutoConfiguration.class)
@ConditionalOnClass({DispatcherHandler.class, DiscoveryClient.class})
@EnableConfigurationProperties
public class GatewayDiscoveryClientAutoConfiguration {
@Bean
@ConditionalOnBean(DiscoveryClient.class)
@ConditionalOnProperty(name = "spring.cloud.gateway.discovery.locator.enabled")
public DiscoveryClientRouteDefinitionLocator discoveryClientRouteDefinitionLocator(
DiscoveryClient discoveryClient, DiscoveryLocatorProperties properties) {
return new DiscoveryClientRouteDefinitionLocator(discoveryClient, properties);
}
@Bean public DiscoveryLocatorProperties discoveryLocatorProperties() {
DiscoveryLocatorProperties properties = new DiscoveryLocatorProperties();
properties.setPredicates(initPredicates());
properties.setFilters(initFilters());
return properties;
}
}
这些注册完毕后,我们的配置文件就开始读取了,
这两个中定义了我们配置文件的读取规则,其中DiscoveryLocatorProperties都有默认的值,我们可以不用关心。GatewayProperties比较重要
@ConfigurationProperties("spring.cloud.gateway")
@Validated
public class GatewayProperties {
@NotNull
@Valid
private List<RouteDefinition> routes = new ArrayList<>();
/**
* List of filter definitions that are applied to every route.
*/
private List<FilterDefinition> defaultFilters = new ArrayList<>();
private List<MediaType> streamingMediaTypes = Arrays.asList(MediaType.TEXT_EVENT_STREAM,
MediaType.APPLICATION_STREAM_JSON);
}
这里就定义了我们配置文件里的所有路由信息,读取完成后,接下来就要变成真正的路由信息了。
这里就要说一下 RouteLocator接口,这个接口提供了一个getRoutes()方法返回一个Flux<Route>所以,他的实现中就定义了读取配置文件转成路由的关键。
public interface RouteLocator {
Flux<Route> getRoutes();
}
我们先看从配置文件中加载的路由信息也就是他的实现RouteDefinitionRouteLocator
public class RouteDefinitionRouteLocator implements RouteLocator, BeanFactoryAware, ApplicationEventPublisherAware {
protected final Log logger = LogFactory.getLog(getClass());
private final RouteDefinitionLocator routeDefinitionLocator;
private final Map<String, RoutePredicateFactory> predicates = new LinkedHashMap<>();
private final Map<String, GatewayFilterFactory> gatewayFilterFactories = new HashMap<>();
private final GatewayProperties gatewayProperties;
private final SpelExpressionParser parser = new SpelExpressionParser();
private BeanFactory beanFactory;
private ApplicationEventPublisher publisher;
public RouteDefinitionRouteLocator(RouteDefinitionLocator routeDefinitionLocator,
List<RoutePredicateFactory> predicates,
List<GatewayFilterFactory> gatewayFilterFactories,
GatewayProperties gatewayProperties) {
this.routeDefinitionLocator = routeDefinitionLocator;
initFactories(predicates);
gatewayFilterFactories.forEach(factory -> this.gatewayFilterFactories.put(factory.name(), factory));
this.gatewayProperties = gatewayProperties;
}
...
}
我们定位到他的构造方法,debug启动
发现这个类的一个属性routeDefinitionLocator中已经定义了我们的路由,只不过是代理对象。 这个属性所对应的接口RouteDefinitionLocator有很多种实现
我们现在看PropertiesRouteDefinitionLocator,这里
public class PropertiesRouteDefinitionLocator implements RouteDefinitionLocator {
private final GatewayProperties properties;
public PropertiesRouteDefinitionLocator(GatewayProperties properties) {
this.properties = properties;
}
@Override
public Flux<RouteDefinition> getRouteDefinitions() {
return Flux.fromIterable(this.properties.getRoutes());
}
}
他的构造方法读取了GatewayProperties ,所以到这里我们的路由就已经存在了。通过他的getRoutes()方法,我们就能方便的取出所有定义的路由信息了
而他的getRoutes()方法又是这样定义的
@Override
public Flux<Route> getRoutes() {
return this.routeDefinitionLocator.getRouteDefinitions()
.map(this::convertToRoute)
//TODO: error handling
.map(route -> {
if (logger.isDebugEnabled()) {
logger.debug("RouteDefinition matched: " + route.getId());
}
return route;
});
}
routeDefinitionLocator.getRouteDefinitions()返回从配置文件中读取的路由转换成 Flux<RouteDefinition> 再通过convertToRoute转换成路由对象的数组,封装成Flux()至此,配置文件中读取路由信息就结束了。
接下来我们来解释通过注册中心的方式接受的路由信息。这里由一个叫DiscoveryClientRouteDefinitionLocator的类来实现,他同样实现了RouteDefinitionLocator接口,能够返回Flux<RouteDefinition>
public class DiscoveryClientRouteDefinitionLocator implements RouteDefinitionLocator {
private final DiscoveryClient discoveryClient;
private final DiscoveryLocatorProperties properties;
private final String routeIdPrefix;
public DiscoveryClientRouteDefinitionLocator(DiscoveryClient discoveryClient, DiscoveryLocatorProperties properties) {
// 服务发现信息
this.discoveryClient = discoveryClient;
//这个配置是前面说过的DiscoveryLocatorProperties
this.properties = properties;
if (StringUtils.hasText(properties.getRouteIdPrefix())) {
this.routeIdPrefix = properties.getRouteIdPrefix();
} else {
this.routeIdPrefix = this.discoveryClient.getClass().getSimpleName() + "_";
}
}
//这个是核心
@Override
public Flux<RouteDefinition> getRouteDefinitions() {
SimpleEvaluationContext evalCtxt = SimpleEvaluationContext
.forReadOnlyDataBinding()
.withInstanceMethods()
.build();
SpelExpressionParser parser = new SpelExpressionParser();
Expression includeExpr = parser.parseExpression(properties.getIncludeExpression());
Expression urlExpr = parser.parseExpression(properties.getUrlExpression());
return Flux.fromIterable(discoveryClient.getServices())
.map(discoveryClient::getInstances)
.filter(instances -> !instances.isEmpty())
.map(instances -> instances.get(0))
.filter(instance -> {
Boolean include = includeExpr.getValue(evalCtxt, instance, Boolean.class);
if (include == null) {
return false;
}
return include;
})
.map(instance -> {
String serviceId = instance.getServiceId();
RouteDefinition routeDefinition = new RouteDefinition();
routeDefinition.setId(this.routeIdPrefix + serviceId);
String uri = urlExpr.getValue(evalCtxt, instance, String.class);
routeDefinition.setUri(URI.create(uri));
final ServiceInstance instanceForEval = new DelegatingServiceInstance(instance, properties);
for (PredicateDefinition original : this.properties.getPredicates()) {
PredicateDefinition predicate = new PredicateDefinition();
predicate.setName(original.getName());
for (Map.Entry<String, String> entry : original.getArgs().entrySet()) {
String value = getValueFromExpr(evalCtxt, parser, instanceForEval, entry);
predicate.addArg(entry.getKey(), value);
}
routeDefinition.getPredicates().add(predicate);
}
for (FilterDefinition original : this.properties.getFilters()) {
FilterDefinition filter = new FilterDefinition();
filter.setName(original.getName());
for (Map.Entry<String, String> entry : original.getArgs().entrySet()) {
String value = getValueFromExpr(evalCtxt, parser, instanceForEval, entry);
filter.addArg(entry.getKey(), value);
}
routeDefinition.getFilters().add(filter);
}
return routeDefinition;
});
}
当我们调用到getRouteDefinitions的时候,他通过discoveryClient转换出了服务发现中心的服务路由
注意这里他没有被转换为Flux<Route>而是保留为Flux<RouteDefinition> 这是一个坑,我们前期很多时候去找Route发现根本就没有从服务注册中心拉下来的,现在才知道,他压根就没去转。
到这里,我们的路由信息就加载完毕了,网关也就启动完成了,之后就是发送请求过滤器发功的时候了。
我们第一个关注的类就是DispatcherHandler,这个类提供的handle()方法,封装了我们之后所有的handlerMappings
public class DispatcherHandler implements WebHandler, ApplicationContextAware {
...
@Override
public Mono<Void> handle(ServerWebExchange exchange) {
if (logger.isDebugEnabled()) {
ServerHttpRequest request = exchange.getRequest();
logger.debug("Processing " + request.getMethodValue() + " request for [" + request.getURI() + "]");
}
if (this.handlerMappings == null) {
return Mono.error(HANDLER_NOT_FOUND_EXCEPTION);
}
return Flux.fromIterable(this.handlerMappings)
.concatMap(mapping -> mapping.getHandler(exchange))
.next()
.switchIfEmpty(Mono.error(HANDLER_NOT_FOUND_EXCEPTION))
.flatMap(handler -> invokeHandler(exchange, handler))
.flatMap(result -> handleResult(exchange, result));
}
...
}
这里会真正的调用我们前面注册的route 的那些 getRouteDefinitions()方法,具体的我也没看明白,大概就是封装了这些Mapping进入之后的过滤链中
之后就到了FilteringWebHandler类,他的handler方法封装了过滤器进去
@Override
public Mono<Void> handle(ServerWebExchange exchange) {
//h获取到当前访问的路由对象
Route route = exchange.getRequiredAttribute(GATEWAY_ROUTE_ATTR);
//获取当前已经存在的 过滤器 也就是配置的默认过滤器
List<GatewayFilter> gatewayFilters = route.getFilters();
//获取全局过滤器
List<GatewayFilter> combined = new ArrayList<>(this.globalFilters);
combined.addAll(gatewayFilters);
//按order排序
AnnotationAwareOrderComparator.sort(combined);
logger.debug("Sorted gatewayFilterFactories: "+ combined);
return new DefaultGatewayFilterChain(combined).filter(exchange);
}
这里的全局过滤器包括除了我标记的两个以外的所有过滤器,之后再介绍
最后按照order排序后的过滤器顺序为
之后就真正的开始进入过滤链了 排除掉我们自己实现的过滤器 第一个进入的是AdaptCachedBodyGlobalFilter
他用来缓存我们的requestBody
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
Flux<DataBuffer> body = exchange.getAttributeOrDefault(CACHED_REQUEST_BODY_KEY, null);
if (body != null) {
//封装 ServerHttpRequest 使得requestBody 能够复写
ServerHttpRequestDecorator decorator = new ServerHttpRequestDecorator(exchange.getRequest()) {
@Override
public Flux<DataBuffer> getBody() {
return body;
}
};
return chain.filter(exchange.mutate().request(decorator).build());
}
return chain.filter(exchange);
}
然后进入的是NettyWriteResponseFilter 这个过滤器实现的是返回response 也就是请求完成后返回的对象。他这里巧妙的使用then()
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// NOTICE: nothing in "pre" filter stage as CLIENT_RESPONSE_ATTR is not added
// then 当filter完成后执行,也就是过滤链执行完毕,返回结果的时候
return chain.filter(exchange).then(Mono.defer(() -> {
HttpClientResponse clientResponse = exchange.getAttribute(CLIENT_RESPONSE_ATTR);
if (clientResponse == null) {
return Mono.empty();
}
log.trace("NettyWriteResponseFilter start");
ServerHttpResponse response = exchange.getResponse();
NettyDataBufferFactory factory = (NettyDataBufferFactory) response.bufferFactory();
//TODO: what if it's not netty
final Flux<NettyDataBuffer> body = clientResponse.receive()
.retain() //TODO: needed?
.map(factory::wrap);
MediaType contentType = response.getHeaders().getContentType();
return (isStreamingMediaType(contentType) ?
response.writeAndFlushWith(body.map(Flux::just)) : response.writeWith(body));
}));
}
后面就是返回结果了 没什么可说的。之后的过滤器我就不一个一个说了,讲讲比较重要的。
RouteToRequestUrlFilter我在注释上标明了,当前uri的值
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR);
if (route == null) {
return chain.filter(exchange);
}
log.trace("RouteToRequestUrlFilter start");\
// http://localhost:8804/cydOrganization/getOrgTreeList
URI uri = exchange.getRequest().getURI();
boolean encoded = containsEncodedParts(uri);
// lb://BASE-API-WEB
URI routeUri = route.getUri();
if (hasAnotherScheme(routeUri)) {
// this is a special url, save scheme to special attribute
// replace routeUri with schemeSpecificPart
exchange.getAttributes().put(GATEWAY_SCHEME_PREFIX_ATTR, routeUri.getScheme());
routeUri = URI.create(routeUri.getSchemeSpecificPart());
}
// lb://BASE-API-WEB:8804/cydOrganization/getOrgTreeList
URI requestUrl = UriComponentsBuilder.fromUri(uri)
.uri(routeUri)
.build(encoded)
.toUri();
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUrl);
return chain.filter(exchange);
}
最后他将url拼装成了我们需要的,能够做服务发现的url,这时他就会进入LoadBalancerClientFilter 同样我标注url的变化
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// lb://BASE-API-WEB:8804/cydOrganization/getOrgTreeList 刚才我们封装完毕的url
URI url = exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR);
String schemePrefix = exchange.getAttribute(GATEWAY_SCHEME_PREFIX_ATTR);
if (url == null || (!"lb".equals(url.getScheme()) && !"lb".equals(schemePrefix))) {
return chain.filter(exchange);
}
//preserve the original url
addOriginalRequestUrl(exchange, url);
log.trace("LoadBalancerClientFilter url before: " + url);
// RibbonServer{serviceId='BASE-API-WEB', server=192.168.47.1:12993, secure=false, metadata={}}
final ServiceInstance instance = loadBalancer.choose(url.getHost());
if (instance == null) {
throw new NotFoundException("Unable to find instance for " + url.getHost());
}
// http://localhost:8804/cydOrganization/getOrgTreeList
URI uri = exchange.getRequest().getURI();
// if the `lb:<scheme>` mechanism was used, use `<scheme>` as the default,
// if the loadbalancer doesn't provide one.
String overrideScheme = null;
if (schemePrefix != null) {
overrideScheme = url.getScheme();
}
// http://192.168.47.1:12993/cydOrganization/getOrgTreeList 到这时 这个地址已经是正确的访问地址了。
URI requestUrl = loadBalancer.reconstructURI(new DelegatingServiceInstance(instance, overrideScheme), uri);
log.trace("LoadBalancerClientFilter url chosen: " + requestUrl);
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUrl);
return chain.filter(exchange);
}
最后进入NettyRoutingFilter 这个filter真正做请求的发送,他使用HttpClient进行请求的发送。这个类不是很长 我就都拷过来
public class NettyRoutingFilter implements GlobalFilter, Ordered {
private final HttpClient httpClient;
private final ObjectProvider<List<HttpHeadersFilter>> headersFilters;
public NettyRoutingFilter(HttpClient httpClient,
ObjectProvider<List<HttpHeadersFilter>> headersFilters) {
this.httpClient = httpClient;
this.headersFilters = headersFilters;
}
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// http://192.168.47.1:12993/cydOrganization/getOrgTreeList 我们刚才处理好的url
URI requestUrl = exchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR);
String scheme = requestUrl.getScheme();
//必须是 http 或者 https 和没有被路由
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);
//到这里发送http请求
return 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()));
}).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()));
// 这里可能导致空指针 是我用的版本的bug SR2版本已经解决
exchange.getAttributes().put("original_response_content_type", 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));
}
}
当这步执行完之后,可以理解为先前的NettyWriteResponseFilter 的then()开始执行,最终返回请求结果。至此spring cloud gateway 的启动流程和访问流程就结束了。