Spring WebFlux містить WebFlux.fn, полегшену модель функціонального програмування, в якій функції використовуються для маршрутизації та обробки запитів, а контракти розроблені таким чином, щоб забезпечувати незмінність. Вона є альтернативою моделі програмування на основі анотацій, але в іншому працює на тій же основі з Reactive Core.
Короткий опис
HandlerFunction
: функції, яка приймає ServerRequest
і повертає відкладений
ServerResponse
(тобто Mono<ServerResponse>
). І об'єкт-запит, і об'єкт-відповідь
мають незмінні контракти, які забезпечують доступ до запиту та відповіді HTTP, сумісний з JDK 8. HandlerFunction
— це еквівалент тіла методу з анотацією @RequestMapping
у моделі програмування на основі анотацій.Вхідні запити надсилаються до функції-обробника за допомогою RouterFunction
: функції, яка приймає ServerRequest
і повертає відкладену HandlerFunction
(тобто Mono<HandlerFunction>
). Якщо функція
маршрутизатора збігається, функція-обробник повертається; інакше повертається порожня функція Mono. RouterFunction
— це еквівалент анотації @RequestMapping
, але з тією істотною відмінністю, що функції маршрутизатора
надають не лише дані, а й логіку роботи.
RouterFunctions.route()
надає засіб складання
маршрутизаторів, який полегшує створення маршрутизаторів, як показано в наступному прикладі:
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) {
// ...
}
}
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 {
// ...
}
}
- 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>
:
Mono<String> string = request.bodyToMono(String.class);
val string = request.awaitBody<String>()
У наступному прикладі тіло витягується до Flux<Person>
(або Flow<Person>
у Kotlin), де об'єкти Person
декодуються з будь-якої серіалізованої форми, наприклад JSON або XML:
Flux<Person> people = request.bodyToFlux(Person.class);
val people = request.bodyToFlow<Person>()
Попередні приклади є скороченнями, що використовують більш загальний ServerRequest.body
(BodyExtractor)
, який приймає інтерфейс функціональної стратегії BodyExtractor
. Допоміжний клас
BodyExtractors
надає доступ до кількох екземплярів. Наприклад, попередні приклади можна записати так:
Mono<String> string = request.body(BodyExtractors.toMono(String.class));
Flux<Person> people = request.body(BodyExtractors.toFlux(Person.class));
val string = request.body(BodyExtractors.toMono(String::class.java)).awaitSingle()
val people = request.body(BodyExtractors.toFlux(Person::class.java)).asFlow()
У наступному прикладі показано, як отримати доступ до даних форми:
Mono<MultiValueMap<String, String>> map = request.formData();
val map = request.awaitFormData()
У наступному прикладі показано, як отримати доступ до багатокомпонентних даних у вигляді Map:
Mono<MultiValueMap<String, Part>> map = request.multipartData();
val map = request.awaitMultipartData()
У наступному прикладі показано, як отримати доступ до кількох компонентів, по одному за раз, у потоковому режимі :
Flux<Part> parts = request.body(BodyExtractors.toParts());
val parts = request.body(BodyExtractors.toParts()).asFlow()
ServerResponse
ServerResponse
забезпечує доступ до HTTP-відповіді, і, оскільки він незмінний, то можна використовувати
метод
build
для його створення. Ти можеш використовувати засіб збирання для встановлення статусу відповіді,
додавання заголовків відповіді або надання тіла відповіді. У наступному прикладі створюється відповідь 200 (OK) із
вмістом у форматі JSON:
Mono<Person> person = ...
ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person, Person.class);
val person: Person = ...
ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).bodyValue(person)
У наступному прикладі показано, як створити відповідь 201 (CREATED) із заголовком Location
і без
тіла:
URI location = ...
ServerResponse.created(location).build();
val location: URI = ...
ServerResponse.created(location).build()
Залежно від кодека, що використовується, можна передати параметри підказки, щоб кастомно налаштувати спосіб серіалізації або десеріалізації тіла. Наприклад, щоб встановити подання на основі Jackson JSON:
ServerResponse.ok().hint(Jackson2CodecSupport.JSON_VIEW_HINT, MyJacksonView.class).body(...);
ServerResponse.ok().hint(Jackson2CodecSupport.JSON_VIEW_HINT, MyJacksonView::class.java) .body(...)
Класи обробників
Можна написати функцію-обробник у вигляді лямбда-виразу, як показано в наступному приклад:
HandlerFunction<ServerResponse> helloWorld =
request -> ServerResponse.ok().bodyValue("Hello World");
val helloWorld = HandlerFunction<ServerResponse> { ServerResponse.ok().bodyValue("Hello World") }
Це зручно, але в додатку нам потрібна наявність кількох функцій, а кілька вбудованих лямбда-виразів можуть
призвести до безладдя. Тому корисно згрупувати пов'язані функції-обробники в клас обробника, який відіграє таку
ж роль, як анотація @Controller
у додатку, заснованому на анотаціях. Наприклад, наступний клас
представляє реактивне сховище Person
:
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());
}
}
listPeople
— це функція-обробник, яка повертає всі об'єктиPerson
, знайдені у сховищі, у форматі JSON.createPerson
— це функція-обробник, яка зберігає новий об'єктPerson
, який міститься в тілі запиту. Зверни увагу, щоPersonRepository.savePerson(Person)
повертаєMono<Void>
: порожнійMono
, який генерує сигнал завершення, якщо Person був прочитаний з запиту та збережений. Тому методbuild(Publisher<Void>)
використовується для надсилання відповіді, коли буде отримано сигнал завершення (тобто колиPerson
буде збережено).getPerson
— це функція-обробник, яка повертає один об'єкт Person, ідентифікований змінною дорогоюid
. Ми виймаємо цей об'єктPerson
зі сховища та створюємо відповідь у форматі JSON, якщо його буде знайдено. Якщо його не буде знайдено, використовуєтьсяswitchIfEmpty(Mono<T>)
, щоб повернути відповідь 404 Not Found.
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()
}
}
listPeople
— це функція-обробник, яка повертає всі об'єктиPerson
, знайдені у сховищі, у форматі JSON.createPerson
— це функція-обробник, яка зберігає новий об'єктPerson
, який міститься в тілі запиту. Зверни увагу, щоPersonRepository.savePerson(Person)
повертаєMono<Void>
: порожнійMono
, який генерує сигнал завершення, якщо Person був прочитаний з запиту та збережений. Тому методbuild(Publisher<Void>)
використовується для надсилання відповіді, коли буде отримано сигнал завершення (тобто колиPerson
буде збережено).getPerson
— це функція-обробник, яка повертає один об'єкт Person, ідентифікований змінною дорогоюid
. Ми виймаємо цей об'єктPerson
зі сховища та створюємо відповідь у форматі JSON, якщо його буде знайдено. Якщо його не буде знайдено, використовуєтьсяswitchIfEmpty(Mono<T>)
, щоб повернути відповідь 404 Not Found.
Валідація
Функціональна кінцева точка може використовувати засоби перевірки Spring для застосування валідації до тіла
запиту. Наприклад, дана кастомна реалізація Validator з Spring для об'єкта Person
:
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());
}
}
}
- Створюємо екземпляр
Validator
. - Застосовуємо валідацію.
- Генеруємо виняток при відповіді 400.
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())
}
}
}
- Створюємо екземпляр
Validator
. - Застосовуємо валідацію.
- Генеруємо виняток при відповіді 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
:
RouterFunction<ServerResponse> route = RouterFunctions.route()
.GET("/hello-world", accept(MediaType.TEXT_PLAIN),
request -> ServerResponse.ok().bodyValue("Hello World")).build();
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 theRouterFunctions.route()
builderRouterFunction.and(RouterFunction)
RouterFunction.andRoute(RequestPredicate, HandlerFunction)
— скорочення дляRouterFunction.and()
з вкладенимиRouterFunctions.route()
.
У цьому прикладі показано компонування з чотирьох маршрутів:
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();
GET /person/{id}
with anAccept
header that matches JSON is routed toPersonHandler.getPerson
GET /person
із заголовкомAccept
, який відповідає формату JSON, направляється доPersonHandler.listPeople
POST /person
без додаткових предикатів відображається наPersonHandler.createPerson
, таotherRoute
- це функція-маршрутизатор, яка створюється в іншому місці і додається до побудованого маршруту.
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)
GET /person/{id}
with anAccept
header that matches JSON is routed toPersonHandler.getPerson
GET /person
із заголовкомAccept
, який відповідає формату JSON, надсилається вPersonHandler.listPeople
POST /person
без додаткових предикатів відображається наPersonHandler.createPerson
,
та
otherRoute
— це функція-маршрутизатор, яка створюється в іншому місці та додається до побудованого маршруту.
Вкладені маршрути
Зазвичай група функцій-маршрутизаторів має загальний предикат, наприклад, загальний шлях. У наведеному
вище прикладі загальним предикатом буде предикат шляху, який відповідає /person
, який
використовується трьома маршрутами. При використанні анотацій можна усунути це дублювання,
використовуючи анотацію @RequestMapping
на рівні типу, що відображається на
/person
. У WebFlux.fn предикати шляху можуть спільно використовуватися за допомогою методу
path
у конструкторі функцій-маршрутизаторів. Наприклад, останні кілька рядків наведеного
вище прикладу можна доповнити таким чином, використовуючи вкладені маршрути:
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();
- Зверніть увагу, що другий параметр
path
– це одержувач, який приймає засіб складання маршрутизатора.
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
:
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();
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:
@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-візуалізації...
}
}
@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
або й те, й
інше. Фільтр застосовуватиметься до всіх маршрутів, побудованих засобом збирання. Це означає, що фільтри,
визначені у вкладених маршрутах, не застосовуються до маршрутів "вищого рівня". Наприклад, розглянемо
наступне:
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();
- Фільтр
before
, який додає заголовок користувача. запиту, застосовується лише до двох маршрутів методу GET. - Фільтр
after
, що реєструє відповідь, застосовується до всіх маршрутів, включаючи вкладені.
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)
}
}
}
- Фільтр
before
, який додає заголовок користувача запиту, застосовується тільки до двох маршрутів методу GET. - Фільтр
after
, що реєструє відповідь, застосовується до всіх маршрутів, включаючи вкладені.
Метод filter
у конструкторі маршрутизатора приймає HandlerFilterFunction
: функцію,
яка приймає ServerRequest
та HandlerFunction
і повертає
ServerResponse
. Параметр функції-обробника є наступним елементом у ланцюжку. Зазвичай це
обробник, до якого маршрутизується запит, але це може бути й інший фільтр, якщо їх застосовується
декілька.
Тепер ми можемо додати простий фільтр безпеки до нашого маршруту, зробивши припущення, що в нас є SecurityManager
,
який може визначати, чи допустимий конкретний шлях. У цьому прикладі показано, як це
зробити:
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();
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)
.
CorsWebFilter
.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