Spring WebFlux содержит WebFlux.fn, облегченную модель функционального программирования, в которой функции используются для маршрутизации и обработки запросов, а контракты разработаны таким образом, чтобы обеспечивать неизменяемость. Она является альтернативой модели программирования на основе аннотаций, но в остальном работает на той же основе из Reactive Core.

Краткое описание

В WebFlux.fn HTTP-запрос обрабатывается с помощью HandlerFunction: функции, которая принимает ServerRequest и возвращает отложенный ServerResponse (т.е. Mono<ServerResponse>). И объект-запрос, и объект-ответ имеют неизменяемые контракты, которые обеспечивают доступ к запросу и ответу HTTP, совместимый с JDK 8. HandlerFunction – это эквивалент тела метода с аннотацией @RequestMapping в модели программирования на основе аннотаций.

Входящие запросы направляются в функцию-обработчик с помощью 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 { 
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. Create router using Coroutines router DSL, a Reactive alternative is also available via router { }.

Один из способов запустить RouterFunction – превратить ее в HttpHandler и установить через один из встроенных серверных адаптеров:

  • RouterFunctions.toHttpHandler(RouterFunction)

  • RouterFunctions.toHttpHandler(RouterFunction, HandlerStrategies)

Большинство приложений можно запускать через Java-конфигурацию WebFlux.

HandlerFunction

ServerRequest и ServerResponse - это неизменяемые интерфейсы, которые обеспечивают доступ к HTTP-запросу и ответу, совместимый с JDK 8. И запрос, и ответ обеспечивают обратную реакцию Reactive Streams для потоков тела. Тело запроса представлено с помощью Flux или Mono из Reactor. Тело ответа представляется с помощью любого Publisher из Reactive Streams, включая Flux и Mono.

ServerRequest

ServerRequest предоставляет доступ к HTTP-методу, URI-идентификатору, заголовкам и параметрам запроса, а доступ к телу предоставляется через методы body.

В следующем примере тело запроса извлекается в Mono<String>:

Java
Mono<String> string = request.bodyToMono(String.class);
Kotlin
val string = request.awaitBody<String>()

В следующем примере тело извлекается в Flux<Person> (или Flow<Person> в Kotlin), где объекты 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()

В следующем примере показано, как получить доступ к нескольким компонентам, по одному за раз, в потоковом режиме:

Java
Flux<Part> parts = request.body(BodyExtractors.toParts());
Kotlin
val parts = request.body(BodyExtractors.toParts()).asFlow()

ServerResponse

ServerResponse обеспечивает доступ к HTTP-ответу, и, поскольку он неизменяемый, то можно использовать метод build для его создания. Вы можете использовать средство сборки для установки статуса ответа, добавления заголовков ответа или предоставления тела ответа. В следующем примере создается ответ 200 (OK) с содержимым в формате JSON:

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)

В следующем примере показано, как создать ответ 201 (CREATED) с заголовком Location и без тела:

Java
URI location = ...
ServerResponse.created(location).build();
Kotlin
val location: URI = ...
ServerResponse.created(location).build()

В зависимости от используемого кодека, можно передать параметры подсказки, чтобы кастомно настроить способ сериализации или десериализации тела. Например, чтобы задать представление на основе Jackson JSON:

Java
ServerResponse.ok().hint(Jackson2CodecSupport.JSON_VIEW_HINT, MyJacksonView.class).body(...);
Kotlin
ServerResponse.ok().hint(Jackson2CodecSupport.JSON_VIEW_HINT, MyJacksonView::class.java).body(...)

Классы обработчиков

Можно написать функцию-обработчик в виде лямбда-выражения, как показано в следующем примере:

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) { 
Flux<Person> people = repository.allPeople();
return ok().contentType(APPLICATION_JSON).body(people, Person.class);
}
public Mono<ServerResponse> createPerson(ServerRequest request) { 
Mono<Person> person = request.bodyToMono(Person.class);
return ok().build(repository.savePerson(person));
}
public Mono<ServerResponse> getPerson(ServerRequest request) { 
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 – это функция-обработчик, которая возвращает один объект Person, идентифицированный переменной пути id. Мы извлекаем этот объект Person из хранилища и создаем ответ в формате JSON, если он будет найден. Если он не будет найден, то используется switchIfEmpty(Mono<T>), чтобы вернуть ответ 404 Not Found.
Kotlin
class PersonHandler(private val repository: PersonRepository) {
suspend fun listPeople(request: ServerRequest): ServerResponse { 
val people: Flow<Person> = repository.allPeople()
return ok().contentType(APPLICATION_JSON).bodyAndAwait(people);
}
suspend fun createPerson(request: ServerRequest): ServerResponse { 
val person = request.awaitBody<Person>()
repository.savePerson(person)
return ok().buildAndAwait()
}
suspend fun getPerson(request: ServerRequest): ServerResponse { 
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) возвращает Mono<Void>: пустой Mono, который генерирует сигнал завершения, если Person был прочитан из запроса и сохранен. Поэтому метод build(Publisher<Void>) используется для отправки ответа, когда будет получен сигнал завершения (то есть когда Person будет сохранена).
  3. getPerson – это функция-обработчик, которая возвращает один объект Person, идентифицированный переменной пути id. Мы извлекаем этот объект Person из хранилища и создаем ответ в формате JSON, если он будет найден. Если он не будет найден, то используется switchIfEmpty(Mono<T>), чтобы вернуть ответ 404 Not Found.

Валидация

Функциональная конечная точка может задействовать средства проверки Spring для применения валидации к телу запроса. Например, дана кастомная реализация Validator из Spring для объекта Person:

Java
public class PersonHandler {
private final Validator validator = new PersonValidator(); 
// ...
public Mono<ServerResponse> createPerson(ServerRequest request) {
Mono<Person> person = request.bodyToMono(Person.class).doOnNext(this::validate); 
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()); 
}
}
}
  1. Создаем экземпляр Validator.
  2. Применяем валидацию.
  3. Генерируем исключение при ответе 400.
