当前位置: 首页 > news >正文

Gateway源码分析:路由Route、断言Predicate、Filter

文章目录

    • 源码总流程图
    • 说明
    • GateWayAutoConfiguration
    • DispatcherHandler
    • getHandler()
    • handleRequestWith()
      • RouteToRequestUrlFilter
      • ReactiveLoadBalancerClientFilter
      • NettyRoutingFilter
    • 补充知识
      • 适配器模式
    • 详细流程图

源码总流程图

在线总流程图
在这里插入图片描述



说明

Gateway的版本使用的是4.0.0

Gateway的实现是基于WebFlux 、Reactor 、Netty



Gateway微服务的yml配置如下

  • Gateway的访问端口为8888
  • id为order_route的路由 uri为lb://mall-order
  • 为mall-order这个微服务定义了一个path路径的predicate断言;定义了三个filter
server:port: 8888
spring:application:name: mall-gateway#配置nacos注册中心地址cloud:nacos:discovery:server-addr: nacos.mall.com:8848username: nacospassword: nacosgateway:#设置路由:路由id、路由到微服务的uri、断言routes:- id: user_route   #路由ID,全局唯一uri: lb://mall-user  #lb 整合负载均衡器ribbon,loadbalancerpredicates:- Path=/user/**   # 断言,路径相匹配的进行路由- id: order_route  #路由ID,全局唯一# 测试 http://localhost:8888/order/findOrderByUserId/1uri: lb://mall-order  #lb 整合负载均衡器loadbalancerpredicates:- Path=/order/**   # 断言,路径相匹配的进行路由#配置过滤器工厂filters:- AddRequestHeader=X-Request-color, red  #添加请求头- AddRequestParameter=color, blue  # 添加请求参数- CheckAuth=hushang,#自定义过滤器工厂



Gateway的工作原理就是下面这一张图

在这里插入图片描述



在开始看Gateway的源码之前,我们回忆一下SpringMVC的实现原理:

  1. DispatchServlet#doDispatch作为Springmvc的入口
  2. HandlerMapper 路由匹配 —> 找到Handler
  3. 通过handler 找的适配的 HandlerAdapter
  4. HandlerAdapter#handle方法执行



而在Gateway的源码中:

  • DispatcherHandler#handle作为入口
  • HandlerMapping 路由匹配 --> 断言predicate匹配 RoutePredicateHandlerMapping#getHandlerInternal,找到路由Route对象
  • 返回FilteringWebHandler
  • HandlerAdapter 适配器 SimpleHandlerAdapter#handle 处理WebHandler
  • 进入到org.springframework.cloud.gateway.handler.FilteringWebHandler#handle —> filterChain处理



Flux和Mono的概念

Reactor学习文档

Flux:

在这里插入图片描述



Mono:

在这里插入图片描述



GateWayAutoConfiguration

在这里插入图片描述



在GatewayAutoConfiguration自动配置类中,它配置了很多bean对象,常见的就比如:

// 保存我们配置文件中关于网关路由相关的所有配置
// GatewayProperties保存了List<RouteDefinition>
// 而RouteDefinition就是每一个路由对象,保存了id、uri、断言集合List<PredicateDefinition>、Filter集合List<FilterDefinition>
@Bean
public GatewayProperties gatewayProperties() {return new GatewayProperties();
}// Path路径匹配的断言工厂,断言相关的bean都是以RoutePredicateFactory结尾
@Bean
@ConditionalOnEnabledPredicate
public PathRoutePredicateFactory pathRoutePredicateFactory() {return new PathRoutePredicateFactory();
}// 添加请求头的Filter,一般都是以GatewayFilterFactory
@Bean
@ConditionalOnEnabledFilter
public AddRequestHeaderGatewayFilterFactory addRequestHeaderGatewayFilterFactory() {return new AddRequestHeaderGatewayFilterFactory();
}// 全局过滤器,把我们访问网关的url转换为路由中配置的uri
// http://localhost:8888/order/findOrderByUserId/1  --->  lb://mall-order/order/findOrderByUserId/1
@Bean
@ConditionalOnEnabledGlobalFilter
public RouteToRequestUrlFilter routeToRequestUrlFilter() {return new RouteToRequestUrlFilter();
}



GateWayAutoConfiguration配置的主要bean

类名说明
GatewayPropertiesgateway属性配置类
PropertiesRouteDefinitionLocator操作GatewayProperties对象,返回Flux<RouteDefinition>
RouteDefinitionRouteLocator将RouteDefinition转换为Route
RoutePredicateHandlerMappingGateway的HandlerMapping,匹配请求对应的Route,返回FilteringWebHandler
XXXRoutePredicateFactory路由断言工厂的bean
XXXGatewayFilterFactory局部Filter
GlobalFilter实现类全局Filter



DispatcherHandler

DispatcherHandler#handle作为我们查看Gateway源码的入口

  • 请求request和响应response实例会被封装为ServerWebExchange
  • 核心方法就是return语句
