Writing Custom Predicates and Filters

Spring Cloud Gateway Server MVC 使用 Spring WebMvc.fn API ( javadoc) 作为 API 网关功能的基础。 可以使用这些 API 扩展 Spring Cloud Gateway Server MVC。用户可能通常希望编写 RequestPredicateHandlerFilterFunction 的自定义实现,以及 HandlerFilterFunction 的两个变体,一个用于“before”过滤器,另一个用于“after”过滤器。

Fundamentals

Spring WebMvc.fn API 的一部分的最基本接口是 ServerRequest ( javadoc) 和 ServerResponse ( javadoc)。这些接口可访问 HTTP 请求和响应的所有部分。

Spring WebMvc.fn 文档 declare 中说明:ServerRequestServerResponse 是不可变接口。在某些情况下,Spring Cloud Gateway Server MVC 必须提供替代实现,以便某些内容可变,以满足 API 网关的代理要求。

Implementing a RequestPredicate

Spring WebMvc.fn RouterFunctions.Builder 期望 RequestPredicate ( javadoc) 来匹配给定的 RouteRequestPredicate 是功能性接口,因此可以用 lambda 函数实现。要实现的方法签名是:

boolean test(ServerRequest request)

Example RequestPredicate Implementation

在此示例中,我们将展示一个谓词的实现,以测试特定 HTTP 标头是否属于 HTTP 请求的一部分。

Spring WebMvc.fn RequestPredicatesGatewayRequestPredicates 中的 RequestPredicate 实现全部实现为 static 方法。我们将在此处执行相同的操作。

SampleRequestPredicates.java
import org.springframework.web.reactive.function.server.RequestPredicate;

class SampleRequestPredicates {
    public static RequestPredicate headerExists(String header) {
		return request -> request.headers().asHttpHeaders().containsKey(header);
    }
}

该实现是一个简单的 lambda 函数,它将 ServerRequest.Headers 对象转换为 HttpHeaders 的更丰富的 API。这允许谓词测试已命名的 header 的存在。

How To Use A Custom RequestPredicate

若要使用新的 headerExists RequestPredicate,我们需要将其插入到 RouterFunctions.Builder 上的适当方法中,例如 route()。当然,在下面的示例中,headerExists 方法中的 lambda 函数可以内联编写。

RouteConfiguration.java
import static SampleRequestPredicates.headerExists;
import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route;
import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http;

@Configuration
class RouteConfiguration {

    @Bean
    public RouterFunction<ServerResponse> headerExistsRoute() {
		return route("header_exists_route")
				.route(headerExists("X-Green"), http("https://example.org"))
					.build();
    }
}

当 HTTP 请求具有名为 X-Green 的标头时,将匹配上述路由。

Writing Custom HandlerFilterFunction Implementations

RouterFunctions.Builder 有三种选项可以添加过滤器: filterbeforeafter。`before`和 `after`方法是通用 `filter`方法的专门化。

Implementing a HandlerFilterFunction

filter 方法采用 HandlerFilterFunction 作为参数。HandlerFilterFunction<T extends ServerResponse, R extends ServerResponse> 是功能性接口,因此可以用 lambda 函数实现。要实现的方法签名是:

R filter(ServerRequest request, HandlerFunction<T> next)

这允许访问 ServerRequest,并在调用 next.handle(request) 后,才能访问 ServerResponse

Example HandlerFilterFunction Implementation

此示例将展示向请求和响应添加标头。

SampleHandlerFilterFunctions.java
import org.springframework.web.servlet.function.HandlerFilterFunction;
import org.springframework.web.servlet.function.ServerRequest;
import org.springframework.web.servlet.function.ServerResponse;

class SampleHandlerFilterFunctions {
	public static HandlerFilterFunction<ServerResponse, ServerResponse> instrument(String requestHeader, String responseHeader) {
		return (request, next) -> {
			ServerRequest modified = ServerRequest.from(request).header(requestHeader, generateId());
			ServerResponse response = next.handle(modified);
			response.headers().add(responseHeader, generateId());
			return response;
		};
	}
}

首先,从现有请求中创建一个新的 ServerRequest。这允许我们使用 header() 方法添加标头。然后,我们调用 next.handle(),传入修改后的 ServerRequest。然后,使用返回的 ServerResponse,我们将标头添加到响应中。

How To Use Custom HandlerFilterFunction Implementations

RouteConfiguration.java
import static SampleHandlerFilterFunctions.instrument;
import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route;
import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http;

@Configuration
class RouteConfiguration {

    @Bean
    public RouterFunction<ServerResponse> instrumentRoute() {
		return route("instrument_route")
				.GET("/**", http("https://example.org"))
					.filter(instrument("X-Request-Id", "X-Response-Id"))
					.build();
    }
}

