GatewayFilterFactory 简介

路由过滤器允许以某种方式修改传入的HTTP请求或传出的HTTP响应。 路径过滤器的范围限定为特定路径。 Spring Cloud Gateway包含许多内置的GatewayFilter工厂。

网关过滤器工厂接口有多个实现类,在每个 GatewayFilterFactory 实现类的 apply( T config) 方法里,都声明了一个实现 GatewayFilter 的内部类。

内置的过滤器工厂一共有22个,分别位于 org.springframework.cloud.gateway.filter.factory及org.springframework.cloud.gateway.filter.factory.rewrite包中

GatewayFilterFactory

2、GatewayFilterFactory 分类

过滤器 有 20 多个 实现 类, 包括 头部 过滤器、 路径 类 过滤器、 Hystrix 过滤器 和 变更 请求 URL 的 过滤器, 还有 参数 和 状态 码 等 其他 类型 的 过滤器。

内置的过滤器工厂有22个实现类,包括 头部过滤器、路径过滤器、Hystrix 过滤器 、请求URL 变更过滤器,还有参数和状态码等其他类型的过滤器。根据过滤器工厂的用途来划分,可以分为以下几种:Header、Parameter、Path、Body、Status、Session、Redirect、Retry、RateLimiter和Hystrix

在这里插入图片描述

3、GatewayFilterFactory 源码

由接口名称上的注解 @FunctionalInterface 可知,GatewayFilterFactory是一个函数式接口,其中 default 类型的方法 name 用来对过滤器进行标准化命名,apply 方法用于定义具体的过滤操作,泛型 C 是在各个实现类中的配置对象

@FunctionalInterface
public interface GatewayFilterFactory<C> extends ShortcutConfigurable, Configurable<C> {

	String NAME_KEY = "name";
	String VALUE_KEY = "value";

	// useful for javadsl
	default GatewayFilter apply(Consumer<C> consumer) {
		C config = newConfig();
		consumer.accept(config);
		return apply(config);
	}

	default Class<C> getConfigClass() {
		throw new UnsupportedOperationException("getConfigClass() not implemented");
	}

	@Override
	default C newConfig() {
		throw new UnsupportedOperationException("newConfig() not implemented");
	}

	GatewayFilter apply(C config);

	default String name() {
		//TODO: deal with proxys
		return NameUtils.normalizeFilterFactoryName(getClass());
	}

	@Deprecated
	default ServerHttpRequest.Builder mutate(ServerHttpRequest request) {
		return request.mutate();
	}
}

3.1、GatewayFilterFactory 抽象实现类

在这里插入图片描述

由 GatewayFilterFactory 类图可知,GatewayFilterFactory主要有三个抽象类:

AbstractGatewayFilterFactory:

    HystrixGatewayFilterFactory
    ModifyRequestBodyGatewayFilterFactory
    ModifyResponseBodyGatewayFilterFactory
    PrefixPathGatewayFilterFactory
    PreserveHostHeaderGatewayFilterFactory
    RedirectToGatewayFilterFactory
    RemoveRequestHeaderGatewayFilterFactory
    RemoveResponseHeaderGatewayFilterFactory
    RequestRateLimiterGatewayFilterFactory
    RetryGatewayFilterFactory
    RewritePathGatewayFilterFactory
    SaveSessionGatewayFilterFactory
    SecureHeadersGatewayFilterFactory
    SetPathGatewayFilterFactory
    SetStatusGatewayFilterFactory
    StripPrefixGatewayFilterFactory

AbstractNameValueGatewayFilterFactory(继承自AbstractGatewayFilterFactory)

    AddRequestHeaderGatewayFilterFactory
    AddRequestParameterGatewayFilterFactory
    AddResponseHeaderGatewayFilterFactory
    SetRequestHeaderGatewayFilterFactory
    SetResponseHeaderGatewayFilterFactory

AbstractChangeRequestUriGatewayFilterFactory(继承自AbstractGatewayFilterFactory)
    
    RequestHeaderToRequestUriGatewayFilterFactory

AbstractNameValueGatewayFilterFactory

该抽象类用于提供通用的方法给键值对参数类型的网关过滤器,接收两个参数,如
AddRequestHeader 这种类型的过滤器

spring:
  cloud:
    gateway:
      routes:
      - id: add_request_header_route
        uri: http://example.org
        filters:
        - AddRequestHeader=X-Request-Foo, Bar

AbstractChangeRequestUriGatewayFilterFactory

