Primavera WebFlux contiene WebFlux.fn, un modelo de programación funcional liviano en el que se utilizan funciones para enrutar y procesar solicitudes, y los contratos están diseñados para ser inmutables. Es una alternativa al modelo de programación basado en anotaciones, pero por lo demás funciona de la misma manera que Reactive Core.

Breve descripción

En WebFlux.fn, una solicitud HTTP se procesa usando HandlerFunction: una función que acepta una ServerRequest y devuelve una ServerResponse diferida (es decir, Mono<ServerResponse>). Tanto el objeto de solicitud como el de respuesta tienen contratos inmutables que proporcionan acceso a la solicitud y respuesta HTTP compatible con JDK 8. HandlerFunction es el equivalente al cuerpo de un método con la anotación @RequestMapping en el modelo de programación basado en anotaciones.

Las solicitudes entrantes se enrutan a una función de controlador usando RouterFunction: una función que acepta una ServerRequest y devuelve una respuesta diferida. HandlerFunction (es decir, Mono<HandlerFunction>). Si la función del enrutador coincide, se devuelve una función de controlador; de lo contrario, se devuelve una función Mono vacía. RouterFunction es el equivalente a la anotación @RequestMapping, pero con la diferencia significativa de que las funciones del enrutador proporcionan no solo datos, sino también lógica operativa.

RouterFunctions.route() proporciona un generador de enrutadores que facilita la creación de enrutadores, como se muestra en el siguiente ejemplo:

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. Cree un enrutador utilizando el enrutador DSL de Coroutines; también hay una alternativa reactiva disponible a través de router { }.

Una forma de ejecutar una RouterFunction es convertirla en un HttpHandler e instálelo a través de uno de los adaptadores de servidor integrados:

  • RouterFunctions.toHttpHandler(RouterFunction)

  • RouterFunctions.toHttpHandler(RouterFunction, HandlerStrategies)

La mayoría de las aplicaciones se pueden iniciar a través de la configuración de WebFlux Java.

HandlerFunction

ServerRequest y ServerResponse son interfaces inmutables que proporcionan acceso compatible con JDK 8 a solicitudes y respuestas HTTP. Tanto la solicitud como la respuesta proporcionan devolución de llamada Reactive Streams para transmisiones corporales. El cuerpo de la solicitud se representa utilizando Flux o Mono de Reactor. El cuerpo de la respuesta se presenta utilizando cualquier Publisher de Reactive Streams, incluidos Flux y Mono.

ServerRequest

ServerRequest proporciona acceso al método HTTP, URI, encabezados y parámetros de solicitud, y el acceso al cuerpo se proporciona a través de los métodos body.

En el siguiente ejemplo, se extrae el cuerpo de la solicitud en un Mono<String>:

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

En el siguiente ejemplo, el cuerpo se recupera en Flux< ;Persona> (o Flujo<Person> en Kotlin), donde los objetos Persona se decodifican desde algún formato serializado, como JSON o XML:

Java
Flux<Persona> personas = request.bodyToFlux(Person.class);
Kotlin
val people = request.bodyToFlow<Person>()

Los ejemplos anteriores son atajos que utilizan el ServerRequest más genérico. body (BodyExtractor), que acepta la interfaz de estrategia funcional BodyExtractor. La clase auxiliar BodyExtractors proporciona acceso a múltiples instancias. Por ejemplo, los ejemplos anteriores podrían escribirse de la siguiente manera:

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()

El siguiente ejemplo muestra cómo acceder a los datos del formulario:

Java
Mono<MultiValueMap<Cadena, Cadena>> map = request.formData();
Kotlin
val  map = request.awaitFormData()

El siguiente ejemplo muestra cómo acceder a datos de varias partes como un mapa:

Java
Mono<MultiValueMap<String, Part>> map = request.multipartData();
Kotlin
val  map = request.awaitMultipartData()

El siguiente ejemplo muestra cómo acceder a varios componentes, uno a la vez, en modo de transmisión:

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

ServerResponse

ServerResponse proporciona acceso a la respuesta HTTP y, dado que es inmutable, puede utilizar el método build para crearla. Puede utilizar la herramienta de compilación para establecer el estado de la respuesta, agregar encabezados de respuesta o proporcionar el cuerpo de la respuesta. El siguiente ejemplo genera una respuesta 200 (OK) con contenido con formato 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)            
        

El siguiente ejemplo muestra cómo crear una respuesta 201 (CREADA) con un encabezado Ubicación y sin cuerpo:

