カスタム述語とフィルターの作成

Spring Cloud Gateway サーバー MVC は、API ゲートウェイ機能の基礎として Spring WebMvc.fn API ( javadoc ) を使用します。

Spring Cloud Gateway サーバー MVC は、これらの API を使用して拡張できます。ユーザーは通常、RequestPredicate (Javadoc) HandlerFilterFunction (Javadoc) のカスタム実装と、HandlerFilterFunction の 2 つのバリエーション (「前」フィルター用と「後」フィルター用) を作成することを期待するかもしれません。

基礎

Spring WebMvc.fn API の一部である最も基本的なインターフェースは、ServerRequest ( javadoc ) および ServerResponse ( javadoc ) です。これらは、HTTP リクエストとレスポンスのすべての部分へのアクセスを提供します。

Spring WebMvc.fn ドキュメントでは、「 `ServerRequest` および ServerResponse は不変のインターフェースである」と宣言されています。場合によっては、API ゲートウェイのプロキシ要件を満たすために一部のものを変更できるように、Spring Cloud Gateway サーバー MVC は代替実装を提供する必要があります。

RequestPredicate の実装

Spring WebMvc.fn RouterFunctions.Builder (Javadoc) は、RequestPredicate ( javadoc ) が指定されたルートと一致することを期待します。RequestPredicate は関数型インターフェースであるため、ラムダを使用して実装できます。実装するメソッドシグネチャーは次のとおりです。

boolean test(ServerRequest request)

RequestPredicate の実装例

この例では、特定の HTTP ヘッダーが HTTP リクエストの一部であることをテストするための述語の実装を示します。

Spring WebMvc.fn RequestPredicates (Javadoc) および GatewayRequestPredicates [GitHub] (英語) の 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);
    }
}

実装は、ServerRequest.Headers (Javadoc) オブジェクトを HttpHeaders (Javadoc) のより豊富な API に変換する単純なラムダです。これにより、述語で名前付き header の存在をテストできるようになります。

カスタム RequestPredicate の使用方法

新しい headerExistsRequestPredicate を使用するには、それを route() (Javadoc) などの RouterFunctions.Builder 上の適切なメソッドに接続する必要があります。もちろん、headerExists メソッドのラムダは、以下の例のようにインラインで記述することができます。

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 には、フィルターを追加するための 3 つのオプションがあります: filter (Javadoc) before (Javadoc) after (Javadoc) before および after メソッドは、一般的な filter メソッドを特殊化したものです。

HandlerFilterFunction の実装

filter メソッドは、パラメーターとして HandlerFilterFunction (Javadoc) を受け取ります。HandlerFilterFunction<T extends ServerResponse, R extends ServerResponse> は関数型インターフェースであるため、ラムダを使用して実装できます。実装するメソッドシグネチャーは次のとおりです。

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 を作成できます。

Before 関数は、HandlerFilterFunction.ofRequestProcessor() (Javadoc) を介して 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() メソッドを使用してヘッダーを追加できるようになります。ServerRequest のみを扱うため、この実装は HandlerFilterFunction よりも単純です。

カスタム Before フィルター実装の使用方法

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 ヘッダーが追加されます。filter() ではなく before() メソッドが使用されていることに注意してください。

フィルター実装後のカスタムの作成

after メソッドは BiFunction<ServerRequest,ServerResponse,ServerResponse> を受け取ります。これにより、ServerRequest と ServerResponse の両方にアクセスでき、更新された情報を含む新しい ServerResponse を返すことができます。

After 関数は、HandlerFilterFunction.ofResponseProcessor() (Javadoc) を介して 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;
		};
	}
}

この場合、単にヘッダーをレスポンスに追加して返します。

カスタム After フィルター実装の使用方法

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 ヘッダーが追加されます。filter() ではなく after() メソッドが使用されていることに注意してください。

構成用のカスタム述語とフィルターを登録する方法

外部構成でカスタム述語とフィルターを使用するには、特別な Supplier クラスを作成し、それを META-INF/spring.factories に登録する必要があります。

カスタム述語の登録

カスタム述語を登録するには、PredicateSupplier を実装する必要があります。PredicateDiscoverer は、登録する 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