改变请求的 URI 过滤器,该过滤器通过方法 determineRequestUri( ServerWebExchange,T)实现改变请求URI的逻辑

4、GatewayFilterFactory 实现类

4.1、Header 类过滤器

4.1.1、AddRequestHeaderGatewayFilterFactory

增加请求的头部信息,并将头部传递到下游。

spring:
  cloud:
    gateway:
      routes:
      - id: add_request_header_route
        uri: http://example.org
        filters:
        - AddRequestHeader=X-Request-Foo, Bar

这将为所有匹配请求的下游请求标头添加X-Request-Foo:Bar头。

4.1.2、RemoveRequestHeaderGatewayFilterFactory

在请求发送到下游之前,为匹配的请求移除设置的头部信息。

spring:
  cloud:
    gateway:
      routes:
      - id: removeresponseheader_route
        uri: http://example.org
        filters:
        - RemoveResponseHeader=X-Response-Foo

请求发送到下游之前删除X-Response-Foo头。

4.1.3、AddResponseHeaderGatewayFilterFactory

将会为匹配的请求增加响应头部,传递到下游的相应头部。

spring:
  cloud:
    gateway:
      routes:
      - id: add_request_header_route
        uri: http://example.org
        filters:
        - AddResponseHeader=X-Response-Foo, Bar

这会将X-Response-Foo:Bar 头添加到所有匹配请求的下游响应标头中。

4.1.4、RemoveResponseHeaderGatewayFilterFactory

将相应结果返给客户端之前,将会移除设置的响应头部信息。

spring:
  cloud:
    gateway:
      routes:
      - id: removeresponseheader_route
        uri: http://example.org
        filters:
        - RemoveResponseHeader=X-Response-Foo

这将在响应返回到网关客户端之前从响应中删除X-Response-Foo标头。

4.1.5、SetRequestHeaderGatewayFilterFactory

当请求经过网关转发时,该过滤器将会用给定的名字替换所有的头部信息

spring:
  cloud:
    gateway:
      routes:
      - id: setresponseheader_route
        uri: http://example.org
        filters:
        - SetRequestHeader=X-Response-Foo, Bar

使用 X-Response-Foo:Bar替换请求头中对应属性的值

4.1.6、SetResponseHeaderGatewayFilterFactory

spring:
  cloud:
    gateway:
      routes:
      - id: setresponseheader_route
        uri: http://example.org
        filters:
        - SetResponseHeader=X-Response-Foo, Bar

此GatewayFilter将替换具有给定名称的所有标头,而不是添加。 因此,如果下游服务器以X-Response-Foo:1234响应,则将替换为X-Response-Foo:Bar,这是网关客户端将接收的内容。

4.1.7、PreserveHostHeaderGatewayFilterFactory

PreserveHostHeader GatewayFilter Factory没有参数。 此过滤器设置路由过滤器将检查的请求属性,以确定是否应发送原始主机头,而不是http客户端确定的主机头。

spring:
  cloud:
    gateway:
      routes:
      - id: preserve_host_route
        uri: http://example.org
        filters:
        - PreserveHostHeader

4.1.8、RequestHeaderToRequestUriGatewayFilterFactory

spring:
  cloud:
    gateway:
      enabled: true
      discovery:
        locator:
          enabled: true
      routes:
      - id: request_header_to_request_uri_route
        uri: http://example.org
        filters:
        - RequestHeaderToRequestUri=X-New-Url

当请求 http://example.org 时,会根据X-New-Url来进行新的url路由

4.1.9、SecureHeadersGatewayFilterFactory

当使用该过滤器时,会默认为请求头添加以下属性:

X-Xss-Protection:1; mode=block
Strict-Transport-Security:max-age=631138519
X-Frame-Options:DENY
X-Content-Type-Options:nosniff
Referrer-Policy:no-referrer
Content-Security-Policy:default-src 'self' https:; font-src 'self' https: data:; img-src 'self' https: data:; object-src 'none'; script-src https:; style-src 'self' https: 'unsafe-inline'
X-Download-Options:noopen
X-Permitted-Cross-Domain-Policies:none

要更改默认值,请在spring.cloud.gateway.filter.secure-headers命名空间中设置相应的属性:

xss-protection-header
strict-transport-security
frame-options
content-type-options
referrer-policy
content-security-policy
download-options
permitted-cross-domain-policies

关于这些请求头的含义可参考该文章:https://blog.appcanary.com/2017/http-security-headers.html

4.2、Parameter 类过滤器

4.2.1、AddRequestParameterGatewayFilterFactory