Java

URI location = ...
ServerResponse.created(location).build();
Kotlin

val location: URI = ...
ServerResponse.created(location).build()

Dependiendo del códec utilizado, puede pasar parámetros de sugerencia para personalizar cómo se serializa o deserializa el cuerpo. Por ejemplo, para definir una vista basada en 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(...)

Clases de controlador

Puede escribir una función de controlador como una expresión lambda, como se muestra en el siguiente ejemplo:

Java

HandlerFunction<ServerResponse> helloWorld =
request -> ServerResponse.ok().bodyValue("Hello World");
Kotlin
val helloWorld = HandlerFunction<ServerResponse> { ServerResponse.ok().bodyValue("Hola mundo") }

Esto es conveniente, pero necesitamos múltiples funciones en una aplicación, y múltiples expresiones lambda en línea pueden generar confusión. Por lo tanto, resulta útil agrupar funciones de controlador relacionadas en una clase de controlador, que desempeña el mismo papel que la anotación @Controller en una aplicación basada en anotaciones. Por ejemplo, la siguiente clase representa una tienda reactiva 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 es una función de controlador que devuelve todos los objetos Person encontrados, en la tienda, en formato JSON.
  2. createPerson es una función de controlador que guarda un nuevo objeto Person contenido en el cuerpo de la solicitud. Tenga en cuenta que PersonRepository.savePerson(Person) devuelve Mono<Void>: un Mono vacío que genera una señal de terminación si se ha leído la Persona de la solicitud. y salvo. Por lo tanto, el método build(Publisher<Void>) se utiliza para enviar una respuesta cuando se recibe una señal de finalización (es decir, cuando se guarda Persona).
  3. getPerson es una función de controlador que devuelve un único objeto Persona identificado por la variable de ruta id. Recuperamos este objeto Persona del almacenamiento y generamos una respuesta JSON si lo encontramos. Si no se encuentra, se utiliza switchIfEmpty(Mono<T>) para devolver una respuesta 404 No encontrado.
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 es una función de controlador que devuelve todos los objetos Person que se encuentran en la tienda en formato JSON.
  2. createPerson es una función de controlador que almacena un nuevo objeto Person contenido en el cuerpo de la solicitud. Tenga en cuenta que PersonRepository.savePerson(Person) devuelve Mono<Void>: un Mono vacío que genera una señal de terminación si se ha leído la Persona de la solicitud. y salvo. Por lo tanto, el método build(Publisher<Void>) se utiliza para enviar una respuesta cuando se recibe una señal de finalización (es decir, cuando se guarda Persona).
  3. getPerson es una función de controlador que devuelve un único objeto Persona identificado por la variable de ruta id. Recuperamos este objeto Persona del almacenamiento y generamos una respuesta JSON si lo encontramos. Si no se encuentra, se utiliza switchIfEmpty(Mono<T>) para devolver una respuesta 404 No encontrado.

Validación

Un punto final de función puede utilizar validadores Spring para aplicar la validación al cuerpo de la solicitud. Por ejemplo, dada una implementación personalizada de Validator de Spring para el objeto 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. Crear una instancia de Validador.
  2. Aplicar validación.
  3. Lanzar una excepción cuando la respuesta sea 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. Crear una instancia de Validador.
  2. Aplicar validación.
  3. Generar una excepción cuando la respuesta sea 400.

Los controladores también pueden utilizar la API de validación de beans estándar (JSR-303), creando e inyectando una instancia Validator global basada en un LocalValidatorFactoryBean.

RouterFunction

Las funciones de enrutador se utilizan para enrutar solicitudes a la HandlerFunction correspondiente. Como regla general, las funciones del enrutador no se escriben de forma independiente, sino que se utiliza un método para la clase auxiliar RouterFunctions para crearlas. RouterFunctions.route() (sin parámetros) proporciona un generador conveniente para crear una función de enrutador, mientras que RouterFunctions.route(RequestPredicate, HandlerFunction) proporciona una forma directa de crear un enrutador.

De hecho, se recomienda utilizar el constructor route() porque proporciona accesos directos convenientes para escenarios de visualización comunes sin requerir la importación de elementos estáticos que son difíciles, detectar. Por ejemplo, el generador de funciones de enrutador ofrece un método GET(String, HandlerFunction) para crear un mapa para solicitudes GET; y POST(String, HandlerFunction) - para solicitudes POST.

