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, а 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, як буде описано пізніше. Зверни увагу, що така логіка роботи відрізняється від моделі програмування на основі анотацій, де "найконкретніший" метод контролера вибирається автоматично, яка повертається з 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. та
  5. 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.