Spring Web MVC містить WebMvc.fn, легку функціональну модель програмування, в якій функції використовуються для маршрутизації та обробки запитів, а контракти розроблені для забезпечення незмінності. Вона є альтернативою моделі програмування на основі анотацій, але в іншому працює на тому ж DispatcherServlet. code>: функція, яка приймає ServerRequest і повертає ServerResponse. І запит, і об'єкт-відповідь мають незмінні контракти, які забезпечують доступ до HTTP-запиту та відповіді, зручний для JDK 8. HandlerFunction — це еквівалент тіла методу, позначеного анотацією @RequestMapping, в моделі програмування на основі анотацій.

Вхідні запити надсилаються до функції-обробника за допомогою RouterFunction: функції, яка приймає ServerRequest і повертає необов'язкову HandlerFunction (тобто Optional<HandlerFunction>). Якщо функція-маршрутизатор збігається, повертається функція-обробник, інакше — порожній Optional. RouterFunction — це еквівалент анотації @RequestMapping, але з тією істотною відмінністю, що функції-маршрутизатори передають не лише дані, а й логіку роботи.

RouterFunctions.route() надає засіб складання маршрутизаторів, який полегшує створення маршрутизаторів, як показано в наступному прикладі:

Java

import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.servlet.function.RequestPredicates.*;
import static org.springframework.web.servlet.function.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 ServerResponse listPeople(ServerRequest request) {
        // ...
    }
    public ServerResponse createPerson(ServerRequest request) {
        // ...
    }
    public ServerResponse getPerson(ServerRequest request) {
        // ...
    }
}
Kotlin

import org.springframework.web.servlet.function.router
val repository: PersonRepository = ...
val handler = PersonHandler(repository)
val route = router { 
    accept(APPLICATION_JSON).nest {
        GET("/person/{id}", handler::getPerson)
        GET("/person", handler::listPeople)
    }
    POST("/person", handler::createPerson)
}
class PersonHandler(private val repository: PersonRepository) {
    // ...
    fun listPeople(request: ServerRequest): ServerResponse {
        // ...
    }
    fun createPerson(request: ServerRequest): ServerResponse {
        // ...
    }
    fun getPerson(request: ServerRequest): ServerResponse {
        // ...
    }
}
  1. Create router using the router DSL.

Якщо зареєструвати RouterFunction як бін, наприклад, відкривши його в класі з анотацією @Configuration, він буде автоматично виявлений сервлетом.

HandlerFunction

ServerRequest та ServerResponse — це незмінні інтерфейси, які надають доступ до HTTP-запиту та відповіді, включно з заголовками, тілом, методом і кодом стану, зручний для JDK 8.

ServerRequest

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

У наступному прикладі тіло запиту витягується в String:

Java
String string = request.body (String.class);
Kotlin
val string = request.body<String>()

У наступному прикладі тіло витягується в List<Person>, де об'єкти Person декодуються із серіалізованої форми, такої як формат JSON або XML:

Java
List<Person> people = request.body(new ParameterizedTypeReference<List<Person>>() {});
Kotlin
val people = request.body<Person>()

У наступному прикладі показано, як отримати доступ до параметрів:

Java
MultiValueMap<String, String> params = request.params();
Kotlin
val map = request.params()

ServerResponse

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

Java
Person person = ...
ServerResponse.ok().contentType(MediaType.APPLICATION_JSON ).body(person);
Kotlin
val person: Person = ...
ServerResponse.ok( ).contentType(MediaType.APPLICATION_JSON).body(person)

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

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

Також можна використовувати в якості тіла асинхронний результат у вигляді CompletableFuture, Publisher або будь-якого іншого типу, що підтримується ReactiveAdapterRegistry. Наприклад:

Java

Mono<Person> person = webClient.get().retrieve().bodyToMono(Person.class);
ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person);
        
Kotlin

val person = webClient.get().retrieve().awaitBody<Person>()
ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person)
        

Якщо не тільки тіло, але також статус або заголовки засновані на асинхронному типі, можна використовувати статичний метод async для ServerResponse, який приймає CompletableFuture<ServerResponse> , Publisher<ServerResponse>, або будь-який інший асинхронний тип, що підтримується ReactiveAdapterRegistry. Наприклад:

Java

Mono<ServerResponse> asyncResponse = webClient.get().retrieve().bodyToMono(Person.class)
    .map(p -> ServerResponse.ok().header("Name", p.name()).body(p));
ServerResponse.async(asyncResponse);