Además del mapeo basado en métodos HTTP, el generador de rutas ofrece una manera de introducir predicados adicionales al mapear solicitudes. Para cada método HTTP existe una sobrecarga que toma RequestPredicate como parámetro a través del cual se pueden expresar restricciones adicionales.

Predicados

Puedes escribir tus propios métodos nativos RequestPredicate, pero una clase auxiliar RequestPredicates proporciona implementaciones comúnmente utilizadas basadas en la ruta de solicitud, el método HTTP, el tipo de contenido, etc. El siguiente ejemplo utiliza un predicado de solicitud para crear una restricción basada en el encabezado 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")
}
}

Puede encadenar varios predicados de solicitud usando:

  • RequestPredicate.and(RequestPredicate); ambos deben coincidir.

  • RequestPredicate.or(RequestPredicate): cualquiera de ellos puede coincidir.

Muchos predicados de RequestPredicates son compuestos. Por ejemplo, RequestPredicates.GET(String) consta de RequestPredicates.method(HttpMethod) y RequestPredicates.path(String). El ejemplo anterior también usa dos predicados de solicitud porque el constructor usa RequestPredicates.GET internamente y lo combina con el predicado accept.

Rutas

Las funciones del enrutador se evalúan de manera ordenada: si la primera ruta no coincide, se evalúa la segunda, y así sucesivamente. Por tanto, tiene sentido declarar rutas más específicas antes que las genéricas. Esto también es importante al registrar funciones de enrutador como Spring beans, como se explica más adelante. Tenga en cuenta que esta lógica operativa difiere del modelo de programación basado en anotaciones, donde el método de controlador "más específico" se selecciona automáticamente.

Cuando se utiliza el generador de funciones del enrutador, todas las rutas definidas se ensamblan en un único RouterFunction, que se devuelve desde build(). Hay otras formas de crear varias funciones de enrutador en una:

  • add(RouterFunction) en RouterFunctions.route() constructor

  • RouterFunction.and(RouterFunction)

  • RouterFunction.andRoute(RequestPredicate, HandlerFunction) - abreviatura de RouterFunction.and() con RouterFunctions.route() anidado.

El siguiente ejemplo muestra un diseño de cuatro rutas:

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} con un encabezado Accept que coincide con JSON se enruta a PersonHandler.getPerson
  2. GET /person con un encabezado Accept que se ajuste al formato JSON se envía a PersonHandler. listPeople
  3. POST /person sin predicados adicionales se asigna a PersonHandler.createPerson y
  4. otherRoute es una función de enrutador que se crea en otro lugar y se agrega a la ruta construida.
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} con El encabezado Aceptar que coincide con JSON se enruta a PersonHandler.getPerson
  2. GET /person con un encabezado Accept que coincide con el formato JSON, enviado a PersonHandler.listPeople
  3. POST /person sin predicados adicionales se asigna a PersonHandler.createPerson, y
  4. otherRoute es una función de enrutador que se crea en otro lugar y se agrega a la ruta construida.

Rutas anidadas

Normalmente, un grupo de funciones de enrutador tiene un predicado común, como una ruta común. En el ejemplo anterior, el predicado común sería el predicado de ruta que coincide con /persona, utilizado por las tres rutas. Al utilizar anotaciones, puede eliminar esta duplicación utilizando la anotación @RequestMapping en el nivel de tipo, que se asigna a /person. En WebFlux.fn, los predicados de ruta se pueden compartir utilizando el método path en el constructor de funciones del enrutador. Por ejemplo, las últimas líneas del ejemplo anterior podrían expandirse para verse así, usando rutas anidadas:

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. Tenga en cuenta que el segundo parámetro es path es el destinatario que recibe el generador de enrutadores.
Kotlin

val route = coRouter {
"/person".nest {
GET("/{id}", accept(APPLICATION_JSON), handler::getPerson)
GET(accept(APPLICATION_JSON), handler::listPeople)
POST(handler::createPerson)
}
}

Aunque el anidamiento basado en rutas es el más común, puedes anidar un predicado de cualquier tipo usando el método nest en la herramienta de compilación. Lo anterior todavía contiene algunas duplicaciones en la forma del predicado común Accept-header. Continuaremos expandiendo el código usando el método nest junto con 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)
}
}
}

Iniciando el servidor

Cómo ¿Iniciar la función del enrutador en el servidor HTTP? Una opción sencilla es convertir la función del enrutador a HttpHandler usando uno de los siguientes:

  • RouterFunctions.toHttpHandler(RouterFunction)

  • RouterFunctions.toHttpHandler(RouterFunction, HandlerStrategies)