spring:
  cloud:
    gateway:
      routes:
      - id: add_request_parameter_route
        uri: http://example.org
        filters:
        - AddRequestParameter=foo, bar

这会将foo = bar添加到下游请求的所有匹配请求的查询字符串中。

4.3、Path 类过滤器

4.3.1、PrefixPathGatewayFilterFactory

spring:
  cloud:
    gateway:
      routes:
      - id: prefixpath_route
        uri: http://example.org
        filters:
        - PrefixPath=/mypath

这将使/mypath前缀为所有匹配请求的路径。 所以对/hello的请求会被发送到/mypath / hello。

4.3.2、RewritePathGatewayFilterFactory

RewritePath网关过滤器工厂采用路径正则表达式参数和替换参数。 这使用Java正则表达式来灵活地重写请求路径。

spring:
  cloud:
    gateway:
      routes:
      - id: rewritepath_route
        uri: http://example.org
        predicates:
        - Path=/foo/**
        filters:
        - RewritePath=/foo/(?<segment>.*), /$\{segment}

对于/ foo / bar的请求路径,这将在发出下游请求之前将路径设置为/ bar。 注意由于YAML规范,$ \替换为$。

4.3.3、SetPathGatewayFilterFactory

SetPath GatewayFilter Factory采用路径模板参数。 它提供了一种通过允许模板化路径段来操作请求路径的简单方法。 这使用了Spring Framework中的uri模板。 允许多个匹配的段。

spring:
  cloud:
    gateway:
      routes:
      - id: setpath_route
        uri: http://example.org
        predicates:
        - Path=/foo/{segment}
        filters:
        - SetPath=/{segment}

对于/ foo / bar的请求路径,这将在发出下游请求之前将路径设置为/ bar。

4.3.4、StripPrefixGatewayFilterFactory

StripPrefix网关过滤器工厂采用一个参数StripPrefix。 StripPrefix参数表示在将请求发送到下游之前从请求中剥离的路径个数。

spring:
  cloud:
    gateway:
      routes:
      - id: nameRoot
        uri: http://nameservice
        predicates:
        - Path=/name/**
        filters:
        - StripPrefix=2

当通过网关向/name/bar/foo发出请求时,对nameservice的请求将类似于http://nameservice/foo。

4.4、Body 类过滤器

4.4.1、ModifyRequestBodyGatewayFilterFactory

用于修改请求头信息

它应该删除原始的Content-Length标头,并在需要时更新Content-Type。

4.4.2、ModifyResponseBodyGatewayFilterFactory

用于修改响应体信息

4.5、Status 类过滤器

4.5.1、SetStatusGatewayFilterFactory

SetStatus GatewayFilter Factory采用单个状态参数。 它必须是有效的Spring HttpStatus。 它可以是整数值404或枚举NOT_FOUND的字符串表示。

spring:
  cloud:
    gateway:
      routes:
      - id: setstatusstring_route
        uri: http://example.org
        filters:
        - SetStatus=BAD_REQUEST
      - id: setstatusint_route
        uri: http://example.org
        filters:
        - SetStatus=401

在任何一种情况下,响应的HTTP状态都将设置为401。

4.6、Session 类过滤器

4.6.1、SaveSessionGatewayFilterFactory

SaveSession GatewayFilter Factory在转发下游调用之前强制执行WebSession :: save操作。 当使用Spring Session与懒加载数据存储之类的东西时,这是特别有用的,并且需要确保在转发调用之前已保存会话状态。

spring:
  cloud:
    gateway:
      routes:
      - id: save_session
        uri: http://example.org
        predicates:
        - Path=/foo/**
        filters:
        - SaveSession

如果要将Spring Security与Spring Session集成,并且希望确保将安全性详细信息转发到远程进程,则这很关键。

4.7、Redirect 类过滤器

4.7.1、RedirectToGatewayFilterFactory

RedirectTo GatewayFilter Factory采用status和url参数。 状态应该是300系列重定向http代码,例如301. url应该是有效的URL。 这将是Location标头的值。

spring:
  cloud:
    gateway:
      routes:
      - id: prefixpath_route
        uri: http://example.org
        filters:
        - RedirectTo=302, http://acme.org

将发送带有Location:http://acme.org 的请求头属性且状态302的请求,以执行重定向。

4.8、Retry 类过滤器

4.8.1、RetryGatewayFilterFactory

该过滤器有三个参数:

  • retries: 重试次数
  • statuses: 应该重试的HTTP状态代码,使用org.springframework.http.HttpStatus表示
  • methods: 应该重试的HTTP方法,使用org.springframework.http.HttpMethod表示
  • series: 要重试的一系列状态代码,使用org.springframework.http.HttpStatus.Series表示
spring:
  cloud:
    gateway:
      routes:
      - id: retry_test
        uri: http://localhost:8080/flakey
        predicates:
        - Host=*.retry.com
        filters:
        - name: Retry
          args:
            retries: 3
            statuses: BAD_GATEWAY

4.9、RateLimiter 类过滤器

4.9.1、RequestRateLimiterGatewayFilterFactory

RequestRateLimiter GatewayFilter Factory使用RateLimiter实现来确定是否允许当前请求继续。 如果不是,则返回HTTP 429 - Too Many Requests(默认情况下)的状态。

此过滤器采用可选的keyResolver参数和特定于速率限制器的参数(参见下文)。

keyResolver是一个实现KeyResolver接口的bean。 在配置中,使用SpEL按名称引用bean。 #{@ myKeyResolver}是一个引用名为myKeyResolver的bean的SpEL表达式。

KeyResolver.java. 

public interface KeyResolver {
	Mono<String> resolve(ServerWebExchange exchange);
}

KeyResolver接口允许可插拔策略派生用于限制请求的密钥。 在未来的里程碑中,将会有一些KeyResolver实现。

KeyResolver的默认实现是PrincipalNameKeyResolver,它从ServerWebExchange检索Principal并调用Principal.getName()。

RequestRateLimiter不能通过“快捷方式”表示法进行配置。 以下示例无效

application.properties

# INVALID SHORTCUT CONFIGURATION
spring.cloud.gateway.routes[0].filters[0]=RequestRateLimiter=2, 2, #{@userkeyresolver}

4.10、Hystrix 类过滤器

4.10.1、HystrixGatewayFilterFactory

Hystrix是Netflix的一个库,它实现了断路器模式。 Hystrix GatewayFilter允许您将断路器引入网关路由,保护您的服务免受级联故障的影响,并允许您在下游故障时提供回退响应。

要在项目中启用Hystrix GatewayFilters,请在Spring Cloud Netflix上添加对spring-cloud-starter-netflix-hystrix的依赖关系。

Hystrix GatewayFilter Factory需要单个名称参数,该参数是HystrixCommand的名称。

spring:
  cloud:
    gateway:
      routes:
      - id: hystrix_route
        uri: http://example.org
        filters:
        - Hystrix=myCommandName

这将使用命令名myCommandName将剩余的过滤器包装在HystrixCommand中。

Hystrix过滤器还可以接受可选的fallbackUri参数。 目前,仅支持forward:schemed URIs。 如果调用了回退,请求将被转发到与URI匹配的控制器。

spring:
  cloud:
    gateway:
      routes:
      - id: hystrix_route
        uri: lb://backing-service:8088
        predicates:
        - Path=/consumingserviceendpoint
        filters:
        - name: Hystrix
          args:
            name: fallbackcmd
            fallbackUri: forward:/incaseoffailureusethis
        - RewritePath=/consumingserviceendpoint, /backingserviceendpoint

当调用Hystrix回调时,这将转发到/ incaseoffailureuset这个URI。 请注意,此示例还通过目标URI上的lb前缀演示(可选)Spring Cloud Netflix Ribbon负载平衡。

主要目的是将fallbackUri用于网关应用程序内的内部控制器或处理程序。 但是,也可以将请求重新路由到外部应用程序中的控制器或处理程序,如下所示:

spring:
  cloud:
    gateway:
      routes:
      - id: ingredients
        uri: lb://ingredients
        predicates:
        - Path=//ingredients/**
        filters:
        - name: Hystrix
          args:
            name: fetchIngredients
            fallbackUri: forward:/fallback
      - id: ingredients-fallback
        uri: http://localhost:9994
        predicates:
        - Path=/fallback

在此示例中,网关应用程序中没有回调端点或处理程序,但是,在http://localhost:9994下注册的另一个应用程序中有一个。

如果请求被转发到回调地址时,Hystrix网关过滤器还会提供导致它的Throwable。 它作为ServerWebExchangeUtils.HYSTRIX_EXECUTION_EXCEPTION_ATTR属性添加到ServerWebExchange中,可以在处理网关应用程序中的回退时使用该属性。

对于外部控制器/处理程序方案,可以添加包含异常详细信息的标头。 您可以在FallbackHeaders GatewayFilter Factory部分中找到有关它的更多信息。

Hystrix设置(例如超时)可以使用全局默认值配置,也可以使用应用程序属性逐个路径配置,如Hystrix wiki中所述。

全局过滤器GlobalFilter

GlobalGilter 全局过滤器接口与 GatewayFilter 网关过滤器接口具有相同的方法定义。全局过滤器是一系列特殊的过滤器,会根据条件应用到所有路由中。网关过滤器是更细粒度的过滤器,作用于指定的路由中。

在这里插入图片描述

从类图中可以看到 GlobalFilter 有十一个实现类,包括路由转发、负载均衡、ws 路由、netty 路由等全局过滤器。下面我们就分别介绍一下这些全局路由过滤器的实现。

2、ForwardRoutingFilter 转发路由过滤器

ForwardRoutingFilter 在交换属性 ServerWebExchangeUtils.GATEWAY_ REQUEST_ URL_ ATTR 中 查找 URL, 如果 URL 为转发模式即 forward:/// localendpoint, 它将使用Spring DispatcherHandler 来处 理请求。 未修改的原始 URL 将保存到 GATEWAY_ ORIGINAL_ REQUEST_ URL_ ATTR 属性的列表中。

public class ForwardRoutingFilter implements GlobalFilter, Ordered {

	private static final Log log = LogFactory.getLog(ForwardRoutingFilter.class);

	private final ObjectProvider<DispatcherHandler> dispatcherHandler;

	public ForwardRoutingFilter(ObjectProvider<DispatcherHandler> dispatcherHandler) {
		this.dispatcherHandler = dispatcherHandler;
	}

	@Override
	public int getOrder() {
		return Ordered.LOWEST_PRECEDENCE;
	}

	@Override
	public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
		URI requestUrl = exchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR);
        //获取请求URI的请求结构
		String scheme = requestUrl.getScheme();
		//该路由已经被处理或者URI格式不是forward则继续其它过滤器
		if (isAlreadyRouted(exchange) || !"forward".equals(scheme)) {
			return chain.filter(exchange);
		}
		setAlreadyRouted(exchange);

		//TODO: translate url?

		if (log.isTraceEnabled()) {
			log.trace("Forwarding to URI: "+requestUrl);
		}
        // 使用dispatcherHandler进行处理
		return this.dispatcherHandler.getIfAvailable().handle(exchange);
	}
}

转发路由过滤器实现比较简单,构造函数传入请求的分发处理器DispatcherHandler。过滤器执行时,首先获取请求地址的url前缀,然后判断该请求是否已被路由处理或者URL的前缀不是forward,则继续执行过滤器链;否则设置路由处理状态并交由DispatcherHandler进行处理。

请求路由是否被处理的判断如下:

// ServerWebExchangeUtils.java

public static void setAlreadyRouted(ServerWebExchange exchange) {
		exchange.getAttributes().put(GATEWAY_ALREADY_ROUTED_ATTR, true);
	}

	public static boolean isAlreadyRouted(ServerWebExchange exchange) {
		return exchange.getAttributeOrDefault(GATEWAY_ALREADY_ROUTED_ATTR, false);
	}

两个 方法 定义 在 ServerWebExchangeUtils 中, 这 两个 方法 用于 修改 与 查询 ServerWebExchange 中的 Map< String, Object> getAttributes(),# getAttributes 方法 返回 当前 exchange 所请 求 属性 的 可变 映射。

这两个方法定义在 ServerWebExchangeUtils 中,分别用于修改和查询 GATEWAY_ALREADY_ROUTED_ATTR 状态。

3、LoadBalancerClientFilter 负载均衡客户端过滤器

spring:
  cloud:
    gateway:
      routes:
      - id: myRoute
        uri: lb://service
        predicates:
        - Path=/service/**

LoadBalancerClientFilter 在交换属性 GATEWAY_ REQUEST_ URL_ ATTR 中查找URL, 如果URL有一个 lb 前缀 ,即 lb:// myservice,将使用 LoadBalancerClient 将名称 解析为实际的主机和端口,如示例中的 myservice。 未修改的原始 URL将保存到 GATEWAY_ ORIGINAL_ REQUEST_ URL_ ATTR 属性的列表中。过滤器还将查看ServerWebExchangeUtils.GATEWAY_SCHEME_PREFIX_ATTR属性以查看它是否等于lb,然后应用相同的规则。

@Override
	public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
		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);
		}
		//保留原始url
		addOriginalRequestUrl(exchange, url);

		log.trace("LoadBalancerClientFilter url before: " + url);
        //负载均衡到具体服务实例
		final ServiceInstance instance = choose(exchange);

		if (instance == null) {
			throw new NotFoundException("Unable to find instance for " + url.getHost());
		}

		URI uri = exchange.getRequest().getURI();

	    //如果没有提供前缀的话,则会使用默认的'< scheme>',否则使用' lb:< scheme>' 机制。
		String overrideScheme = null;
		if (schemePrefix != null) {
			overrideScheme = url.getScheme();
		}
        //根据获取的服务实例信息,重新组装请求的 url
		URI requestUrl = loadBalancer.reconstructURI(new DelegatingServiceInstance(instance, overrideScheme), uri);
        // Routing 相关 的 GatewayFilter 会 通过 GATEWAY_ REQUEST_ URL_ ATTR 属性, 发起 请求。
		log.trace("LoadBalancerClientFilter url chosen: " + requestUrl);
		exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUrl);
		return chain.filter(exchange);
	}

从过滤器执行方法中可以看出,负载均衡客户端过滤器的实现步骤如下:

1、构造函数传入负载均衡客户端,依赖中添加 Spring Cloud Netflix Ribbon 即可 注入 该 Bean。

2、获取请求的 URL 及其前缀,如果 URL 不为空且前缀为lb或者网关请求的前缀是 lb,则保存原始的URL,负载到具体的服务实例并根据获取的服务实例信息,重新组装请求的URL。

3、最后,添加请求的URL到GATEWAY_ REQUEST_ URL_ ATTR,并提交到过滤器链中继续执行

在组装请求的地址时,如果loadbalancer没有提供前缀的话,则使用默认的,即overrideScheme 为null,否则的话使用 lb:

4、NettyRoutingFilter 和 NettyWriteResponseFilter

如果 ServerWebExchangeUtils.GATEWAY_ REQUEST_ URL_ ATTR 请求属性中的URL 具有http或https前缀,NettyRoutingFilter 路由过滤器将运行,它使用 Netty HttpClient 代理对下游的请求。响应信息放在ServerWebExchangeUtils.CLIENT_ RESPONSE_ ATTR 属性中,在过滤器链中进行传递。

该过滤器实际处理 和客户端负载均衡的实现方式类似:

首先获取请求的URL及前缀,判断前缀是不是http或者https,如果该请求已经被路由或者前缀不合法,则调用过滤器链直接向后传递;否则正常对头部进行过滤操作。

public class NettyRoutingFilter implements GlobalFilter, Ordered {

	private final HttpClient httpClient;
	private final ObjectProvider<List<HttpHeadersFilter>> headersFilters;
	private final HttpClientProperties properties;

	public NettyRoutingFilter(HttpClient httpClient,
							  ObjectProvider<List<HttpHeadersFilter>> headersFilters,
							  HttpClientProperties properties) {
		this.httpClient = httpClient;
		this.headersFilters = headersFilters;
		this.properties = properties;
	}

	@Override
	public int getOrder() {
		return Ordered.LOWEST_PRECEDENCE;
	}

	@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);
			}

			if (properties.getResponseTimeout() != null) {
				proxyRequest.context(ctx -> ctx.addHandlerFirst(
						new ReadTimeoutHandler(properties.getResponseTimeout().toMillis(), TimeUnit.MILLISECONDS)));
			}

			return proxyRequest.sendHeaders() //I shouldn't need this
					.send(request.getBody().map(dataBuffer ->
							((NettyDataBuffer) dataBuffer).getNativeBuffer()));
		});

		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()));

			String contentTypeValue = headers.getFirst(HttpHeaders.CONTENT_TYPE);
			if (StringUtils.hasLength(contentTypeValue)) {
				exchange.getAttributes().put(ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR, contentTypeValue);
			}

			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);
		})
				.onErrorMap(t -> properties.getResponseTimeout() != null && t instanceof ReadTimeoutException,
						t -> new TimeoutException("Response took longer than timeout: " +
								properties.getResponseTimeout()))
				.then(chain.filter(exchange));
	}
}

NettyRoutingFilter 过滤器的构造函数有三个参数:

HttpClient httpClient : 基于 Netty 实现的 HttpClient,通过该属性请求后端 的 Http 服务

ObjectProvider<List> headersFilters: ObjectProvider 类型 的 headersFilters,用于头部过滤

HttpClientProperties properties: Netty HttpClient 的配置属性

4.1、NettyRoutingFilter ## HttpHeadersFilter 头部过滤器接口

filterRequest 用于对请求头部的信息进行处理,是定义在接口 HttpHeadersFilter 中的默认方法,该接口有三个实现类,请求头部将会经过这三个头部过滤器,并最终返回修改之后的头部。

public interface HttpHeadersFilter {

	enum Type {
		REQUEST, RESPONSE
	}

	/**
	 * Filters a set of Http Headers
	 * 
	 * @param input Http Headers
	 * @param exchange
	 * @return filtered Http Headers
	 */
	HttpHeaders filter(HttpHeaders input, ServerWebExchange exchange);

	static HttpHeaders filterRequest(List<HttpHeadersFilter> filters,
							  ServerWebExchange exchange) {
		HttpHeaders headers = exchange.getRequest().getHeaders();
		return filter(filters, headers, exchange, Type.REQUEST);
	}

	static HttpHeaders filter(List<HttpHeadersFilter> filters, HttpHeaders input,
			ServerWebExchange exchange, Type type) {
		HttpHeaders response = input;
		if (filters != null) {
			HttpHeaders reduce = filters.stream()
					.filter(headersFilter -> headersFilter.supports(type))
					.reduce(input,
							(headers, filter) -> filter.filter(headers, exchange),
							(httpHeaders, httpHeaders2) -> {
								httpHeaders.addAll(httpHeaders2);
								return httpHeaders;
							});
			return reduce;
		}

		return response;
	}

	default boolean supports(Type type) {
		return type.equals(Type.REQUEST);
	}
}

