Spring Cloud Gateway源码解析-07-过滤器解析之GlobalFilter

927 阅读9分钟

WebClientHttpRoutingFilter前言

Spring Cloud Gateway源码解析-05-请求处理之FilteringWebHandler中已经讲解过在获取过在DispatcherHandler通过RoutePredicateHandlerMapping获取到FilteringWebHandler后会调用FilteringWebHandlerhandle方法。并且FilteringWebHandler中已经组合了当前路由的Filter和SCG全局的Filter。

DefaultGatewayFilterChain

FilteringWebHandler#handle方法中会创建DefaultGatewayFilterChain去链式的执行所有的Filter。

private static class DefaultGatewayFilterChain implements GatewayFilterChain {

		private final int index;

		private final List<GatewayFilter> filters;

		DefaultGatewayFilterChain(List<GatewayFilter> filters) {
			this.filters = filters;
			//在FilteringWebHandler的handle方法中初始化时设置当前应调用的过滤器下标为0,也就是第一个
			this.index = 0;
		}
		//在filter方法中调用,传入过滤器链和需要执行的过滤器index
		private DefaultGatewayFilterChain(DefaultGatewayFilterChain parent, int index) {
			this.filters = parent.getFilters();
			this.index = index;
		}

		public List<GatewayFilter> getFilters() {
			return filters;
		}

		@Override
		public Mono<Void> filter(ServerWebExchange exchange) {
			return Mono.defer(() -> {
				//判断是否已经执行过所有的过滤器
				if (this.index < filters.size()) {
					//取出当前需执行的过滤器
					GatewayFilter filter = filters.get(this.index);
					//每个Filter都创建一个DefaultGatewayFilterChain去执行
					DefaultGatewayFilterChain chain = new DefaultGatewayFilterChain(this,
							this.index + 1);
					return filter.filter(exchange, chain);
				} else {
					return Mono.empty(); // complete
				}
			});
		}
	}

Filter

SCG内置了许多Filter,分为GatewayFilter和GlobalFilter,GatewayFilter和Predicate类似,都是通过Factory创建的,通过配置作用于每个路由,而GlobalFilter是作用于所有的请求。当请求匹配到对应路由的时候,会将GlobalFilter和路由绑定的GatewayFilter合并到一起,所有的Filter都实现了org.springframework.core.Ordered接口,因此会根据Filtr的order进行排序。Filter中包括了真正发送请求的Filter,因此又可分为前置Filter和后置Filter。

补充

GatewayFilterFactory并没有实现Ordered接口,其实在RouteDefinitionRouteLocator组装Filter的时候进行了封装OrderedGatewayFilter,OrderedGatewayFilter实现了Ordered接口,会在初始化时设置order。

List<GatewayFilter> loadGatewayFilters(String id,
										   List<FilterDefinition> filterDefinitions) {	
		//生成GatewayFilter
		GatewayFilter gatewayFilter = factory.apply(configuration);
		if (gatewayFilter instanceof Ordered) {
			ordered.add(gatewayFilter);
		} else {
			//如果没有实现Ordered接口,则根据遍历的顺序排序
			ordered.add(new OrderedGatewayFilter(gatewayFilter, i + 1));
		}
}

public class OrderedGatewayFilter implements GatewayFilter, Ordered {

	private final GatewayFilter delegate;

	private final int order;

	public OrderedGatewayFilter(GatewayFilter delegate, int order) {
		this.delegate = delegate;
		this.order = order;
	}
}

在这里插入图片描述

GlobalFilter

通过下边这张图可以看到所有的GlobalFilter及执行顺序。
在这里插入图片描述

GlobalFilterorder值
RemoveCachedBodyFilterInteger.MIN_VALUE;
AdaptCachedBodyGlobalFilterInteger.MIN_VALUE + 1000
NettyWriteResponseFilter-1
ForwardPathFilter0
GatewayMetricsFilter0
RouteToRequestUrlFilter1000
LoadBalancerClientFilter10100
WebsocketRoutingFilterInteger.MAX_VALUE -1
NettyRoutingFilterInteger.MAX_VALUE
ForwardRoutingFilterInteger.MAX_VALUE

当order值相同时,根据加载的先后顺序执行。NettyRoutingFilter先于ForwardRoutingFilter加载。

RemoveCachedBodyFilter

清除上下文中的body缓存。是配合AdaptCachedBodyGlobalFilter使用的,AdaptCachedBodyGlobalFilter会将请求body放入缓存,等所有的filter执行完后再从上下文中将body缓存删除。

public class RemoveCachedBodyFilter implements GlobalFilter, Ordered {

	@Override
	public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