Luego puede utilizar el valor devuelto HttpHandler con diferentes adaptadores de servidor, siguiendo las instrucciones relativas a HttpHandler para un servidor específico.

Una opción más típica, también utilizada en Spring Boot, es trabajar con la configuración basada en DispatcherHandler a través de la configuración WebFlux, que utiliza la configuración Spring para declarar el componentes necesarios para manejar las solicitudes. La configuración de WebFlux Java declara los siguientes componentes de infraestructura para admitir puntos finales funcionales:

  • RouterFunctionMapping: descubre uno o más beans RouterFunction<?> en la configuración de Spring, los ordena, los combina usando RouterFunction.andOther y enruta las solicitudes al RouterFunction compuesto resultante.

  • HandlerFunctionAdapter: un adaptador simple que permite que DispatcherHandler llame a la HandlerFunction que se asignó a la solicitud.

  • ServerResponseResultHandler: procesa el resultado de una llamada a HandlerFunction llamando al método writeTo en ServerResponse.

Los componentes anteriores permiten que los puntos finales funcionales "encajen" en el ciclo de vida de procesamiento de solicitudes de DispatcherServlet y también (potencialmente) funcionen con controladores anotados, si se declaran, simultáneamente. Esta también es una forma de activar puntos finales funcionales usando un iniciador de Spring Boot para WebFlux.

El siguiente ejemplo muestra la configuración de WebFlux Java:

Java
 
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {
@Bean
public RouterFunction<?> routerFunctionA() {
// ...
}
@Bean
public RouterFunction<?> routerFunctionB() {
// ...
}
// ...
@Override
public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
// configure converting messages...
}
@Override
public void addCorsMappings(CorsRegistry registry) {
// configure CORS...
}
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
// configure the view resolution for HTML visualization...
}
}
Kotlin

@Configuration
@EnableWebFlux
class WebConfig : WebFluxConfigurer {
@Bean
fun routerFunctionA(): RouterFunction<*> {
// ...
}
@Bean
fun routerFunctionB(): RouterFunction<*> {
// ...
}
// ...
override fun configureHttpMessageCodecs(configurer: ServerCodecConfigurer) {
// configure converting messages...
}
override fun addCorsMappings(registry: CorsRegistry) {
// configure CORS...
}
override fun configureViewResolvers(registry: ViewResolverRegistry) {
// configure the view resolution for HTML visualization...
}
}

Filtrado de funciones del controlador

Puede filtrar funciones del controlador utilizando antes, después o métodos filter en la herramienta de cuarenta funciones de enrutamiento. Las anotaciones pueden lograr una funcionalidad similar utilizando @ControllerAdvice, ServletFilter o ambos. El filtro se aplicará a todas las rutas creadas por la herramienta de construcción. Esto significa que los filtros definidos en rutas anidadas no se aplican a rutas de "nivel superior". Por ejemplo, considere lo siguiente:

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. antes filtro que agrega un filtro personalizado solicitud de título, se aplica solo a dos rutas del método GET.
  2. El filtro after, que registra la respuesta, se aplica a todas las rutas, incluidas las anidadas.
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. El filtro antes, que agrega un encabezado de solicitud personalizado, se aplica solo a las dos rutas del método GET.
  2. El filtro después, que registra la respuesta , se aplica a todas las rutas, incluidas las anidadas.

El método filter en el constructor del enrutador acepta una HandlerFilterFunction: una función que acepta ServerRequest y HandlerFunction y devuelve ServerResponse. El parámetro de la función del controlador es el siguiente elemento de la cadena. Normalmente, este es el controlador al que se enruta la solicitud, pero podría ser otro filtro si se aplica más de uno.

Ahora podemos agregar un filtro de seguridad simple a nuestra ruta, asumiendo que tenemos un SecurityManager , que puede determinar si una ruta en particular es válida. El siguiente ejemplo muestra cómo hacer esto:

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();
        }
    }
}
}

El ejemplo anterior muestra que llamar a next.handle(ServerRequest) es opcional. Permitimos que la función del controlador se ejecute solo cuando se permite el acceso.

Además de utilizar el método filter en el constructor de funciones del enrutador, puede aplicar un filtro a una función de enrutador existente a través de RouterFunction.filter(HandlerFilterFunction).

La compatibilidad con CORS para puntos finales funcionales se proporciona a través de un CorsWebFilter especial.