HttpHeadersFilter 接口的三个实现类:

  • ForwardedHeadersFilter:

    增加 Forwarded头部,头部值为协议类型、host和目标地址

  • XForwardedHeadersFilter:

    增加 X- Forwarded- For、 X- Forwarded- Host、 X- Forwarded- Port 和 X- Forwarded- Proto 头部。 代理转发时,用以自定义的头部信息向下游传递。

  • RemoveHopByHopHeadersFilter:

    为了定义缓存和非缓存代理的行为,我们将HTTP头字段分为两类:端到端的头部字段,发送给请求或响应的最终接收人;逐跳头部字段,对单个传输级别连接有意义,并且不被缓存存储或由代理转发。

    所以该头部过滤器会移除逐跳头部字段,包括以下8个字段:

    Proxy- Authenticate

    Proxy- Authorization

    TE

    Trailer

    Transfer- Encoding

    Upgrade

    proxy- connection

    content- length

4.2、NettyWriteResponseFilter

NettyWriteResponseFilter 与 NettyRoutingFilter 成对使用。“ 预” 过滤阶段没有任何内容,因为 CLIENT_ RESPONSE_ ATTR 在 WebHandler 运行之前不会被添加。

@Override
	public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
		// NOTICE: nothing in "pre" filter stage as CLIENT_RESPONSE_ATTR is not added
		// until the WebHandler is run
		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 = null;
			try {
				contentType = response.getHeaders().getContentType();
			} catch (Exception e) {
				log.trace("invalid media type", e);
			}
			return (isStreamingMediaType(contentType) ?
					response.writeAndFlushWith(body.map(Flux::just)) : response.writeWith(body));
		}));
	}