Kotlin
class PersonHandler(private val repository: PersonRepository) {
private val validator = PersonValidator() 
// ...
suspend fun createPerson(request: ServerRequest): ServerResponse {
val person = request.awaitBody<Person>()
validate(person) 
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()) 
}
}
}
  1. Создаем экземпляр Validator.
  2. Применяем валидацию.
  3. Генерируем исключение при ответе 400.

Обработчики также могут использовать стандартный API валидации бинов (JSR-303), создавая и внедряя глобальный экземпляр Validator на основе LocalValidatorFactoryBean.

RouterFunction

Функции-маршрутизаторы используются для маршрутизации запросов к соответствующим HandlerFunction. Как правило, функции-маршрутизаторы пишутся не самостоятельно, а для их создания используется метод для вспомогательного класса RouterFunctions. RouterFunctions.route() (без параметров) предоставляет удобное средство сборки для создания функции-маршрутизатора, в то время как RouterFunctions.route(RequestPredicate, HandlerFunction) предлагает прямой способ создания маршрутизатора.

Как правильно, рекомендуется использовать средство сборки route(), поскольку оно обеспечивает удобные сокращения для типичных сценариев отображения, не требуя импортирования статических элементов, которые трудно обнаружить. Например, средство сборки функций-маршрутизаторов предлагает метод GET(String, HandlerFunction) для создания Map для GET-запросов; и POST(String, HandlerFunction) - для POST-запросов.

Помимо отображения на основе HTTP-методов, средство сборки маршрутов предлагает способ введения дополнительных предикатов при отображении на запросы. Для каждого HTTP-метода существует перегруженный вариант, который принимает RequestPredicate в качестве параметра, через который могут быть выражены дополнительные ограничения.

Предикаты

Вы можете написать свой собственный 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.

Маршруты

Функции-маршрутизаторы вычисляются упорядоченно: если первый маршрут не совпадает, то вычисляется второй, и так далее. Поэтому имеет смысл объявлять более конкретные маршруты перед обобщенными. Это также важно при регистрации функций-маршрутизаторов в качестве бинов Spring, как будет описано позже. Обратите внимание, что такая логика работы отличается от модели программирования на основе аннотаций, где "наиболее конкретный" метод контроллера выбирается автоматически.

При использовании средства сборки функций-маршрутизаторов все определенные маршруты компонуются в одну RouterFunction, которая возвращается из build(). Существуют и другие способы компоновки нескольких функций маршрутизатора в одну:

  • add(RouterFunction) on the RouterFunctions.route() builder

  • RouterFunction.and(RouterFunction)

  • RouterFunction.andRoute(RequestPredicate, HandlerFunction) - сокращение для RouterFunction.and() с вложенными RouterFunctions.route().

В следующем примере показана компоновка из четырех маршрутов:

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) 
.GET("/person", accept(APPLICATION_JSON), handler::listPeople) 
.POST("/person", handler::createPerson) 
.add(otherRoute) 
.build();
  1. GET /person/{id} with an Accept header that matches JSON is routed to PersonHandler.getPerson
  2. GET /person с заголовком Accept, который соответствует формату JSON, направляется в PersonHandler.listPeople
  3. POST /person без дополнительных предикатов отображается на PersonHandler.createPerson, и
  4. otherRoute – это функция-маршрутизатор, которая создается в другом месте и добавляется к построенному маршруту.
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) 
GET("/person", accept(APPLICATION_JSON), handler::listPeople) 
POST("/person", handler::createPerson) 
}.and(otherRoute) 
  1. GET /person/{id} with an Accept header that matches JSON is routed to PersonHandler.getPerson
  2. GET /person с заголовком Accept, который соответствует формату JSON, направляется в PersonHandler.listPeople
  3. POST /person без дополнительных предикатов отображается на PersonHandler.createPerson, и
  4. otherRoute – это функция-маршрутизатор, которая создается в другом месте и добавляется к построенному маршруту.

Вложенные маршруты

Обычно группа функций-маршрутизаторов имеет общий предикат, например, общий путь. В приведенном выше примере общим предикатом будет предикат пути, который соответствует /person, используемый тремя маршрутами. При использовании аннотаций можно устранить это дублирование, используя аннотацию @RequestMapping на уровне типа, которая отображается на /person. В WebFlux.fn предикаты пути могут совместно использоваться с помощью метода path в конструкторе функций-маршрутизаторов. Например, последние несколько строк приведенного выше примера можно дополнить следующим образом, используя вложенные маршруты:

