Spring Web Reactive | 1. Spring WebFlux | 1.5. Functional Endpoints
Spring WebFluxにはWebFlux.fnが含まれています。WebFlux.fnは関数を使用してリクエストをルーティングおよび処理し、不変性(immutability)を持つように設計されています。これはアノテーションベースのプログラミングモデルの代替であり、それ以外は同じリアクティブコアの上で実行されます。
1.5.1. 概要
WebFlux.fnでは、HTTPリクエストはHandlerFunctionで処理されます。これはServerRequestを受け取り、遅延(非同期)されるServerResponse、つまりMono<ServerResponse>を返す関数です。リクエストオブジェクトとレスポンスオブジェクトはいずれも、HTTPリクエストとレスポンスへのJDK 8フレンドリーなアクセスを提供する不変オブジェクトです。HandlerFunctionは、アノテーションベースのプログラミングモデルにおける@RequestMappingメソッドの本体(body)に相当します。
受信リクエストはRouterFunctionを使用してハンドラー関数へ渡されます。これはServerRequestを受け取り、遅延されるHandlerFunction、つまりMono<HandlerFunction>を返す関数です。ルーター関数が一致するとハンドラー関数が返され、そうでなければ空のMonoが返されます。RouterFunctionは@RequestMappingアノテーションに相当しますが、ルーター関数はデータだけでなく動作も提供するという大きな違いがあります。
RouterFunctions.route()は、次の例のようにルーターの作成を容易にするルータービルダーを提供します。
Java
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.reactive.function.server.RequestPredicates.*;
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
PersonRepository repository = ...
PersonHandler handler = new PersonHandler(repository);
RouterFunction<ServerResponse> route = route()
.GET("/person/{id}", accept(APPLICATION_JSON), handler::getPerson)
.GET("/person", accept(APPLICATION_JSON), handler::listPeople)
.POST("/person", handler::createPerson)
.build();
public class PersonHandler {
// ...
public Mono<ServerResponse> listPeople(ServerRequest request) {
// ...
}
public Mono<ServerResponse> createPerson(ServerRequest request) {
// ...
}
public Mono<ServerResponse> getPerson(ServerRequest request) {
// ...
}
}
Kotlin
val repository: PersonRepository = ...
val handler = PersonHandler(repository)
val route = coRouter { // (1)
accept(APPLICATION_JSON).nest {
GET("/person/{id}", handler::getPerson)
GET("/person", handler::listPeople)
}
POST("/person", handler::createPerson)
}
class PersonHandler(private val repository: PersonRepository) {
// ...
suspend fun listPeople(request: ServerRequest): ServerResponse {
// ...
}
suspend fun createPerson(request: ServerRequest): ServerResponse {
// ...
}
suspend fun getPerson(request: ServerRequest): ServerResponse {
// ...
}
}
- (1)
CoroutinesルーターDSLを使用してルーターを作成します。Reactiveな代替もrouter { }で使用できます。
RouterFunctionを実行する1つの方法は、RouterFunctionをHttpHandlerへ変換し、組み込みのサーバーアダプターのいずれかを通じてインストールすることです。
RouterFunctions.toHttpHandler(RouterFunction)RouterFunctions.toHttpHandler(RouterFunction, HandlerStrategies)
ほとんどのアプリケーションはWebFlux Java構成を通じて実行できます。サーバーの実行を参照してください。
1.5.2. HandlerFunction
ServerRequestとServerResponseは、HTTPリクエストとレスポンスへのJDK 8フレンドリーなアクセスを提供する不変インターフェースです。リクエストとレスポンスはいずれも、本文ストリームに対してReactive Streamsのバックプレッシャーを提供します。リクエスト内容はReactor FluxまたはMonoで表されます。レスポンス本文は、FluxやMonoを含むReactive Streams Publisherで表されます。詳細はリアクティブライブラリを参照してください。
ServerRequest
ServerRequestはHTTPメソッド、URI、ヘッダー、クエリパラメーターへのアクセスを提供し、本文へのアクセスはbodyメソッドを通じて提供されます。
次の例では、リクエスト本文をMono<String>として抽出します。
Java
Mono<String> string = request.bodyToMono(String.class);
Kotlin
val string = request.awaitBody<String>()
次の例では、本文をFlux<Person>(またはKotlinのFlow<Person>)として抽出します。PersonオブジェクトはJSONやXMLなどのシリアライズされた形式からデコードされます。
Java
Flux<Person> people = request.bodyToFlux(Person.class);
Kotlin
val people = request.bodyToFlow<Person>()
上の例は、より一般的なServerRequest.body(BodyExtractor)を使いやすくしたもので、BodyExtractor関数型戦略インターフェースを受け取ります。ユーティリティクラスBodyExtractorsは多数のインスタンスへのアクセスを提供します。たとえば、上の例は次のように書けます。
Java
Mono<String> string = request.body(BodyExtractors.toMono(String.class));
Flux<Person> people = request.body(BodyExtractors.toFlux(Person.class));
Kotlin
val string = request.body(BodyExtractors.toMono(String::class.java)).awaitSingle()
val people = request.body(BodyExtractors.toFlux(Person::class.java)).asFlow()
次の例は、フォームデータにアクセスする方法を示しています。
Java
Mono<MultiValueMap<String, String> map = request.formData();
Kotlin
val map = request.awaitFormData()
次の例は、マルチパートデータにMapとしてアクセスする方法を示しています。
Java
Mono<MultiValueMap<String, Part> map = request.multipartData();
Kotlin
val map = request.awaitMultipartData()
次の例は、ストリーミング方式でパートに1つずつアクセスする方法を示しています。
Java
Flux<Part> parts = request.body(BodyExtractors.toParts());
Kotlin
val parts = request.body(BodyExtractors.toParts()).asFlow()
ServerResponse
ServerResponseはHTTPレスポンスへのアクセスを提供します。これは不変なので、buildメソッドを使用してHTTPレスポンスを作成できます。ビルダーを使用してレスポンスステータスを設定したり、レスポンスヘッダーを追加したり、本文を提供したりできます。次の例では、JSONコンテンツを持つ200(OK)レスポンスを作成します。
Java
Mono<Person> person = ...
ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person, Person.class);
Kotlin
val person: Person = ...
ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).bodyValue(person)
次の例は、Locationヘッダーを持ち本文のない201(CREATED)レスポンスを作成する方法を示しています。
Java
URI location = ...
ServerResponse.created(location).build();
Kotlin
val location: URI = ...
ServerResponse.created(location).build()
使用されるコーデックに応じて、ヒントパラメーターを渡して本文のシリアライズまたはデシリアライズ方法を指定できます。たとえば、Jackson JSON Viewを指定できます。
Java
ServerResponse.ok().hint(Jackson2CodecSupport.JSON_VIEW_HINT, MyJacksonView.class).body(...);
Kotlin
ServerResponse.ok().hint(Jackson2CodecSupport.JSON_VIEW_HINT, MyJacksonView::class.java).body(...)
ハンドラークラス(Handler Classes)
次の例のように、ハンドラー関数をラムダとして作成できます。
Java
HandlerFunction<ServerResponse> helloWorld = request -> ServerResponse.ok().bodyValue("Hello World");
Kotlin
val helloWorld = HandlerFunction<ServerResponse> { ServerResponse.ok().bodyValue("Hello World") }
これは便利ですが、アプリケーションでは複数の関数が必要になり、多数のインラインラムダは煩雑になることがあります。関連するハンドラー関数を、アノテーションベースアプリケーションのControllerと同じ役割を持つハンドラークラスにグループ化すると便利です。たとえば、次のクラスはリアクティブなPersonリポジトリに関連する処理を行います。
Java
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.reactive.function.server.ServerResponse.ok;
public class PersonHandler {
private final PersonRepository repository;
public PersonHandler(PersonRepository repository) {
this.repository = repository;
}
public Mono<ServerResponse> listPeople(ServerRequest request) { // (1)
Flux<Person> people = repository.allPeople();
return ok().contentType(APPLICATION_JSON).body(people, Person.class);
}
public Mono<ServerResponse> createPerson(ServerRequest request) { // (2)
Mono<Person> person = request.bodyToMono(Person.class);
return ok().build(repository.savePerson(person));
}
public Mono<ServerResponse> getPerson(ServerRequest request) { // (3)
int personId = Integer.valueOf(request.pathVariable("id"));
return repository.getPerson(personId)
.flatMap(person -> ok().contentType(APPLICATION_JSON).bodyValue(person))
.switchIfEmpty(ServerResponse.notFound().build());
}
}
- (1)
listPeopleは、リポジトリで検索されたすべてのPersonオブジェクトをJSONとして返すハンドラー関数です。 - (2)
createPersonは、リクエスト本文に含まれる新しいPersonを保存するハンドラー関数です。PersonRepository.savePerson(Person)はMono<Void>を返すことに注意してください。空のMonoは、リクエストから読み取ったPersonが保存されたときに完了シグナルを発行します。そのため、build(Publisher<Void>)メソッドを使用して、完了シグナルを受信したとき、つまりPersonが保存されたときにレスポンスを送信します。 - (3)
getPersonは、idパス変数で識別される1人の人物を返すハンドラー関数です。Personリポジトリから検索し、見つかればJSONレスポンスを作成します。見つからなければswitchIfEmpty(Mono<T>)を使用して404 Not Foundレスポンスを返します。
Kotlin
class PersonHandler(private val repository: PersonRepository) {
suspend fun listPeople(request: ServerRequest): ServerResponse { // (1)
val people: Flow<Person> = repository.allPeople()
return ok().contentType(APPLICATION_JSON).bodyAndAwait(people);
}
suspend fun createPerson(request: ServerRequest): ServerResponse { // (2)
val person = request.awaitBody<Person>()
repository.savePerson(person)
return ok().buildAndAwait()
}
suspend fun getPerson(request: ServerRequest): ServerResponse { // (3)
val personId = request.pathVariable("id").toInt()
return repository.getPerson(personId)?.let { ok().contentType(APPLICATION_JSON).bodyValueAndAwait(it) }
?: ServerResponse.notFound().buildAndAwait()
}
}
- (1)
listPeopleは、リポジトリで検索されたすべてのPersonオブジェクトをJSONとして返すハンドラー関数です。 - (2)
createPersonは、リクエスト本文に含まれる新しいPersonを保存するハンドラー関数です。PersonRepository.savePerson(Person)は戻り値のないサスペンド関数であることに注意してください。 - (3)
getPersonは、idパス変数で識別される1人の人物を返すハンドラー関数です。Personリポジトリから検索し、見つかればJSONレスポンスを作成します。なければ404 Not Foundレスポンスを返します。
検証(Validation)
関数型エンドポイントは、Springの検証機能を使用してリクエスト本文に検証を適用できます。次の例は、Person用のカスタムSpring Validator実装を指定した場合です。
Java
public class PersonHandler {
private final Validator validator = new PersonValidator(); // (1)
// ...
public Mono<ServerResponse> createPerson(ServerRequest request) {
Mono<Person> person = request.bodyToMono(Person.class).doOnNext(this::validate); // (2)
return ok().build(repository.savePerson(person));
}
private void validate(Person person) {
Errors errors = new BeanPropertyBindingResult(person, "person");
validator.validate(person, errors);
if (errors.hasErrors()) {
throw new ServerWebInputException(errors.toString()); // (3)
}
}
}
Kotlin
class PersonHandler(private val repository: PersonRepository) {
private val validator = PersonValidator() // (1)
// ...
suspend fun createPerson(request: ServerRequest): ServerResponse {
val person = request.awaitBody<Person>()
validate(person) // (2)
repository.savePerson(person)
return ok().buildAndAwait()
}
private fun validate(person: Person) {
val errors: Errors = BeanPropertyBindingResult(person, "person");
validator.validate(person, errors);
if (errors.hasErrors()) {
throw ServerWebInputException(errors.toString()) // (3)
}
}
}
- (1)
Validatorインスタンスを作成します。 - (2) 検証を適用します。
- (3) 400レスポンスに対して例外を発生させます。
ハンドラーにLocalValidatorFactoryBeanに基づくグローバルValidatorインスタンスを作成して注入することで、標準Bean Validation API(JSR-303)も使用できます。Spring Validationを参照してください。
1.5.3. RouterFunction
ルーター関数を使用して、リクエストを対応するHandlerFunctionへルーティングします。通常、ルーター関数は直接作成せず、RouterFunctionsユーティリティクラスのメソッドを使用して作成します。RouterFunctions.route()(パラメーターなし)はルーター関数を作成するための流れるようなビルダーを提供し、RouterFunctions.route(RequestPredicate, HandlerFunction)はルーターを直接作成する方法を提供します。
一般的にはroute()ビルダーの使用を推奨します。これは、見つけにくいstatic importを必要とせず、一般的なマッピングシナリオに便利なショートカットを提供するためです。たとえば、ルーター関数ビルダーはGETリクエストのマッピングを作成するGET(String, HandlerFunction)メソッドと、POST用のPOST(String, HandlerFunction)を提供します。
HTTPメソッドベースのマッピングに加えて、ルートビルダーはリクエストにマッピングするときに追加の述語を導入する方法を提供します。HTTPメソッドにはRequestPredicateパラメーターを取るオーバーロード版があり、追加の制約を表現できます。
条件(Predicates)
独自のRequestPredicateを作成できますが、RequestPredicatesユーティリティクラスは、リクエストパス、HTTPメソッド、コンテンツタイプなどに基づく一般的な実装を提供します。次の例では、リクエスト条件を使用してAcceptヘッダーに基づく制約を作成します。
Java
RouterFunction<ServerResponse> route = RouterFunctions.route()
.GET("/hello-world", accept(MediaType.TEXT_PLAIN),
request -> ServerResponse.ok().bodyValue("Hello World")).build();
Kotlin
val route = coRouter {
GET("/hello-world", accept(TEXT_PLAIN)) {
ServerResponse.ok().bodyValueAndAwait("Hello World")
}
}
次を使用して、複数のリクエスト条件を組み合わせることができます。
RequestPredicate.and(RequestPredicate)- すべて一致する必要があります。RequestPredicate.or(RequestPredicate)- どちらか一方が一致すればよいです。
多くのRequestPredicates条件は合成されています。たとえば、RequestPredicates.GET(String)はRequestPredicates.method(HttpMethod)とRequestPredicates.path(String)で構成されています。上の例でも、ビルダーが内部でRequestPredicates.GETを使用し、それをaccept条件と合成しているため、2つのリクエスト条件を使用しています。
ルート(Routes)
ルーター関数は順番に評価されます。最初のルートが一致しない場合、2番目のルートが評価されます。一般的なルートより前に、より具体的なルートを宣言するのは意味があります。これは後述するように、ルーター関数をSpring Beanとして登録する場合にも重要です。この動作は、「最も具体的な」コントローラーメソッドが自動的に選択されるアノテーションベースのプログラミングモデルとは異なる点に注意してください。
ルーター関数ビルダーを使用する場合、定義されたすべてのルートはbuild()から返される1つのRouterFunctionに構成されます。複数のルーター関数を一緒に構成する他の方法もあります。
RouterFunctions.route()ビルダーのadd(RouterFunction)RouterFunction.and(RouterFunction)RouterFunction.andRoute(RequestPredicate, HandlerFunction)-RouterFunctions.route()とネストされたRouterFunction.and()の簡潔な形式です。
次の例は、4つのルート構成を示しています。
Java
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.reactive.function.server.RequestPredicates.*;
PersonRepository repository = ...
PersonHandler handler = new PersonHandler(repository);
RouterFunction<ServerResponse> otherRoute = ...
RouterFunction<ServerResponse> route = route()
.GET("/person/{id}", accept(APPLICATION_JSON), handler::getPerson) (1)
.GET("/person", accept(APPLICATION_JSON), handler::listPeople) (2)
.POST("/person", handler::createPerson) (3)
.add(otherRoute) (4)
.build();
Kotlin
import org.springframework.http.MediaType.APPLICATION_JSON
val repository: PersonRepository = ...
val handler = PersonHandler(repository);
val otherRoute: RouterFunction<ServerResponse> = coRouter { }
val route = coRouter {
GET("/person/{id}", accept(APPLICATION_JSON), handler::getPerson) // (1)
GET("/person", accept(APPLICATION_JSON), handler::listPeople) // (2)
POST("/person", handler::createPerson) // (3)
}.and(otherRoute) // (4)
- (1) JSONに一致する
Acceptヘッダーを持つGET /person/{id}はPersonHandler.getPersonへルーティングされます。 - (2) JSONに一致する
Acceptヘッダーを持つGET /personはPersonHandler.listPeopleへルーティングされます。 - (3) 追加条件のない
POST /personはPersonHandler.createPersonへマッピングされます。 - (4)
otherRouteは別の場所で作成され、構築されたルートに追加されるルーター関数です。
ネストされたルート(Nested Routes)
ルーター関数のグループが共有条件(共有パスなど)を持つことは一般的です。上の例では、共有条件は3つのルートで使用される/personに一致するパス条件です。アノテーションを使用する場合、/personにマッピングされた型レベルの@RequestMappingアノテーションを使用して、この重複を削除します。WebFlux.fnでは、ルーティング条件はルーター関数ビルダーのpathメソッドを通じて共有できます。たとえば、上の例の最後の数行は、ネストされたパスを使用して次のように改善できます。
Java
RouterFunction<ServerResponse> route = route()
.path("/person", builder -> builder (1)
.GET("/{id}", accept(APPLICATION_JSON), handler::getPerson)
.GET(accept(APPLICATION_JSON), handler::listPeople)
.POST("/person", handler::createPerson))
.build();
- (1)
pathの2番目のパラメーターは、ルータービルダーを使用するConsumerであることに注意してください。
Kotlin
val route = coRouter {
"/person".nest {
GET("/{id}", accept(APPLICATION_JSON), handler::getPerson)
GET(accept(APPLICATION_JSON), handler::listPeople)
POST("/person", handler::createPerson)
}
}
パスベースのネストが最も一般的ですが、Builderのnestメソッドを使用して任意の種類の条件でネストできます。上の例には、共有されたAcceptヘッダー条件という形でまだ重複が含まれています。nestメソッドとacceptを一緒に使用すると、さらに改善できます。
Java
RouterFunction<ServerResponse> route = route()
.path("/person", b1 -> b1
.nest(accept(APPLICATION_JSON), b2 -> b2
.GET("/{id}", handler::getPerson)
.GET(handler::listPeople))
.POST("/person", handler::createPerson))
.build();
Kotlin
val route = coRouter {
"/person".nest {
accept(APPLICATION_JSON).nest {
GET("/{id}", handler::getPerson)
GET(handler::listPeople)
POST("/person", handler::createPerson)
}
}
}
1.5.4. サーバーの実行
HTTPサーバーでルーター関数をどのように実行するのでしょうか。簡単なオプションは、次のいずれかを使用してルーター関数をHttpHandlerへ変換することです。
RouterFunctions.toHttpHandler(RouterFunction)RouterFunctions.toHttpHandler(RouterFunction, HandlerStrategies)
その後、サーバー固有の手順に従ってHttpHandlerを実行することで、返されたHttpHandlerをさまざまなサーバーアダプターで使用できます。
Spring Bootでも使用される一般的なオプションは、WebFlux構成を通じてDispatcherHandlerベースのセットアップで実行することです。WebFlux構成は、Spring設定を使用してリクエストを処理するために必要なコンポーネントを宣言します。WebFlux Java構成は、関数型エンドポイントをサポートするために次のインフラストラクチャコンポーネントを宣言します。
RouterFunctionMapping: Spring設定で1つ以上のRouterFunction<?>Beanを検出し、順序付け可能なRouterFunction.andOtherを通じてそれらを結合し、その結果生成されたRouterFunctionへリクエストをルーティングします。HandlerFunctionAdapter:DispatcherHandlerがリクエストにマッピングされたHandlerFunctionを呼び出せるようにする簡単なアダプターです。ServerResponseResultHandler:ServerResponseのwriteToメソッドを呼び出して、HandlerFunction呼び出しの結果を処理します。
上記のコンポーネントにより、関数型エンドポイントはDispatcherHandlerのリクエスト処理ライフサイクルに適合し、アノテーション付きコントローラーがある場合はそれと並行して実行される可能性があります。これはSpring Boot WebFluxスターターが関数型エンドポイントを使用する方法でもあります。
次の例はWebFlux Java構成を示しています(実行方法はDispatcherHandlerを参照してください)。
Java
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {
@Bean
public RouterFunction<?> routerFunctionA() {
// ...
}
@Bean
public RouterFunction<?> routerFunctionB() {
// ...
}
// ...
@Override
public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
// configure message conversion...
}
@Override
public void addCorsMappings(CorsRegistry registry) {
// configure CORS...
}
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
// configure view resolution for HTML rendering...
}
}
Kotlin
@Configuration
@EnableWebFlux
class WebConfig : WebFluxConfigurer {
@Bean
fun routerFunctionA(): RouterFunction<*> {
// ...
}
@Bean
fun routerFunctionB(): RouterFunction<*> {
// ...
}
// ...
override fun configureHttpMessageCodecs(configurer: ServerCodecConfigurer) {
// configure message conversion...
}
override fun addCorsMappings(registry: CorsRegistry) {
// configure CORS...
}
override fun configureViewResolvers(registry: ViewResolverRegistry) {
// configure view resolution for HTML rendering...
}
}
1.5.5. ハンドラー関数のフィルタリング
ルーティング関数ビルダーでbefore、after、またはfilterメソッドを使用して、ハンドラー関数をフィルタリングできます。アノテーションを使用する場合、@ControllerAdvice、ServletFilter、またはその両方で類似の機能を提供します。フィルターはビルダーによって作成されたすべてのルートに適用されます。これは、ネストされたルートで定義されたフィルターが「最上位」のルートには適用されないことを意味します。たとえば、次の例を見てみましょう。
Java
RouterFunction<ServerResponse> route = route()
.path("/person", b1 -> b1
.nest(accept(APPLICATION_JSON), b2 -> b2
.GET("/{id}", handler::getPerson)
.GET(handler::listPeople)
.before(request -> ServerRequest.from(request) // (1)
.header("X-RequestHeader", "Value")
.build()))
.POST("/person", handler::createPerson))
.after((request, response) -> logResponse(response)) // (2)
.build();
Kotlin
val route = router {
"/person".nest {
GET("/{id}", handler::getPerson)
GET("", handler::listPeople)
before { // (1)
ServerRequest.from(it)
.header("X-RequestHeader", "Value").build()
}
POST("/person", handler::createPerson)
after { _, response -> // (2)
logResponse(response)
}
}
}
- (1) カスタムリクエストヘッダーを追加する
beforeフィルターは、2つのGETルートにのみ適用されます。 - (2) レスポンスを記録する
afterフィルターは、ネストされたものを含むすべてのルートに適用されます。
ルータービルダーのfilterメソッドは、HandlerFilterFunctionを引数に取ります。これはServerRequestとHandlerFunctionを受け取り、ServerResponseを返す関数です。ハンドラー関数パラメーターはチェーン内の次の要素を表します。通常はルーティングされたハンドラーですが、複数適用される場合は別のフィルターになることもあります。
特定のパスが許可されるかどうかを判断できるSecurityManagerがあると仮定すると、ルートに簡単なセキュリティフィルターを追加できます。次の例はその方法を示しています。
Java
SecurityManager securityManager = ...
RouterFunction<ServerResponse> route = route()
.path("/person", b1 -> b1
.nest(accept(APPLICATION_JSON), b2 -> b2
.GET("/{id}", handler::getPerson)
.GET(handler::listPeople))
.POST("/person", handler::createPerson))
.filter((request, next) -> {
if (securityManager.allowAccessTo(request.path())) {
return next.handle(request);
}
else {
return ServerResponse.status(UNAUTHORIZED).build();
}
})
.build();
Kotlin
val securityManager: SecurityManager = ...
val route = router {
("/person" and accept(APPLICATION_JSON)).nest {
GET("/{id}", handler::getPerson)
GET("", handler::listPeople)
POST("/person", handler::createPerson)
filter { request, next ->
if (securityManager.allowAccessTo(request.path())) {
next(request)
}
else {
status(UNAUTHORIZED).build();
}
}
}
}
上の例は、next.handle(ServerRequest)の呼び出しが任意であることを示しています。アクセスが許可された場合にのみ、ハンドラー関数を実行します。
ルーター関数ビルダーでfilterメソッドを使用する以外にも、RouterFunction.filter(HandlerFilterFunction)を通じて既存のルーター関数にフィルターを適用できます。
関数型エンドポイントのCORSサポートは、専用の
CorsWebFilterを通じて提供されます。