如果 CLIENT_ RESPONSE_ ATTR 请求 属性 中 存在 Netty HttpClientResponse, 则 会应用 NettyWriteResponseFilter。 它在其他过滤器完成后运行,并将代理响应写回 网关客户端响应。成对出现的 WebClientHttpRoutingFilter 和 WebClientWriteResponseFilter 过滤器,与基于Nettty 的路由和响应过滤器执行相同 的功能,但不需要使用Netty。

5、RouteToRequestUrlFilter 路由到指定url的过滤器

如果 ServerWebExchangeUtils.GATEWAY_ ROUTE_ ATTR 请求属性中有Route对象, 则 会运行 RouteToRequestUrlFilter 过滤器。他会根据请求URI创建一个新的URI。 新的 URI 位于 ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR 请求属性中。该过滤器会组装成发送到代理服务的URL地址,向后传递到路由转发的过滤器。

@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");
		URI uri = exchange.getRequest().getURI();
		boolean encoded = containsEncodedParts(uri);
		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());
		}

		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);
	}
  • 首先获取请求中的 Route, 如 果为 空 则 直接 提交 过滤器 链; 否则 获取 routeUri, 并 判断 routeUri 是否 特殊, 如果 是 则需 要 处理 URL, 保存 前缀 到 GATEWAY_SCHEME_PREFIX_ATTR, 并将 routeUri 替换

  • 首先获取请求中的Route,如果为空则直接提交给过滤器链

  • 获取routeUri并判断是否特殊,如果是则需要处理URL,保存前缀到GATEWAY_SCHEME_PREFIX_ATTR,并将routeUri 替换为schemeSpecificPart

  • 然后拼接requestUrl,将请求的URI转换为路由定义的routeUri

  • 最后,提交到过滤器链继续执行