Java
RouterFunction<ServerResponse> route = route()
.path("/person", builder -> builder 
.GET("/{id}", accept(APPLICATION_JSON), handler::getPerson)
.GET(accept(APPLICATION_JSON), handler::listPeople)
.POST(handler::createPerson))
.build();
  1. Обратите внимание, что второй параметр path – это получатель, который принимает средство сборки маршрутизатора.
Kotlin
val route = coRouter {
"/person".nest {
GET("/{id}", accept(APPLICATION_JSON), handler::getPerson)
GET(accept(APPLICATION_JSON), handler::listPeople)
POST(handler::createPerson)
}
}

Хотя вложение на основе пути является наиболее распространенным, можно вложить предикат любого типа, используя метод nest в средстве сборки. Вышеприведенный вариант все еще содержит некоторое дублирование в виде общего предиката Accept-header. Мы продолжить дополнять код, используя метод 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(handler::createPerson))
.build();
Kotlin
val route = coRouter {
"/person".nest {
accept(APPLICATION_JSON).nest {
    GET("/{id}", handler::getPerson)
    GET(handler::listPeople)
    POST(handler::createPerson)
}
}
}

Запуск сервера

Как запустить функцию маршрутизатора в HTTP-сервере? Простым вариантом является преобразование функции-маршрутизатора в HttpHandler с помощью одного из следующих способов:

  • RouterFunctions.toHttpHandler(RouterFunction)

  • RouterFunctions.toHttpHandler(RouterFunction, HandlerStrategies)

Затем вы можете использовать возвращенный HttpHandler с различными серверными адаптерами, следуя инструкциям касательно HttpHandler для конкретного сервера.

Более типичным вариантом, также используемым в Spring Boot, является работа с конфигурацией на основе DispatcherHandler через конфигурацию WebFlux, которая использует Spring-конфигурацию для объявления компонентов, необходимых для обработки запросов. Java-конфигурация WebFlux объявляет следующие компоненты инфраструктуры для поддержки функциональных конечных точек:

  • RouterFunctionMapping: Обнаруживает один или несколько бинов RouterFunction<?> в конфигурации Spring, упорядочивает их, объединяет с помощью RouterFunction.andOther и направляет запросы к результирующей составной RouterFunction.

  • HandlerFunctionAdapter: Простой адаптер, позволяющий DispatcherHandler вызывать HandlerFunction, которая была отображена на запрос.

  • ServerResponseResultHandler: Обрабатывает результат вызова HandlerFunction, вызывая метод writeTo в ServerResponse.

Предыдущие компоненты позволяют функциональным конечным точкам "вписаться" в жизненный цикл обработки запросов DispatcherServlet, а также (потенциально) работать с аннотированными контроллерами, если таковые объявлены, одновременно. Это также способ активации функциональных конечных точек с помощью стартера из Spring Boot для WebFlux.

В следующем примере показана Java-конфигурация WebFlux:

Java
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {
@Bean
public RouterFunction<?> routerFunctionA() {
// ...
}
@Bean
public RouterFunction<?> routerFunctionB() {
// ...
}
// ...
@Override
public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
// конфигурируем преобразование сообщений...
}
@Override
public void addCorsMappings(CorsRegistry registry) {
// конфигурируем CORS...
}
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
// конфигурируем разрешение представления для HTML-визуализации...
}
}
Kotlin
@Configuration
@EnableWebFlux
class WebConfig : WebFluxConfigurer {
@Bean
fun routerFunctionA(): RouterFunction<*> {
// ...
}
@Bean
fun routerFunctionB(): RouterFunction<*> {
// ...
}
// ...
override fun configureHttpMessageCodecs(configurer: ServerCodecConfigurer) {
// конфигурируем преобразование сообщений...
}
override fun addCorsMappings(registry: CorsRegistry) {
// конфигурируем CORS...
}
override fun configureViewResolvers(registry: ViewResolverRegistry) {
// конфигурируем разрешение представления для HTML-визуализации...
}
}

Функции-обработчики фильтрации

Вы можете фильтровать функции-обработчики с помощью методов 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) 
        .header("X-RequestHeader", "Value")
        .build()))
.POST(handler::createPerson))
.after((request, response) -> logResponse(response)) 
.build();
  1. Фильтр before, добавляющий пользовательский заголовок запроса, применяется только к двум маршрутам метода GET.
  2. Фильтр after, регистрирующий ответ, применяется ко всем маршрутам, включая вложенные.
Kotlin
val route = router {
"/person".nest {
GET("/{id}", handler::getPerson)
GET("", handler::listPeople)
before { 
    ServerRequest.from(it)
            .header("X-RequestHeader", "Value").build()
}
POST(handler::createPerson)
after { _, response -> 
    logResponse(response)
}
}
}
  1. Фильтр before, добавляющий пользовательский заголовок запроса, применяется только к двум маршрутам метода 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(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(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.