Writing Custom Predicates and Filters

Spring Cloud Gateway Server MVC 使用 Spring WebMvc.fn API ( javadoc) 作为 API 网关功能的基础。

Spring Cloud Gateway Server MVC uses the Spring WebMvc.fn API (javadoc) as the basis for the API Gateway functionality.

可以使用这些 API 扩展 Spring Cloud Gateway Server MVC。用户可能通常希望编写 RequestPredicateHandlerFilterFunction 的自定义实现,以及 HandlerFilterFunction 的两个变体,一个用于“before”过滤器,另一个用于“after”过滤器。

Spring Cloud Gateway Server MVC is extensible using these APIs. Users might commonly expect to write custom implementations of RequestPredicate and HandlerFilterFunction and two variations of HandlerFilterFunction, one for "before" filters and another for "after" filters.

Fundamentals

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

The most basic interfaces that are a part of the Spring WebMvc.fn API are ServerRequest (javadoc) and ServerResponse (javadoc). These provide access to all parts of the HTTP request and response.

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

The Spring WebMvc.fn docs declare that "`ServerRequest` and ServerResponse are immutable interfaces. In some cases, Spring Cloud Gateway Server MVC has to provide alternate implementations so that some things can be mutable to satisfy the proxy requirements of an API gateway.

Implementing a RequestPredicate

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

The Spring WebMvc.fn RouterFunctions.Builder expects a RequestPredicate (javadoc) to match a given Route. RequestPredicate is a functional interface and can therefor be implemented with lambdas. The method signature to implement is:

boolean test(ServerRequest request)

Example RequestPredicate Implementation

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

For this example, we will show the implementation of a predicate to test that a particular HTTP headers is part of the HTTP request.

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

The RequestPredicate implementations in Spring WebMvc.fn RequestPredicates and in GatewayRequestPredicates are all implemented as static methods. We will do the same here.

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 的存在。

The implementation is a simple lambda that transforms the ServerRequest.Headers object to the richer API of HttpHeaders. This allows the predicate to test for the presence of the named header.

How To Use A Custom RequestPredicate

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

To use our new headerExists RequestPredicate, we need to plug it in to an appropriate method on the RouterFunctions.Builder such as route(). Of course, the lambda in the headerExists method could be written inline in the example below.

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 的标头时,将匹配上述路由。

The above route will be matched when an HTTP request has a header named X-Green.

Writing Custom HandlerFilterFunction Implementations

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

The RouterFunctions.Builder has three options to add filters: filter, before, and after. The before and after methods are specializations of the general filter method.

Implementing a HandlerFilterFunction

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

The filter method takes a HandlerFilterFunction as a parameter. HandlerFilterFunction<T extends ServerResponse, R extends ServerResponse> is a functional interface and can therefor be implemented with lambdas. The method signature to implement is:

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

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

This allows access to the ServerRequest and after calling next.handle(request) access to the ServerResponse is available.

Example HandlerFilterFunction Implementation

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

This example will show adding a header to both the request and response.

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,我们将标头添加到响应中。

First, a new ServerRequest is created from the existing request. This allows us to add the header using the header() method. Then we call next.handle() passing in the modified ServerRequest. Then using the returned ServerResponse we add the header to the response.

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 标头。

The above route will add a X-Request-Id header to the request and a X-Response-Id header to the response.

Writing Custom Before Filter Implementations

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

The before method takes a Function<ServerRequest, ServerRequest> as a parameter. This allows for creating a new ServerRequest with updated data to be returned from the function.

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

Before functions may be adapted to HandlerFilterFunction instances via HandlerFilterFunction.ofRequestProcessor().

Example Before Filter Implementation

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

In this example we will add a header with a generated value to the request.

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

A new ServerRequest is created from the existing request. This allows us to add the header using the header() method. This implementation is simpler than the HandlerFilterFunction because we only deal with the 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() 方法。

The above route will add a X-Request-Id header to the request. Note the use of the before() method, rather than filter().

Writing Custom After Filter Implementations

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

The after method takes a BiFunction<ServerRequest,ServerResponse,ServerResponse>. This allows access to both the ServerRequest and the ServerResponse and the ability to return a new ServerResponse with updated information.

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

After functions may be adapted to HandlerFilterFunction instances via HandlerFilterFunction.ofResponseProcessor().

Example After Filter Implementation

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

In this example we will add a header with a generated value to the response.

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

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

In this case we simply add the header to the response and return it.

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() 方法。

The above route will add a X-Response-Id header to the response. Note the use of the after() method, rather than filter().

How To Register Custom Predicates and Filters for Configuration

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

To use custom Predicates and Filters in external configuration you need to create a special Supplier class and register it in META-INF/spring.factories.

Registering Custom Predicates

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

To register custom predicates you need to implement PredicateSupplier. The PredicateDiscoverer looks for static methods that return RequestPredicates to register.

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 中。

You then need to add the class in 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

The SimpleFilterSupplier allows for easily registering custom filters. The FilterDiscoverer looks for static methods that return HandlerFilterFunction to register. If you need more flexibility than SimpleFilterSupplier you can implement FilterSupplier directly.

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 中。

You then need to add the class in META-INF/spring.factories.

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