@Override
public Mono<Void> handle(ServerWebExchange exchange) {if (this.handlerMappings == null) {return createNotFoundError();}if (CorsUtils.isPreFlightRequest(exchange.getRequest())) {return handlePreFlight(exchange);}return Flux.fromIterable(this.handlerMappings) // 遍历所有的HandlerMapper.concatMap(mapping -> mapping.getHandler(exchange)) // 调用每一个HandlerMapper,能否找到Handler.next() // 继续遍历下一个HandlerMapper.switchIfEmpty(createNotFoundError()) // 如果HandlerMapper遍历完后都没有Handler,那么要抛异常了.onErrorResume(ex -> handleDispatchError(exchange, ex)).flatMap(handler -> handleRequestWith(exchange, handler)); // 如果找到Handler,那就去通过HandlerAdapter去调用Handler
}



fromIterable()方法的作用就是就是遍历Gateway所有的HandlerMapper,我们这里肯定最终是使用的RoutePredicateHandlerMapping这个路由断言的

在这里插入图片描述

我们接下来继续往下,遍历各个HandlerMapper,并调用mapping.getHandler(exchange)方法,这里最终会调用至RoutePredicateHandlerMapping类的getHandlerInternal()方法中,经过断言匹配后,返回一个FilteringWebHandler对象。该方法接下来会详细介绍。



中间这几行其实主要就是如果我当前往Gateway的请求,通过路由断言没有匹配上,那么就会抛异常

.next()
.switchIfEmpty(createNotFoundError())
.onErrorResume(ex -> handleDispatchError(exchange, ex))



经过路由断言匹配,得到一个WebHandler对象之后,会执行handleRequestWith(exchange, handler)方法,在该方法中会找一个与WebHandler匹配的HandlerAdapter来适配WebHandler对象,最终去调用WebHandler的



getHandler()

通过DispatcherHandler#handle方法中的.concatMap(mapping -> mapping.getHandler(exchange)) 这一行代码

进入到了AbstractHandlerMapping#getHandler

  • 遍历我们yml配置文件中所有定义的路由

  • 根据我们路由定义的断言Predicate规则去调用对应的断言工厂

  • 将匹配成功的路由保存至exchange对象中

    exchange.getAttributes().put(GATEWAY_ROUTE_ATTR, r);
    exchange.getAttributes().put(GATEWAY_PREDICATE_MATCHED_PATH_ROUTE_ID_ATTR, routeId);

  • 断言匹配成功就返回一个WebHandler接口的实现类FilteringWebHandler对象

public Mono<Object> getHandler(ServerWebExchange exchange) {// 从这里我们就可以发现,通过getHandlerInternal(exchange)方法就能找Handler,之后的.map()方法中就直接return handler;return getHandlerInternal(exchange).map(handler -> {if (logger.isDebugEnabled()) {logger.debug(exchange.getLogPrefix() + "Mapped to " + handler);}ServerHttpRequest request = exchange.getRequest();// 正常情况下这个if都不会进入if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {CorsConfiguration config = (this.corsConfigurationSource != null ?this.corsConfigurationSource.getCorsConfiguration(exchange) : null);CorsConfiguration handlerConfig = getCorsConfiguration(handler, exchange);config = (config != null ? config.combine(handlerConfig) : handlerConfig);if (config != null) {config.validateAllowCredentials();}if (!this.corsProcessor.process(config, exchange) || CorsUtils.isPreFlightRequest(request)) {return NO_OP_HANDLER;}}// 直接返回handlerreturn handler;});
}





我们这里就直接进入到RoutePredicateHandlerMapping类中

在这里插入图片描述

RoutePredicateHandlerMapping#getHandlerInternal的详细代码如下