Події, надіслані сервером, можуть бути передані за допомогою статичного методу sse для ServerResponse. Засіб збирання, наданий цим методом, дозволяє надсилати рядки або інші об'єкти у форматі JSON. Наприклад:

Java

public RouterFunction<ServerResponse> sse() {
    return route(GET("/sse"), request -> ServerResponse.sse(sseBuilder -> {
                // Save the sseBuilder object somewhere...
            }));
}
// У якомусь іншому потоці, що відправляє рядок
sseBuilder.send("Hello world");
// Або об'єкт, який буде перетворений на JSON
Person person = ...
sseBuilder.send(person);
// Налаштовуємо подію за допомогою інших методів
sseBuilder.id("42")
        .event("sse event")
        .data(person);
// і в якийсь момент буде готово
sseBuilder.complete();
Kotlin

fun sse( ): RouterFunction<ServerResponse> = router {
    GET("/sse") { request -> ServerResponse.sse { sseBuilder ->
        // Save the sseBuilder object somewhere..
    }
}
// У якомусь іншому потоці, що відправляє рядок
sseBuilder.send("Hello world")
// Або об'єкті, який буде перетворений на JSON
val person = ...
sseBuilder.send (person)
// Налаштовуємо подію за допомогою інших методів
sseBuilder.id("42")
        .event("sse event")
        .data(person)
// і в якийсь момент буде готово
sseBuilder.complete()

Класи обробників

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

Java

HandlerFunction<ServerResponse> helloWorld =
    request -> ServerResponse.ok().body("Hello World");
        
Kotlin

val helloWorld: (ServerRequest ) -> ServerResponse =
    { ServerResponse.ok().body("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 ServerResponse listPeople(ServerRequest request) { 
        List<Person> people = repository.allPeople();
        return ok().contentType(APPLICATION_JSON).body(people);
    }
    public ServerResponse createPerson(ServerRequest request) throws Exception { 
        Person person = request.body(Person.class);
        repository.savePerson(person);
        return ok().build();
    }
    public ServerResponse getPerson(ServerRequest request) { 
        int personId = Integer.parseInt(request.pathVariable("id"));
        Person person = repository.getPerson(personId);
        if (person != null) {
            return ok().contentType(APPLICATION_JSON).body(person);
        }
        else {
            return ServerResponse.notFound().build();
        }
    }
}
        
  1. listPeople – це функція-обробник, яка повертає всі об'єкти Person, знайдені в репозиторії, у форматі JSON.
  2. createPerson — це функція-обробник, яка зберігає новий об'єкт Person, що міститься в тілі запиту.
  3. getPerson — це функція-обробник, яка повертає одну людину, ідентифіковану змінною шляху id. Ми виймаємо цей об'єкт Person зі сховища та створюємо відповідь у форматі JSON, якщо його буде знайдено. Якщо його не буде знайдено, ми повертаємо відповідь 404 Not Found.
Kotlin

class PersonHandler(private val repository: PersonRepository) {
    fun listPeople(request : ServerRequest): ServerResponse { 
        val people: List<Person> = repository.allPeople()
        return ok().contentType(APPLICATION_JSON).body(people);
    } fun createPerson(request: ServerRequest): ServerResponse { 
        val person = request.body<Person>()
        repository.savePerson(person)
        return ok().build()
    }
    fun getPerson(request: ServerRequest): ServerResponse { 
        val personId = request.pathVariable("id").toInt()
        return repository.getPerson(personId)?.let { ok().contentType(APPLICATION_JSON).body(it) }
                ?: ServerResponse.notFound().build()
    }
}
  1. listPeople — це функція-обробник, яка повертає всі об'єкти Person, знайдені в репозиторії, у форматі JSON.
  2. createPerson — це функція-обробник, яка зберігає новий об'єкт Person, який міститься в тілі запиту.
  3. getPerson — це функція-обробник, яка повертає одну людину, ідентифіковану змінною шляху id. Ми виймаємо цей об'єкт Person зі сховища та створюємо відповідь у форматі JSON, якщо його буде знайдено. Якщо його не буде знайдено, ми повертаємо відповідь 404 Not Found.

Валідація

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

Java

public class PersonHandler {
    private final Validator validator = new PersonValidator() ; 
    // ...
    public ServerResponse createPerson(ServerRequest request) {
        Person person = request.body(Person.class);
        validate(person); 
        repository.savePerson(person);
        return ok().build();
    }
    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()  /
    / ...
    fun createPerson(request: ServerRequest): ServerResponse {
        val person = request.body<Person>() validate(person) 
        repository.savePerson(person)
        return ok().build()
    }
    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), створюючи та впроваджуючи глобальний екземпляр валідатора на основі 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().body("Hello World")).build();