6、WebsocketRoutingFilter

如果请求中的ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR 属性对应的URL前缀为 ws 或 wss,则启用Websocket 路由过滤器。它使用Spring Web Socket 作为底层通信组件向下游转发 WebSocket 请求。Websocket 可以通过添加前缀 lb来实现负载均衡,如 lb:ws://serviceid

如果您使用SockJS作为普通http的回调,则应配置正常的HTTP路由以及Websocket路由

spring:
  cloud:
    gateway:
      routes:
      # SockJS route
      - id: websocket_sockjs_route
        uri: http://localhost:3001
        predicates:
        - Path=/websocket/info/**
      # Normwal Websocket route
      - id: websocket_route
        uri: ws://localhost:3001
        predicates:
        - Path=/websocket/**

Websocket 路由过滤器进行处理时,首先获取请求的URL及其前缀,判断是否满足 Websocket 过滤器启用的条件;对于未被路由处理且请求前缀为ws或wss的请求,设置路由处理状态位,构造过滤后的头部。最后将请求通过代理转发。

// WebsocketRoutingFilter.java

@Override
	public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
	    //检查websocket 是否是 upgrade
		changeSchemeIfIsWebSocketUpgrade(exchange);

		URI requestUrl = exchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR);
		String scheme = requestUrl.getScheme();
        //判断是否满足websocket启用条件
		if (isAlreadyRouted(exchange) || (!"ws".equals(scheme) && !"wss".equals(scheme))) {
			return chain.filter(exchange);
		}
		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());
		}
        //将请求代理转发
		return this.webSocketService.handleRequest(exchange,
				new ProxyWebSocketHandler(requestUrl, this.webSocketClient,
						filtered, protocols));
	}

ProxyWebSocketHandler 是 WebSocketHandler 的实现类,处理客户端 WebSocket Session。 下面看一下代理 WebSocket 处理器的具体实现:

// WebsocketRoutingFilter.java

private static class ProxyWebSocketHandler implements WebSocketHandler {

		private final WebSocketClient client;
		private final URI url;
		private final HttpHeaders headers;
		private final List<String> subProtocols;

		public ProxyWebSocketHandler(URI url, WebSocketClient client, HttpHeaders headers, List<String> protocols) {
			this.client = client;
			this.url = url;
			this.headers = headers;
			if (protocols != null) {
				this.subProtocols = protocols;
			} else {
				this.subProtocols = Collections.emptyList();
			}
		}

		@Override
		public List<String> getSubProtocols() {
			return this.subProtocols;
		}

		@Override
		public Mono<Void> handle(WebSocketSession session) {
			// pass headers along so custom headers can be sent through
			return client.execute(url, this.headers, new WebSocketHandler() {
				@Override
				public Mono<Void> handle(WebSocketSession proxySession) {
					// Use retain() for Reactor Netty
					Mono<Void> proxySessionSend = proxySession
							.send(session.receive().doOnNext(WebSocketMessage::retain));
                            // .log("proxySessionSend", Level.FINE);
					Mono<Void> serverSessionSend = session
							.send(proxySession.receive().doOnNext(WebSocketMessage::retain));
                            // .log("sessionSend", Level.FINE);
					return Mono.zip(proxySessionSend, serverSessionSend).then();
				}

				/**
				 * Copy subProtocols so they are available downstream.
				 * @return
				 */
				@Override
				public List<String> getSubProtocols() {
					return ProxyWebSocketHandler.this.subProtocols;
				}
			});
		}
	}
  • WebSocketClient# execute 方法连接后端被代理的 WebSocket 服务。

  • 连接成功后,回调WebSocketHandler实现的内部类的handle( WebSocketSession session)方法

  • WebSocketHandler 实现的内部类实现对消息的转发: 客户端=> 具体业务服务=> 客户 端; 然后合并代理服务的会话信息 proxySessionSend 和业务服务的会话信息serverSessionSend。

7、其它过滤器

AdaptCachedBodyGlobalFilter— 用于缓存请求体的过滤器,在全局过滤器中的优先级较高。

ForwardPathFilter— 请求中的 gatewayRoute 属性对应 Route 对象,当 Route 中的 URI scheme 为 forward 模式 时, 该过滤器用于设置请求的 URI 路径为 Route 对象 中的 URI 路径。

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