編寫自定義 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