撰寫自訂謂詞與篩選器

Spring Cloud Gateway Server MVC 使用 Spring WebMvc.fn API (javadoc) 作為 API 閘道功能的基礎。

Spring Cloud Gateway Server MVC 可使用這些 API 進行擴充。使用者通常可能期望撰寫 RequestPredicateHandlerFilterFunction 的自訂實作,以及 HandlerFilterFunction 的兩種變體,一種用於「前置」篩選器,另一種用於「後置」篩選器。

基礎知識

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) 以比對給定的 路由RequestPredicate 是一個函數介面,因此可以使用 lambda 實作。要實作的方法簽章為

boolean test(ServerRequest request)

RequestPredicate 實作範例

在此範例中,我們將展示謂詞的實作,以測試特定 HTTP 標頭是否為 HTTP 請求的一部分。

Spring WebMvc.fn 中的 RequestPredicate 實作 RequestPredicatesGatewayRequestPredicates 皆實作為 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 物件轉換為更豐富的 API HttpHeaders。這允許謂詞測試是否存在指定的 header

如何使用自訂 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 的標頭時,將會比對上述路由。

撰寫自訂 HandlerFilterFunction 實作

RouterFunctions.Builder 具有三個選項可新增篩選器: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());
			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.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 標頭新增至回應。

撰寫自訂前置篩選器實作

before 方法採用 Function<ServerRequest, ServerRequest> 作為參數。這允許建立新的 ServerRequest,其中包含要從函數傳回的更新資料。

前置函數可以透過 HandlerFilterFunction.ofRequestProcessor() 適配至 HandlerFilterFunction 實例。

前置篩選器實作範例

在此範例中,我們將新增一個具有產生值的標頭至請求。

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

如何使用自訂前置篩選器實作

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

撰寫自訂後置篩選器實作

after 方法採用 BiFunction<ServerRequest,ServerResponse,ServerResponse>。這允許存取 ServerRequestServerResponse,並能夠傳回具有更新資訊的新 ServerResponse

後置函數可以透過 HandlerFilterFunction.ofResponseProcessor() 適配至 HandlerFilterFunction 實例。

後置篩選器實作範例

在此範例中,我們將新增一個具有產生值的標頭至回應。

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

在此案例中,我們僅將標頭新增至回應並傳回。

如何使用自訂後置篩選器實作

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

如何註冊用於組態的自訂謂詞和篩選器

若要在外部組態中使用自訂謂詞和篩選器,您需要建立特殊的 Supplier 類別,並在 META-INF/spring.factories 中註冊它。

註冊自訂謂詞

若要註冊自訂謂詞,您需要實作 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

註冊自訂篩選器

SimpleFilterSupplier 允許輕鬆註冊自訂篩選器。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