上述路由将向请求添加 X-Request-Id 标头,向响应添加 X-Response-Id 标头。

Writing Custom Before Filter Implementations

before 方法将 Function<ServerRequest, ServerRequest> 作为参数。这样就可以创建一个新的 ServerRequest,其中包含要从函数返回的更新数据。

在此之前,可通过 HandlerFilterFunction.ofRequestProcessor() 将函数适于 HandlerFilterFunction 实例。

Example Before Filter Implementation

在此示例中,我们将向请求中添加一个具有生成值的新标头。

SampleBeforeFilterFunctions.java
import java.util.function.Function;
import org.springframework.web.servlet.function.ServerRequest;

class SampleBeforeFilterFunctions {
	public static Function<ServerRequest, ServerRequest> instrument(String header) {
		return request -> ServerRequest.from(request).header(header, generateId());;
	}
}

从现有请求中创建一个新的 ServerRequest。这允许我们使用 header() 方法添加标头。此实现比 HandlerFilterFunction 更简单,因为我们只处理 ServerRequest

How To Use Custom Before Filter Implementations

RouteConfiguration.java
import static SampleBeforeFilterFunctions.instrument;
import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route;
import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http;

@Configuration
class RouteConfiguration {

    @Bean
    public RouterFunction<ServerResponse> instrumentRoute() {
		return route("instrument_route")
				.GET("/**", http("https://example.org"))
					.before(instrument("X-Request-Id"))
					.build();
    }
}

上述路由将向请求添加 X-Request-Id 标头。请注意使用 before() 方法,而不是 filter() 方法。

Writing Custom After Filter Implementations

after 方法采用了 BiFunction<ServerRequest,ServerResponse,ServerResponse>。通过这种方法,既可以访问 ServerRequest 也可以访问 ServerResponse,并且能够返回包含更新信息的新 ServerResponse

可以通过 HandlerFilterFunction.ofResponseProcessor()HandlerFilterFunction 实例上启用附加功能。

Example After Filter Implementation

在此示例中,我们将向响应添加一个带有生成值的头信息。

SampleAfterFilterFunctions.java
import java.util.function.BiFunction;
import org.springframework.web.servlet.function.ServerRequest;
import org.springframework.web.servlet.function.ServerResponse;

class SampleAfterFilterFunctions {
	public static BiFunction<ServerRequest, ServerResponse, ServerResponse> instrument(String header) {
		return (request, response) -> {
			response.headers().add(header, generateId());
			return response;
		};
	}
}

在此示例中,我们只需将头信息添加到响应并返回即可。

How To Use Custom After Filter Implementations

RouteConfiguration.java
import static SampleAfterFilterFunctions.instrument;
import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route;
import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http;

@Configuration
class RouteConfiguration {

    @Bean
    public RouterFunction<ServerResponse> instrumentRoute() {
		return route("instrument_route")
				.GET("/**", http("https://example.org"))
					.after(instrument("X-Response-Id"))
					.build();
    }
}

以上路由会将 X-Response-Id 头信息添加到响应中。请注意,这里使用 after() 方法,而不是 filter() 方法。

How To Register Custom Predicates and Filters for Configuration

要在外部配置中使用自定义谓词和筛选器,需要创建专门的 Supplier 类并将其注册到 META-INF/spring.factories 中。

Registering Custom Predicates

要注册自定义谓词,需要实现 PredicateSupplierPredicateDiscoverer 查找返回 RequestPredicates 的静态方法来进行注册。

SampleFilterSupplier.java

package com.example;

import org.springframework.cloud.gateway.server.mvc.predicate.PredicateSupplier;

@Configuration
class SamplePredicateSupplier implements PredicateSupplier {

	@Override
	public Collection<Method> get() {
		return Arrays.asList(SampleRequestPredicates.class.getMethods());
	}

}

然后,需要将该类添加到 META-INF/spring.factories 中。

META-INF/spring.factories
org.springframework.cloud.gateway.server.mvc.predicate.PredicateSupplier=\
  com.example.SamplePredicateSupplier

Registering Custom Filters

SimpleFilterSupplier 允许轻松注册自定义筛选器。FilterDiscoverer 查找返回 HandlerFilterFunction 的静态方法来进行注册。如果你需要 SimpleFilterSupplier 更灵活的功能,可以直接实现 FilterSupplier

SampleFilterSupplier.java
package com.example;

import org.springframework.cloud.gateway.server.mvc.filter.SimpleFilterSupplier;

@Configuration
class SampleFilterSupplier extends SimpleFilterSupplier {

    public SampleFilterSupplier() {
		super(SampleAfterFilterFunctions.class);
	}
}

然后,需要将该类添加到 META-INF/spring.factories 中。

META-INF/spring.factories
org.springframework.cloud.gateway.server.mvc.filter.FilterSupplier=\
  com.example.SampleFilterSupplier