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()
предоставляет средство сборки маршрутизаторов, которое облегчает создание маршрутизаторов, как показано в следующем примере:
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, 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>
:
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, как будет описано позже. Обратите внимание, что такая логика работы отличается от модели программирования на основе аннотаций, где "наиболее конкретный" метод контроллера выбирается автоматически.
При использовании средства сборки функций-маршрутизаторов все определенные маршруты компонуются в одну RouterFunction
, которая возвращается из build()
. Существуют и другие способы компоновки нескольких функций маршрутизатора в одну:
-
add(RouterFunction)
on theRouterFunctions.route()
builder -
RouterFunction.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
.