Kotlin

import org.springframework.web.servlet.function.router
val route = router {
    GET("/hello-world", accept(TEXT_PLAIN)) {
        ServerResponse.ok().body("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) до засобу складання RouterFunctions.route()

  • 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.servlet.function.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} із заголовком Accept, який відповідає формату JSON, маршрутизується в PersonHandler.getPerson
  2. GET /person із заголовком Accept, який відповідає формату JSON, маршрутизується в PersonHandler.listPeople
  3. POST /person без додаткових предикатів відображається на PersonHandler.createPerson, та
  4. otherRoute — це функція-маршрутизатор, яка створюється в іншому місці та додається до побудованого маршруту.
Kotlin

import org.springframework.http.MediaType.APPLICATION_JSON
import org.springframework.web.servlet.function.router
val repository: PersonRepository = ... val handler = PersonHandler(repository);
val otherRoute = router { }
val route = router {
    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} із заголовком Accept, який відповідає формату JSON, маршрутизується в PersonHandler.getPerson
  2. GET /person із заголовком Accept, який відповідає формату JSON, маршрутизується в PersonHandler.listPeople
  3. POST /person без додаткових предикатів відображається на PersonHandler.createPerson, та
  4. otherRoute — це функція-маршрутизатор, яка створюється в іншому місці та додається до побудованого маршруту.

Вкладені маршрути

Зазвичай група функцій-маршрутизаторів має загальний предикат, наприклад, загальний шлях. У наведеному вище прикладі загальним предикатом буде предикат шляху, що відповідає /person, який використовується трьома маршрутами. При використанні анотацій можна усунути це дублювання, якщо застосувати анотацію @RequestMapping на рівні типу, що відображається на /person. У WebMvc.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

import org.springframework.web.servlet.function.router
val route = router {
    "/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

import org.springframework.web.servlet.function.router
val route = router {
    "/person".nest {
        accept(APPLICATION_JSON).nest {
            GET("/{id}", handler::getPerson)
            GET("", handler::listPeople)
            POST(handler::createPerson)
        }
    }
}

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

Зазвичай функції маршрутизатора в конфігурації на основі DispatcherHandler запускаються через конфігурацію MVC, яка задіює конфігурацію Spring для оголошення компонентів, необхідних для обробки запитів. Конфігурація MVC на Java оголошує наступні компоненти інфраструктури для підтримки функціональних кінцевих точок:

  • RouterFunctionMapping: Виявляє один або кілька бінів RouterFunction<?> у конфігурації Spring, упорядковує їх, об'єднує за допомогою RouterFunction.andOther і маршрутизує запити до результуючої складової функції RouterFunction.

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

Попередні компоненти дозволяють функціональним кінцевим точкам "вписатися" в життєвий цикл обробки запитів DispatcherServlet, а також (потенційно) працювати пліч-о-пліч з анотованими контролерами, якщо такі оголошені. Це також спосіб активації функціональних кінцевих точок у пусковій системі Spring Boot Web.

У наступному прикладі показано конфігурацію WebFlux Java:

Java

@Configuration
@EnableMvc
public class WebConfig implements WebMvcConfigurer {
    @Bean
    public RouterFunction<?> routerFunctionA() {
        // ...
    } @Bean
    public RouterFunction<?> routerFunctionB() {
        // ...
    }
    // ...
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        // конфігуруємо перетворення повідомлень...
    }
    @Override
    public void addCorsMappings(CorsRegistry registry ) {
        // конфігуруємо CORS...
    }
    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        // конфігуруємо дозвіл подання для HTML-візуалізації...
    }
}
Kotlin

@Configuration
@EnableMvcclass WebConfig : WebMvcConfigurer {
    @Bean
    fun routerFunctionA(): RouterFunction<*> {
        // ...
    }
    @Bean
    fun routerFunctionB(): RouterFunction<*> {
        // ...
    }
    // ...
    override fun configureMessageConverters(converters: List<HttpMessageConverter<*>>) {
        // конфігуруємо перетворення повідомлень...
    }
    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

import org.springframework.web.servlet.function.router
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

import org.springframework.web.servlet.function.router
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 для функціональних кінцевих точок забезпечується за допомогою спеціального CorsFilter.