		//调用下一个filter
		return chain.filter(exchange).doFinally(s -> {//doFinally表示最终执行的操作
			//清除缓存
			Object attribute = exchange.getAttributes().remove(CACHED_REQUEST_BODY_ATTR);
			if (attribute != null && attribute instanceof PooledDataBuffer) {
				PooledDataBuffer dataBuffer = (PooledDataBuffer) attribute;
				if (dataBuffer.isAllocated()) {
					if (log.isTraceEnabled()) {
						log.trace("releasing cached body in exchange attribute");
					}
					dataBuffer.release();
				}
			}
		});
	}

}

AdaptCachedBodyGlobalFilter

用来添加缓存的Filter,具体看下面的代码注释即可。

public class AdaptCachedBodyGlobalFilter
		implements GlobalFilter, Ordered, ApplicationListener<EnableBodyCachingEvent> {
      
   /**
	 * 当我们配置了RetryGatewayFilterFactory重试时,会在执行重试逻辑时发布EnableBodyCachingEvent,此处会监听到该事件
	 * @param event
	 */
	@Override
	public void onApplicationEvent(EnableBodyCachingEvent event) {
		this.routesToCache.putIfAbsent(event.getRouteId(), true);
	}

	@Override
	public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
		// the cached ServerHttpRequest is used when the ServerWebExchange can not be
		// mutated, for example, during a predicate where the body is read, but still
		// needs to be cached.
		//从上下文获取CACHED_SERVER_HTTP_REQUEST_DECORATOR_ATTR,CACHED_SERVER_HTTP_REQUEST_DECORATOR_ATTR通过名称可以看出来就是请求的封装
		//当使用了ReadBodyRoutePredicateFactory时,test时会将请求body放入上下文中,此处的缓存主要是为了后边不用再序列化
		//如果不为空,表示请求和请求体都已经缓存了,则通过缓存的request构建一个上下文请求
		ServerHttpRequest cachedRequest = exchange
				.getAttributeOrDefault(CACHED_SERVER_HTTP_REQUEST_DECORATOR_ATTR, null);
		if (cachedRequest != null) {
			exchange.getAttributes().remove(CACHED_SERVER_HTTP_REQUEST_DECORATOR_ATTR);
			return chain.filter(exchange.mutate().request(cachedRequest).build());
		}

		//如果上边没有从上下文中获取到缓存,则获取CACHED_REQUEST_BODY_ATTR
		// CACHED_REQUEST_BODY_ATTR是请求体的缓存,此处的缓存可能是想让我们通过自定义Predicate或者Filter的方式在此Filter之前将body先序列化缓存
		DataBuffer body = exchange.getAttributeOrDefault(CACHED_REQUEST_BODY_ATTR, null);
		Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR);

		//此处判断body是否为空或者routesToCache是否包含当前路由ID,表示当前请求已经被缓存过
		//routesToCache在#onApplicationEvent中可能会put值
		if (body != null || !this.routesToCache.containsKey(route.getId())) {
			return chain.filter(exchange);
		}
		//如果上边的条件都不满足,则会将当前请求Body放到缓存中
		return ServerWebExchangeUtils.cacheRequestBody(exchange, (serverHttpRequest) -> {
			// don't mutate and build if same request object
			//如果是同一个请求,则直接执行Filter逻辑
			if (serverHttpRequest == exchange.getRequest()) {
				return chain.filter(exchange);
			}
			//否则,通过新的请求构建一个请求上下文
			return chain.filter(exchange.mutate().request(serverHttpRequest).build());
		});
	}
}

NettyWriteResponseFilter

NettyWriteResponseFilter在所有Filter之后运行(除了RemoveCachedBodyFilter),用于将服务的响应数据返回到客户端,与之类似的有WebClientWriteResponseFilter,属于测试版本,并未启用
NettyWriteResponseFilter会获取上下文中的CLIENT_RESPONSE_CONN_ATTR,这个Attr会在NettyRoutingFilter中放入,后边会解释NettyRoutingFilter的作用。

public class NettyWriteResponseFilter implements GlobalFilter, Ordered {
  @Override
	public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
		return chain.filter(exchange)
				.doOnError(throwable -> cleanup(exchange))
				.then(Mono.defer(() -> {
					/**
					 * 从上下文中获取CLIENT_RESPONSE_CONN_ATTR,Connection是对NettyChannel的封装
					 * CLIENT_RESPONSE_CONN_ATTR是在{@link NettyRoutingFilter#filter}中放入的
					 */
					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?
					/**
					 *将byteBuf转换为DateBuff,
					 * 因为{@link org.springframework.http.ReactiveHttpOutputMessage#writeWith(Publisher)}需要DateBuff类型的
					 */
					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);
						}
					}
					//将NettyResponse写回给客户端
					return (isStreamingMediaType(contentType)
							? response.writeAndFlushWith(body.map(Flux::just))
							: response.writeWith(body));
				})).doOnCancel(() -> cleanup(exchange));
		// @formatter:on
	}
}

