编写自定义 Predicate 和 Filter

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

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

基础

Spring WebMvc.fn API 中最基本的接口是 ServerRequest (javadoc) 和 ServerResponse (javadoc)。它们提供了对 HTTP 请求和响应所有部分的访问。

Spring WebMvc.fn 文档 声明 “`ServerRequest` 和 `ServerResponse` 是不可变接口。在某些情况下,Spring Cloud Gateway Server MVC 必须提供备用实现,以便某些内容可以变为可变,以满足 API 网关的代理需求。

实现 RequestPredicate

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

boolean test(ServerRequest request)

RequestPredicate 实现示例

本示例将展示一个 predicate 的实现,用于测试特定的 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。这允许 predicate 测试指定 header 是否存在。

如何使用自定义 RequestPredicate

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

RouteConfiguration.java
import static SampleRequestPredicates.headerExists;
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.uri;
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())
            .before(uri("https://example.org"))
            .build();
    }
}

当 HTTP 请求包含名为 X-Green 的头部时,上述路由将被匹配。

编写自定义 HandlerFilterFunction 实现

RouterFunctions.Builder 有三种添加 filter 的选项:filterbeforeafterbeforeafter 方法是通用 filter 方法的特化。

实现 HandlerFilterFunction

filter 方法将 HandlerFilterFunction 作为参数。HandlerFilterFunction<T extends ServerResponse, R extends ServerResponse> 是一个函数式接口,因此可以使用 lambda 实现。需要实现的方法签名是

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

这允许访问 ServerRequest,并且在调用 next.handle(request) 后,可以访问 ServerResponse

HandlerFilterFunction 实现示例

本示例将展示如何向请求和响应都添加头部。

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()).build();
			ServerResponse response = next.handle(modified);
			response.headers().add(responseHeader, generateId());
			return response;
		};
	}
}

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

如何使用自定义 HandlerFilterFunction 实现

RouteConfiguration.java
import static SampleHandlerFilterFunctions.instrument;
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.uri;
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())
				.filter(instrument("X-Request-Id", "X-Response-Id"))
                .before(uri("https://example.org"))
                .build();
    }
}

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

编写自定义 Before Filter 实现

before 方法将 Function<ServerRequest, ServerRequest> 作为参数。这允许创建带有更新数据的新 ServerRequest,并从函数返回。

Before 函数可以通过 HandlerFilterFunction.ofRequestProcessor() 适应于 HandlerFilterFunction 实例。

Before Filter 实现示例

本示例中,我们将向请求添加一个带有生成值的头部。

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()).build();
	}
}

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

如何使用自定义 Before Filter 实现

RouteConfiguration.java
import static SampleBeforeFilterFunctions.instrument;
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.uri;
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())
            .before(uri("https://example.org"))
            .before(instrument("X-Request-Id"))
            .build();
    }
}

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

编写自定义 After Filter 实现

after 方法接受一个 BiFunction<ServerRequest,ServerResponse,ServerResponse>。这允许访问 ServerRequestServerResponse,并能够返回带有更新信息的新 ServerResponse

After 函数可以通过 HandlerFilterFunction.ofResponseProcessor() 适应于 HandlerFilterFunction 实例。

After Filter 实现示例

本示例中,我们将向响应添加一个带有生成值的头部。

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

在这种情况下,我们只需将头部添加到响应并返回。

如何使用自定义 After Filter 实现

RouteConfiguration.java
import static SampleAfterFilterFunctions.instrument;
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.uri;
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())
            .before(uri("https://example.org"))
            .after(instrument("X-Response-Id"))
            .build();
    }
}

上述路由将向响应添加 X-Response-Id 头部。注意使用了 after() 方法,而不是 filter()

如何注册自定义 Predicate 和 Filter 以用于配置

要在外部配置中使用自定义 Predicate 和 Filter,您需要创建一个特殊的 Supplier 类并将其注册在 META-INF/spring.factories 中。

注册自定义 Predicate

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

SampleFilterSupplier.java

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

注册自定义 Filter

SimpleFilterSupplier 允许轻松注册自定义 filter。FilterDiscoverer 查找返回 HandlerFilterFunction 的静态方法进行注册。如果您需要比 SimpleFilterSupplier 更大的灵活性,可以直接实现 FilterSupplier

SampleFilterSupplier.java
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