protected Mono<?> getHandlerInternal(ServerWebExchange exchange) {if (this.managementPortType == DIFFERENT && this.managementPort != null&& exchange.getRequest().getURI().getPort() == this.managementPort) {return Mono.empty();}exchange.getAttributes().put(GATEWAY_HANDLER_MAPPER_ATTR, getSimpleName());return Mono.deferContextual(contextView -> {exchange.getAttributes().put(GATEWAY_REACTOR_CONTEXT_ATTR, contextView);// 核心方法是lookupRoute(exchange),这里会去进行路由的校验,根据我们配置文件中定义的路由断言规则进行校验return lookupRoute(exchange).flatMap((Function<Route, Mono<?>>) r -> {// 下面几行代码就是操作exchange的属性// 上方的lookupRoute()方法中会添加GATEWAY_PREDICATE_ROUTE_ATTR,这里就进行移除exchange.getAttributes().remove(GATEWAY_PREDICATE_ROUTE_ATTR);// 把当前路由对象添加进exchange对象中,之后的流程还会用到我们的路由对象exchange.getAttributes().put(GATEWAY_ROUTE_ATTR, r);// 路由匹配成功,就直接返回WebHandler对象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) + "]");}})));});
}





再进入到RoutePredicateHandlerMapping#lookupRoute方法中

protected Mono<Route> lookupRoute(ServerWebExchange exchange) {// 获取到我们yml配置文件中所有定义的路由,并进行遍历return this.routeLocator.getRoutes().concatMap(route -> Mono.just(route).filterWhen(r -> {// 添加GATEWAY_PREDICATE_ROUTE_ATTRexchange.getAttributes().put(GATEWAY_PREDICATE_ROUTE_ATTR, r.getId());// 调用当前路由对象中的断言的apply()方法,apply()方法中又是一些异步的处理流程// 这里就会根据我们配置文件中为路由配置的各个断言,去调用各个断言对象return r.getPredicate().apply(exchange);}).doOnError(e -> logger.error("Error applying predicate for route: " + route.getId(), e)).onErrorResume(e -> Mono.empty())).next()// TODO: error handling.map(route -> {if (logger.isDebugEnabled()) {logger.debug("Route matched: " + route.getId());}validateRoute(route, exchange);return route;});
}

我当前gateway的配置文件中定义了两个路由

spring:cloud:gateway:#设置路由:路由id、路由到微服务的uri、断言routes:- id: user_route   #路由ID,全局唯一uri: lb://mall-user  #lb 整合负载均衡器ribbon,loadbalancerpredicates:- Path=/user/**   # 断言,路径相匹配的进行路由- id: order_route  #路由ID,全局唯一# 测试 http://localhost:8888/order/findOrderByUserId/1uri: lb://mall-order  #lb 整合负载均衡器loadbalancerpredicates:- Path=/order/**   # 断言,路径相匹配的进行路由

所以上面的方法会执行两次

在这里插入图片描述

在这里插入图片描述

因为return r.getPredicate().apply(exchange);又是一些异步调用,但我们从方法名就能看出来,这里根据我们yml配置文件中路由定义的断言去调用对应的断言对象。这就是各个断言工厂具体的实现了,接下来就以Path路径匹配的断言工厂来举例

class DefaultAsyncPredicate<T> implements AsyncPredicate<T> {private final Predicate<T> delegate;@Overridepublic Publisher<Boolean> apply(T t) { // 调用各个Predicate对象的test()方法return Mono.just(delegate.test(t));}//...
}





所以我们现在就直接去看path路径匹配的断言类PathRoutePredicateFactory

@Override
public Predicate<ServerWebExchange> apply(Config config) {final ArrayList<PathPattern> pathPatterns = new ArrayList<>();synchronized (this.pathPatternParser) {pathPatternParser.setMatchOptionalTrailingSeparator(config.isMatchTrailingSlash());config.getPatterns().forEach(pattern -> {PathPattern pathPattern = this.pathPatternParser.parse(pattern);pathPatterns.add(pathPattern);});}return new GatewayPredicate() {// 会进入到test()方法中@Overridepublic boolean test(ServerWebExchange exchange) {// 当前请求的uri路径  /order/findOrderByUserId/1PathContainer path = parsePath(exchange.getRequest().getURI().getRawPath());PathPattern match = null;for (int i = 0; i < pathPatterns.size(); i++) {// yml配置文件中的配置项  /order/**PathPattern pathPattern = pathPatterns.get(i);// 如果path匹配成功,那么match对象就不为nullif (pathPattern.matches(path)) {match = pathPattern;break;}}// 如果path匹配成功,那么match对象就不为null  。匹配成功的处理逻辑if (match != null) {traceMatch("Pattern", match.getPatternString(), path, true);PathMatchInfo pathMatchInfo = match.matchAndExtract(path);putUriTemplateVariables(exchange, pathMatchInfo.getUriVariables());//  match.getPatternString() 为   /order/**exchange.getAttributes().put(GATEWAY_PREDICATE_MATCHED_PATH_ATTR, match.getPatternString());String routeId = (String) exchange.getAttributes().get(GATEWAY_PREDICATE_ROUTE_ATTR);// 保存当前路由idif (routeId != null) {exchange.getAttributes().put(GATEWAY_PREDICATE_MATCHED_PATH_ROUTE_ID_ATTR, routeId);}return true;}// path匹配不成,返回falseelse {traceMatch("Pattern", config.getPatterns(), path, false);return false;}}@Overridepublic Object getConfig() {return config;}@Overridepublic String toString() {return String.format("Paths: %s, match trailing slash: %b", config.getPatterns(),config.isMatchTrailingSlash());}};
}



handleRequestWith()

// DispatcherHandler#handleRequestWith
private Mono<Void> handleRequestWith(ServerWebExchange exchange, Object handler) {if (ObjectUtils.nullSafeEquals(exchange.getResponse().getStatusCode(), HttpStatus.FORBIDDEN)) {return Mono.empty();  // CORS rejection}if (this.handlerAdapters != null) {// 遍历所有的HandlerAdapterfor (HandlerAdapter adapter : this.handlerAdapters) {// 找能处理WebHandler类型的HandlerAdapter , 最终找到SimpleHandlerAdapterif (adapter.supports(handler)) {return adapter.handle(exchange, handler).flatMap(result -> handleResult(exchange, result));}}}return Mono.error(new IllegalStateException("No HandlerAdapter: " + handler));
}

如下图所示,找到SimpleHandlerAdapter这个

在这里插入图片描述



SimpleHandlerAdapter的详细代码如下所示

public class SimpleHandlerAdapter implements HandlerAdapter {@Overridepublic boolean supports(Object handler) {return WebHandler.class.isAssignableFrom(handler.getClass());}@Overridepublic Mono<HandlerResult> handle(ServerWebExchange exchange, Object handler) {// 强转WebHandler webHandler = (WebHandler) handler;// getHandler()方法返回了一个FilteringWebHandler对象,这里就调用它的handle()方法Mono<Void> mono = webHandler.handle(exchange);// 返回一个空对象return mono.then(Mono.empty());}}



FilteringWebHandler#handle方法

@Override
public Mono<Void> handle(ServerWebExchange exchange) {// getHandler()方法中存入了Route路由对象,这里取出来,该对象保存着我们yml配置文件中的配置Route route = exchange.getRequiredAttribute(GATEWAY_ROUTE_ATTR);// 取出我们yml配置文件中 该路由的局部Filter,我这里有三个,一个添加请求头、一个添加请求参数、一个自定义的List<GatewayFilter> gatewayFilters = route.getFilters();// 全局FilterList<GatewayFilter> combined = new ArrayList<>(this.globalFilters);// 将局部Filter和全局Filter存入一个集合中combined.addAll(gatewayFilters);// 排序AnnotationAwareOrderComparator.sort(combined);if (logger.isDebugEnabled()) {logger.debug("Sorted gatewayFilterFactories: " + combined);}// 调用各自的filter()方法return new DefaultGatewayFilterChain(combined).filter(exchange);
}



在这里插入图片描述

[GatewayFilterAdapter{RemoveCachedBodyFilter@5cfed0ba}, order = -2147483648]"
[GatewayFilterAdapter{AdaptCachedBodyGlobalFilter@22a6d75c}, order = -2147482648]"
[GatewayFilterAdapter{NettyWriteResponseFilter@691567ea}, order = -1]"
[GatewayFilterAdapter{ForwardPathFilter@28be7fec}, order = 0]"
// 三个局部Filter
[[AddRequestHeader X-Request-color = 'red'], order = 1]"
[[AddRequestParameter color = 'blue'], order = 2]"
[com.tuling.mall.gateway.filter.CheckAuthGatewayFilterFactory$$Lambda$980/0x0000014b3c649390@54f513cb, order = 3]"
// 路由转换 把http://localhost:8888/order/findOrderByUserId/1  --->  lb://mall-order/order/findOrderByUserId/1
[GatewayFilterAdapter{RouteToRequestUrlFilter@5c8d58ed}, order = 10000]"
// 根据lb://前缀过滤处理,使用serviceId选择一个服务实例,从而实现负载均衡
[GatewayFilterAdapter{ReactiveLoadBalancerClientFilter@437ed416}, order = 10150]"
[GatewayFilterAdapter{LoadBalancerServiceInstanceCookieFilter@11f23038}, order = 10151]"
[GatewayFilterAdapter{WebsocketRoutingFilter@26f0141}, order = 2147483646]"
// 发送netty 请求
[GatewayFilterAdapter{NettyRoutingFilter@de77146}, order = 2147483647]"
[GatewayFilterAdapter{ForwardRoutingFilter@6a567f7b}, order = 2147483647]"



接下来就挑几个重要的全局GlobalFilter来分析

RouteToRequestUrlFilter

路由转换 把http://localhost:8888/order/findOrderByUserId/1?color=blue —> lb://mall-order/order/findOrderByUserId/1?color=blue

@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {// 从exchange中取路由Route对象Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR);if (route == null) {return chain.filter(exchange);}log.trace("RouteToRequestUrlFilter start");// 取当前请求uri : http://localhost:8888/order/findOrderByUserId/1?color=blueURI uri = exchange.getRequest().getURI();boolean encoded = containsEncodedParts(uri);// 路由对象中保存的uri,也就是我们在yml文件中配置的值:   lb://mall-orderURI routeUri = route.getUri();if (hasAnotherScheme(routeUri)) {exchange.getAttributes().put(GATEWAY_SCHEME_PREFIX_ATTR, routeUri.getScheme());routeUri = URI.create(routeUri.getSchemeSpecificPart());}// 如果我们在yml文件中配置的uri,即不是lb开头并且host还为null,那么就抛异常if ("lb".equalsIgnoreCase(routeUri.getScheme()) && routeUri.getHost() == null) {throw new IllegalStateException("Invalid host: " + routeUri.toString());}// 转换结果为:  lb://mall-order/order/findOrderByUserId/1?color=blueURI mergedUrl = UriComponentsBuilder.fromUri(uri).scheme(routeUri.getScheme()).host(routeUri.getHost()).port(routeUri.getPort()).build(encoded).toUri();// 存入exchange中,之后的LoadBalancer全局Filter中会用到exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, mergedUrl);return chain.filter(exchange);
}



ReactiveLoadBalancerClientFilter

解析lb://服务名,去服务注册中心获取服务实例instance,并通过负载均衡算法选择一个具体的instance

public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {// 此时的url是这个样子:   lb://mall-order/order/findOrderByUserId/1?color=blueURI 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);}addOriginalRequestUrl(exchange, url);if (log.isTraceEnabled()) {log.trace(ReactiveLoadBalancerClientFilter.class.getSimpleName() + " url before: " + url);}// 再获取一遍:lb://mall-order/order/findOrderByUserId/1?color=blueURI requestUri = exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR);// 就是服务名:    mall-orderString serviceId = requestUri.getHost();Set<LoadBalancerLifecycle> supportedLifecycleProcessors = LoadBalancerLifecycleValidator.getSupportedLifecycleProcessors(clientFactory.getInstances(serviceId, LoadBalancerLifecycle.class),RequestDataContext.class, ResponseData.class, ServiceInstance.class);// 请求信息 封装成一个对象DefaultRequest<RequestDataContext> lbRequest = new DefaultRequest<>(new RequestDataContext(new RequestData(exchange.getRequest()), getHint(serviceId)));// 调用choose()方法return choose(lbRequest, serviceId, supportedLifecycleProcessors).doOnNext(response -> {// 服务注册中心的响应response,获取server实例对象ServiceInstance retrievedInstance = response.getServer();// 值为http://localhost:8888/order/findOrderByUserId/1?color=blueURI uri = exchange.getRequest().getURI();String overrideScheme = retrievedInstance.isSecure() ? "https" : "http";if (schemePrefix != null) {overrideScheme = url.getScheme();}// 服务实例DelegatingServiceInstance serviceInstance = new DelegatingServiceInstance(retrievedInstance,overrideScheme);// 最终通过上面的服务实例,修改之后的请求url为:http://192.168.236.173:8020/order/findOrderByUserId/1?color=blueURI requestUrl = reconstructURI(serviceInstance, uri);if (log.isTraceEnabled()) {log.trace("LoadBalancerClientFilter url chosen: " + requestUrl);}// 存入exchange对象中,之后netty发送请求会用到该urlexchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUrl);exchange.getAttributes().put(GATEWAY_LOADBALANCER_RESPONSE_ATTR, response);supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onStartRequest(lbRequest, response));}).then(chain.filter(exchange)).doOnError(throwable -> supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onComplete(new CompletionContext<ResponseData, ServiceInstance, RequestDataContext>(CompletionContext.Status.FAILED, throwable, lbRequest,exchange.getAttribute(GATEWAY_LOADBALANCER_RESPONSE_ATTR))))).doOnSuccess(aVoid -> supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onComplete(new CompletionContext<ResponseData, ServiceInstance, RequestDataContext>(CompletionContext.Status.SUCCESS, lbRequest,exchange.getAttribute(GATEWAY_LOADBALANCER_RESPONSE_ATTR),new ResponseData(exchange.getResponse(), new RequestData(exchange.getRequest()))))));
}// 进入到choose()方法中
private Mono<Response<ServiceInstance>> choose(Request<RequestDataContext> lbRequest, String serviceId,Set<LoadBalancerLifecycle> supportedLifecycleProcessors) {ReactorLoadBalancer<ServiceInstance> loadBalancer = this.clientFactory.getInstance(serviceId,ReactorServiceInstanceLoadBalancer.class);if (loadBalancer == null) {throw new NotFoundException("No loadbalancer available for " + serviceId);}supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onStart(lbRequest));// 调用ReactorLoadBalancer对象的choose()方法return loadBalancer.choose(lbRequest);
}



NettyRoutingFilter

发送netty请求

public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {// 经过负载均衡之后的请求url,具体的下游服务请求地址// http://192.168.236.173:8020/order/findOrderByUserId/1?color=blueURI requestUrl = exchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR);// scheme为httpString scheme = requestUrl.getScheme();if (isAlreadyRouted(exchange) || (!"http".equalsIgnoreCase(scheme) && !"https".equalsIgnoreCase(scheme))) {return chain.filter(exchange);}setAlreadyRouted(exchange);ServerHttpRequest request = exchange.getRequest();// GET 请求final HttpMethod method = HttpMethod.valueOf(request.getMethod().name());// url为 http://192.168.236.173:8020/order/findOrderByUserId/1?color=bluefinal String url = requestUrl.toASCIIString();// 请求头HttpHeaders filtered = filterRequest(getHeadersFilters(), exchange);final DefaultHttpHeaders httpHeaders = new DefaultHttpHeaders();filtered.forEach(httpHeaders::set);boolean preserveHost = exchange.getAttributeOrDefault(PRESERVE_HOST_HEADER_ATTRIBUTE, false);// 路由对象Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR);Flux<HttpClientResponse> responseFlux = getHttpClient(route, exchange).headers(headers -> {headers.add(httpHeaders);// Will either be set below, or later by Nettyheaders.remove(HttpHeaders.HOST);if (preserveHost) {String host = request.getHeaders().getFirst(HttpHeaders.HOST);headers.add(HttpHeaders.HOST, host);}// 发送请求}).request(method).uri(url).send((req, nettyOutbound) -> {if (log.isTraceEnabled()) {nettyOutbound.withConnection(connection -> log.trace(...);}return nettyOutbound.send(request.getBody().map(this::getByteBuf));}).responseConnection((res, connection) -> {// Defer committing the response until all route filters have run// Put client response as ServerWebExchange attribute and write// response later NettyWriteResponseFilterexchange.getAttributes().put(CLIENT_RESPONSE_ATTR, res);exchange.getAttributes().put(CLIENT_RESPONSE_CONN_ATTR, connection);ServerHttpResponse response = exchange.getResponse();// put headers and status so filters can modify the responseHttpHeaders headers = new HttpHeaders();res.responseHeaders().forEach(entry -> headers.add(entry.getKey(), entry.getValue()));String contentTypeValue = headers.getFirst(HttpHeaders.CONTENT_TYPE);if (StringUtils.hasLength(contentTypeValue)) {exchange.getAttributes().put(ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR, contentTypeValue);}setResponseStatus(res, response);// make sure headers filters run after setting status so it is// available in responseHttpHeaders filteredResponseHeaders = HttpHeadersFilter.filter(getHeadersFilters(), headers, exchange,Type.RESPONSE);if (!filteredResponseHeaders.containsKey(HttpHeaders.TRANSFER_ENCODING)&& filteredResponseHeaders.containsKey(HttpHeaders.CONTENT_LENGTH)) {// It is not valid to have both the transfer-encoding header and// the content-length header.// Remove the transfer-encoding header in the response if the// content-length header is present.response.getHeaders().remove(HttpHeaders.TRANSFER_ENCODING);}exchange.getAttributes().put(CLIENT_RESPONSE_HEADER_NAMES, filteredResponseHeaders.keySet());response.getHeaders().addAll(filteredResponseHeaders);return Mono.just(res);});Duration responseTimeout = getResponseTimeout(route);if (responseTimeout != null) {responseFlux = responseFlux.timeout(responseTimeout,Mono.error(new TimeoutException("Response took longer than timeout: " + responseTimeout))).onErrorMap(TimeoutException.class,th -> new ResponseStatusException(HttpStatus.GATEWAY_TIMEOUT, th.getMessage(), th));}return responseFlux.then(chain.filter(exchange));
}



补充知识

适配器模式

FilteringWebHandler#handle方法中,先获取路由的局部Filter,在创建一个集合存放全局Filter,在把局部Filter和全局Filter放在一起。这里就有一个问题:

局部Filter的类型是GatewayFilter,而全局Filter的类型是GlobalFilter,它们是怎么通过下面这种方式存放在一个集合中的嘞?

// 局部Filter
List<GatewayFilter> gatewayFilters = route.getFilters();
// 全局Filter
List<GatewayFilter> combined = new ArrayList<>(this.globalFilters);
// 将局部Filter和全局Filter存入一个集合中
combined.addAll(gatewayFilters);



这里就用到了适配器模式,具体实现步骤是:

  • 创建一个GatewayFilterAdapter类,实现 GatewayFilter接口
  • GatewayFilterAdapter类中定义一个GlobalFilter属性,构造方法中传GlobalFilter类型的对象赋值给该属性
  • 实现GatewayFilter接口的filter()方法,在filter()方法中调用全局Filter的filter()方法
private static class GatewayFilterAdapter implements GatewayFilter {private final GlobalFilter delegate;GatewayFilterAdapter(GlobalFilter delegate) {this.delegate = delegate;}@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {return this.delegate.filter(exchange, chain);}
}



遍历List<GlobalFilter> globalFilters全局Filter,分别存入GatewayFilterAdapter对象中

List<GatewayFilterAdapter> ----> List<GatewayFilter> globalFilters集合

@Override
public Mono<Void> handle(ServerWebExchange exchange) {Route route = exchange.getRequiredAttribute(GATEWAY_ROUTE_ATTR);// 局部FilterList<GatewayFilter> gatewayFilters = route.getFilters();// 全局Filter,这样就把全局GlobalFilter变为了GatewayFilter类型了List<GatewayFilter> combined = new ArrayList<>(this.globalFilters);// 存入一个集合中combined.addAll(gatewayFilters);AnnotationAwareOrderComparator.sort(combined);// 调用各自的filter()方法return new DefaultGatewayFilterChain(combined).filter(exchange);
}



详细流程图

在这里插入图片描述

相关文章:

Gateway源码分析:路由Route、断言Predicate、Filter

文章目录 源码总流程图说明GateWayAutoConfigurationDispatcherHandlergetHandler()handleRequestWith()RouteToRequestUrlFilterReactiveLoadBalancerClientFilterNettyRoutingFilter 补充知识适配器模式 详细流程图 源码总流程图 在线总流程图 说明 Gateway的版本使用的是…...

ARM体系结构和接口技术(十)按键中断实验①

一、按键中断实验 &#xff08;一&#xff09;分析按键电路图 &#xff08;二&#xff09;芯片手册 二、按键中断实验分析 注&#xff1a;NVIC----Cortx-M核GIC----Cortx-A核 &#xff08;一&#xff09;查看所有外设的总线以及寄存器基地址 注&#xff1a;GIC的总线是A7核的…...

PostgreSQL使用(二)——插入、更新、删除数据

说明&#xff1a;本文介绍PostgreSQL的DML语言&#xff1b; 插入数据 -- 1.全字段插入&#xff0c;字段名可以省略 insert into tb_student values (1, 张三, 1990-01-01, 88.88);-- 2.部分字段插入&#xff0c;字段名必须写全 insert into tb_student (id, name) values (2,…...

有关css的题目

css样式来源有哪些&#xff1f; 内联样式&#xff1a; <a style"color: red"> </a> 内部样式&#xff1a;<style></style> 外部样式&#xff1a;写在独立的 .css文件中的 浏览器的默认样式 display有哪些属性 none - 不展示 block - 块类型…...

【开源库】libodb库编译及使用

前言 本文介绍windows平台下libodb库的编译及使用。 文末提供libodb-2.4.0编译好的msvc2019_64版本&#xff0c;可直接跳转自取 ODB库学习相关 【开源库学习】libodb库学习&#xff08;一&#xff09; 【开源库学习】libodb库学习&#xff08;二&#xff09; 【开源库学习】…...

电力需求预测挑战赛笔记 Task3 #Datawhale AI 夏令营

上文&#xff1a; 电力需求预测挑战赛笔记 Task2 #Datawhale AI 夏令营-CSDN博客文章浏览阅读80次。【代码】电力需求预测挑战赛笔记 Task2。https://blog.csdn.net/qq_23311271/article/details/140360632 前面我们介绍了如何使用经验模型以及常见的lightgbm决策树模型来解决…...

Promise 详解(原理篇)

目录 什么是 Promise 实现一个 Promise Promise 的声明 解决基本状态 添加 then 方法 解决异步实现 解决链式调用 完成 resolvePromise 函数 解决其他问题 添加 catch 方法 添加 finally 方法 添加 resolve、reject、race、all 等方法 如何验证我们的 Promise 是否…...

动态内存经典笔试题分析

目录 1.题目一 2.题目二 3.题目三 4.题目四 1.题目一 #include<stdlib.h> #include<stdio.h> #include<string.h> void GetMemory(char* p) {p (char*)malloc(100); } void Test(void) {char* str NULL;GetMemory(str);strcpy(str, "hello world…...

JS设计模式(一)单例模式

注释很详细&#xff0c;直接上代码 本文建立在已有JS面向对象基础的前提下&#xff0c;若无&#xff0c;请移步以下博客先行了解 JS面向对象&#xff08;一&#xff09;类与对象写法 特点和用途&#xff1a; 全局访问点&#xff1a;通过单例模式可以在整个应用程序中访问同一个…...

uniapp动态计算并设置元素高度

<template><view><scroll-view id"sv-box" :scroll-y"true" :style"{height:navHeightpx}"></scroll-view><view id"btn-box"><button>取消</button><button>确认</button><…...

直播架构如何设计核心节点和边缘节点

在直播架构中&#xff0c;核心节点和边缘节点的分工及主要服务是确保直播服务稳定、高效和可扩展的关键。以下是对这些节点的详细描述&#xff1a; 核心节点 核心节点通常位于数据中心&#xff0c;负责处理直播的主要逻辑和数据处理。其主要服务包括&#xff1a; 直播管理后…...

自动驾驶-预测概览

通过生成一条路径来预测一个物体的行为&#xff0c;在每一个时间段内&#xff0c;为每一辆汽车重新计算预测他们新生成的路径&#xff0c;这些预测路径为规划阶段做出决策提供了必要信息 预测路径有实时性的要求&#xff0c;预测模块能够学习新的行为。我们可以使用多源的数据…...

基于PSO算法优化PID参数的一些问题

目录 前言 Q1&#xff1a;惯性权重ω如何设置比较好&#xff1f;学习因子C1和C2如何设置&#xff1f; Q2&#xff1a;迭代速度边界设定一定能够遍历&#xff08;/覆盖&#xff09;整个PID参数二维空间范围吗&#xff1f;还是说需要与迭代次数相关&#xff1f;迭代次数越高&a…...

什么是决策树?

1. 什么是决策树&#xff1f; 决策树&#xff08;Decision Tree&#xff09;是一种常用的机器学习算法&#xff0c;用于解决分类和回归问题。它通过构建树结构来表示决策过程&#xff0c;分支节点表示特征选择&#xff0c;叶节点表示类别或回归值。 2. 决策树的组成部分 决策…...

ASP 快速参考

ASP 快速参考 概述 ASP&#xff08;Active Server Pages&#xff09;是一种由微软开发的服务器端脚本环境&#xff0c;用于动态网页设计和开发。它允许开发者创建和运行动态交互性网页&#xff0c;如访问数据库、发送电子邮件等。ASP页面通常以.asp为文件扩展名&#xff0c;并…...

(二)原生js案例之数码时钟计时

原生js实现的数字时间上下切换显示时间的效果&#xff0c;有参考相关设计&#xff0c;思路比较难&#xff0c;代码其实很简单 效果 代码实现 必要的样式 <style>* {padding: 0;margin: 0;}.content{/* text-align: center; */display: flex;align-items: center;justif…...

[CSS] 浮动布局的深入理解与应用

文章目录 浮动的简介元素浮动后的特点解决浮动产生的影响浮动后的影响解决浮动产生的影响 浮动相关属性实际应用示例示例1&#xff1a;图片与文字环绕示例2&#xff1a;多列布局示例3&#xff1a;响应式布局 总结 浮动布局是CSS中一种非常强大的布局方式&#xff0c;最初设计用…...

Linux云计算 |【第一阶段】ENGINEER-DAY2

主要内容&#xff1a; 磁盘空间管理fdisk、parted工具、开机自动挂载、文件系统、交换空间 KVM虚拟化 实操前骤&#xff1a; 1&#xff09;添加一块硬盘&#xff08;磁盘&#xff09;&#xff0c;需要关机才能进行操作&#xff0c;点击左下角【添加硬件】 2&#xff09;选择2…...

9.11和9.9哪个大?

没问题 文心一言 通义千问...

学懂C语言(十二):C语言中的二进制原理及应用

目录 1. 二进制原理 1.1 什么是二进制&#xff1f; 1.2 如何在C语言中表示二进制&#xff1f; 2. 二进制的表示 2.1 二进制和其他进制的转换 2.2 C语言中的二进制表示 3. 二进制运算 3.1 位运算符 3.2 计算过程示例 4. 应用示例 4.1 使用位运算实现开关 5. 总结 C语…...

【根据当天日期输出明天的日期(需对闰年做判定)。】2022-5-15

缘由根据当天日期输出明天的日期(需对闰年做判定)。日期类型结构体如下&#xff1a; struct data{ int year; int month; int day;};-编程语言-CSDN问答 struct mdata{ int year; int month; int day; }mdata; int 天数(int year, int month) {switch (month){case 1: case 3:…...

可靠性+灵活性:电力载波技术在楼宇自控中的核心价值

可靠性灵活性&#xff1a;电力载波技术在楼宇自控中的核心价值 在智能楼宇的自动化控制中&#xff0c;电力载波技术&#xff08;PLC&#xff09;凭借其独特的优势&#xff0c;正成为构建高效、稳定、灵活系统的核心解决方案。它利用现有电力线路传输数据&#xff0c;无需额外布…...

MMaDA: Multimodal Large Diffusion Language Models

CODE &#xff1a; https://github.com/Gen-Verse/MMaDA Abstract 我们介绍了一种新型的多模态扩散基础模型MMaDA&#xff0c;它被设计用于在文本推理、多模态理解和文本到图像生成等不同领域实现卓越的性能。该方法的特点是三个关键创新:(i) MMaDA采用统一的扩散架构&#xf…...

【算法训练营Day07】字符串part1

文章目录 反转字符串反转字符串II替换数字 反转字符串 题目链接&#xff1a;344. 反转字符串 双指针法&#xff0c;两个指针的元素直接调转即可 class Solution {public void reverseString(char[] s) {int head 0;int end s.length - 1;while(head < end) {char temp …...

令牌桶 滑动窗口->限流 分布式信号量->限并发的原理 lua脚本分析介绍

文章目录 前言限流限制并发的实际理解限流令牌桶代码实现结果分析令牌桶lua的模拟实现原理总结&#xff1a; 滑动窗口代码实现结果分析lua脚本原理解析 限并发分布式信号量代码实现结果分析lua脚本实现原理 双注解去实现限流 并发结果分析&#xff1a; 实际业务去理解体会统一注…...

【HTML-16】深入理解HTML中的块元素与行内元素

HTML元素根据其显示特性可以分为两大类&#xff1a;块元素(Block-level Elements)和行内元素(Inline Elements)。理解这两者的区别对于构建良好的网页布局至关重要。本文将全面解析这两种元素的特性、区别以及实际应用场景。 1. 块元素(Block-level Elements) 1.1 基本特性 …...

【RockeMQ】第2节|RocketMQ快速实战以及核⼼概念详解(二)

升级Dledger高可用集群 一、主从架构的不足与Dledger的定位 主从架构缺陷 数据备份依赖Slave节点&#xff0c;但无自动故障转移能力&#xff0c;Master宕机后需人工切换&#xff0c;期间消息可能无法读取。Slave仅存储数据&#xff0c;无法主动升级为Master响应请求&#xff…...

IT供电系统绝缘监测及故障定位解决方案

随着新能源的快速发展&#xff0c;光伏电站、储能系统及充电设备已广泛应用于现代能源网络。在光伏领域&#xff0c;IT供电系统凭借其持续供电性好、安全性高等优势成为光伏首选&#xff0c;但在长期运行中&#xff0c;例如老化、潮湿、隐裂、机械损伤等问题会影响光伏板绝缘层…...

比较数据迁移后MySQL数据库和OceanBase数据仓库中的表

设计一个MySQL数据库和OceanBase数据仓库的表数据比较的详细程序流程,两张表是相同的结构,都有整型主键id字段,需要每次从数据库分批取得2000条数据,用于比较,比较操作的同时可以再取2000条数据,等上一次比较完成之后,开始比较,直到比较完所有的数据。比较操作需要比较…...

深入理解Optional:处理空指针异常

1. 使用Optional处理可能为空的集合 在Java开发中&#xff0c;集合判空是一个常见但容易出错的场景。传统方式虽然可行&#xff0c;但存在一些潜在问题&#xff1a; // 传统判空方式 if (!CollectionUtils.isEmpty(userInfoList)) {for (UserInfo userInfo : userInfoList) {…...