ForwardPathFilter

用来处理Forward URI,对应ForwardRoutingFilter来转发请求。

public class ForwardPathFilter implements GlobalFilter, Ordered {

	@Override
	public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
		//获取路由Route
		Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR);
		//获取请求URI
		URI routeUri = route.getUri();
		String scheme = routeUri.getScheme();
		//如果请求已经被处理过或者uri的scheme不是forward,则不处理
		//可以通过自定义过滤器来设置GATEWAY_ALREADY_ROUTED_ATTR为true从而使Filter不起作用
		if (isAlreadyRouted(exchange) || !"forward".equals(scheme)) {
			return chain.filter(exchange);
		}
		//替换请求path重新构建path
		exchange = exchange.mutate()
				.request(exchange.getRequest().mutate().path(routeUri.getPath()).build())
				.build();
		return chain.filter(exchange);
	}
	}
}

这里提一下可以通过调用ServerWebExchangeUtils#setAlreadyRouted禁用掉部分Filter,可禁用的Filter如下。
在这里插入图片描述

GatewayMetricsFilter

网关指标监控过滤器,需要添加spring-boot-starter-actuator依赖,可通过spring.cloud.gateway.metrics.enabled=true/false进行配置,默认为开启状态。可以通过/actuator/metrics/gateway.requests来访问查看。
可提供如下数据:

  • routeId
  • routUrI
  • outcome:结果,按HttpStatus.Series分类。
    • INFORMATIONAL
    • SUCCESSFUL
    • REDIRECTION
    • CLIENT_ERROR
    • SERVER_ERROR
  • statue**:状态**
  • httpStatusCode:状态码
  • httpMethod:请求方式

GatewayMetricsFilter不是重点,我们就不做详细的分析了,感兴趣的可以自己分析下!

RouteToRequestUrlFilter

用于根据RouteUri生成真正请求的URL,并放入请求上下文中供后边Filter使用

public class RouteToRequestUrlFilter implements GlobalFilter, Ordered {
  @Override
	public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
		Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR);
		//判断上下中是否有GATEWAY_ROUTE_ATTR,在RoutePredicateHandlerMapping中放入的
		//如果没有则不执行
		if (route == null) {
			return chain.filter(exchange);
		}
		log.trace("RouteToRequestUrlFilter start");
		//获取请求的URI
		URI uri = exchange.getRequest().getURI();
		//判断是否包含编码的部分,如%
		boolean encoded = containsEncodedParts(uri);
		//获取Route的uri
		URI routeUri = route.getUri();

		//判断是否为其他类型的协议 如:lb,则会将lb去掉
		if (hasAnotherScheme(routeUri)) {
			// this is a special url, save scheme to special attribute
			// replace routeUri with schemeSpecificPart
			//将当前请求的schema放入上下文中
			exchange.getAttributes().put(GATEWAY_SCHEME_PREFIX_ATTR,
					routeUri.getScheme());
			routeUri = URI.create(routeUri.getSchemeSpecificPart());
		}
		//如果RouteUri以lb开头,必须请求中带有host
		if ("lb".equalsIgnoreCase(routeUri.getScheme()) && routeUri.getHost() == null) {
			// Load balanced URIs should always have a host. If the host is null it is
			// most
			// likely because the host name was invalid (for example included an
			// underscore)
			throw new IllegalStateException("Invalid host: " + routeUri.toString());
		}
		//生成RequestURL,并放入上下文中
		//此处生成的URL的Path最终会以请求的Path为主,会覆盖真正的RouteUri,
		// 例如RouteUri为http://localhost:8088/api/hello,请求的URI为http://localhost:8080/api,
		// 	那此处生成的URL为http://localhost:8080/api
		URI mergedUrl = UriComponentsBuilder.fromUri(uri)
				// .uri(routeUri)
				.scheme(routeUri.getScheme()).host(routeUri.getHost())
				.port(routeUri.getPort()).build(encoded).toUri();
		exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, mergedUrl);
		return chain.filter(exchange);
	}

}

LoadBalancerClientFilter和ReactiveLoadBalancerClientFilter

在后面分析整合注册中心时会讲解。

WebsocketRoutingFilter

处理websocket请求,当请求上下文中的GATEWAY_REQUEST_URL_ATTR的URL中的协议(schema)为ws或者wss该Filter生效,使用Spring的WebSocket对请求进行转发。同时可以进行负载均衡,通过在Route的URI配置前边加上lb:生效。

配置

spring:
  cloud:
    gateway:
      routes:
        - id: hello_route
          uri: ws://httpbin或者wss://httpbin 也可在前边加上lb:表示需要负载均衡
          predicates:
            - Path=/api

filter方法

public class WebsocketRoutingFilter implements GlobalFilter, Ordered {
  @Override
	public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
		changeSchemeIfIsWebSocketUpgrade(exchange);

		URI requestUrl = exchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR);
		String scheme = requestUrl.getScheme();
		//只处理协议为ws或者wss的
		if (isAlreadyRouted(exchange)
				|| (!"ws".equals(scheme) && !"wss".equals(scheme))) {
			return chain.filter(exchange);
		}
		//设置为已被处理,后边的NettyRoutingFilter或者WebClientHttpRoutingFilter则不会执行
		setAlreadyRouted(exchange);

		HttpHeaders headers = exchange.getRequest().getHeaders();
		HttpHeaders filtered = filterRequest(getHeadersFilters(), exchange);

		List<String> protocols = headers.get(SEC_WEBSOCKET_PROTOCOL);
		if (protocols != null) {
			protocols = headers.get(SEC_WEBSOCKET_PROTOCOL).stream().flatMap(
					header -> Arrays.stream(commaDelimitedListToStringArray(header)))
					.map(String::trim).collect(Collectors.toList());
		}
		/**
		 * 通过{@link HandshakeWebSocketService}去转发的请求
		 */
		return this.webSocketService.handleRequest(exchange, new ProxyWebSocketHandler(
				requestUrl, this.webSocketClient, filtered, protocols));
	}
}

NettyRoutingFilter

NettyRoutingFilter处理schema为http/https的请求,使用基于Netty HttpClient请求后端的服务,上边讲到的NettyWriteResponseFilter用来处理NettyRoutingFilter请求后端获得的响应,将响应写回给客户端。

同时SCG还定义了WebClientHttpRoutingFilter,于NettyRoutingFilter类似,区别在于没有使用Netty去做请求转发的代理。
NettyRoutingFilter中会将请求的响应放入上下文中,供NettyWriteResponseFilter使用。
请求超时时间:通过查看getHttpClient方法,可看到通route.getMetadata().get(``connect-timeout``);获取我们配置的路由的请求超时时间。
响应超时时间:同请求超时时间。代码见getResponseTimeout方法。
**
画了一张大概的图描述NettyRoutingFilterNettyWriteResponseFilter的关系
在这里插入图片描述

public class NettyRoutingFilter implements GlobalFilter, Ordered {
  public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
		/**
		 * 从上下文中获取在{@link RouteToRequestUrlFilter}中放入的请求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.getMethodValue());
		//获取请求的URI
		final String url = requestUrl.toASCIIString();
		//执行请求头Filter,如ForwardedHeadersFilter、RemoveHopByHopHeadersFilter、XForwardedHeadersFilter
		HttpHeaders filtered = filterRequest(getHeadersFilters(), exchange);

		final DefaultHttpHeaders httpHeaders = new DefaultHttpHeaders();
		//基于filter过后的请求头创建Http请求头
		filtered.forEach(httpHeaders::set);

		boolean preserveHost = exchange
				.getAttributeOrDefault(PRESERVE_HOST_HEADER_ATTRIBUTE, false);

		//获取路由
		Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR);
		//创建HttpClient
		Flux<HttpClientResponse> responseFlux = getHttpClient(route, exchange)
				.headers(headers -> {
					//添加请求头
					headers.add(httpHeaders);
					// Will either be set below, or later by Netty
					//移除HOST
					headers.remove(HttpHeaders.HOST);
					if (preserveHost) {//判断是否需要增加HOST
						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("outbound route: "
										+ connection.channel().id().asShortText()
										+ ", inbound: " + exchange.getLogPrefix()));
					}
					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 NettyWriteResponseFilter
					//将调用真实服务返回的Response放入上下文,但NettyWriteResponseFilter中也没有用
					exchange.getAttributes().put(CLIENT_RESPONSE_ATTR, res);
					//将Netty Channle放入上下文供NettyWriteResponseFilter使用
					exchange.getAttributes().put(CLIENT_RESPONSE_CONN_ATTR, connection);
					...................省略部分代码..................
	}
		//获取响应超时时间
		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));		
}

ForwardRoutingFilter

用来处理forward协议的请求,将ForwardPathFilter构建的新的Request发送给DispatcherHandler处理。

public class ForwardRoutingFilter implements GlobalFilter, Ordered {
	@Override
	public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
		//获取RouteToRequestUrlFilter中生成的请求URL
		URI requestUrl = exchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR);
		String scheme = requestUrl.getScheme();
		//判定是否已经处理过或者请求协议为forward
		if (isAlreadyRouted(exchange) || !"forward".equals(scheme)) {
			return chain.filter(exchange);
		}

		// TODO: translate url?

		if (log.isTraceEnabled()) {
			log.trace("Forwarding to URI: " + requestUrl);
		}
		//发送给DispatcherHander处理
		return this.getDispatcherHandler().handle(exchange);
	}
}