Spring Web MVC contiene WebMvc.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 se ejecuta en el mismo DispatcherServlet.

Breve descripción

En WebMvc.fn, una solicitud HTTP se maneja mediante un HandlerFunction: una función que acepta ServerRequest y devuelve ServerResponse. Tanto el objeto de solicitud como el de respuesta tienen contratos inmutables que proporcionan acceso compatible con JDK 8 a la solicitud y respuesta HTTP. HandlerFunction es el equivalente al cuerpo de un método marcado con la anotación @RequestMapping, en un 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 HandlerFunction opcional (es decir, Optional<HandlerFunction>). Si la función del enrutador coincide, se devuelve la función del controlador; de lo contrario, se devuelve un Opcional vacío. RouterFunction es el equivalente a la anotación @RequestMapping, pero con la diferencia significativa de que las funciones del enrutador transfieren 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
importar organización estática springframework.http.MediaType.APPLICATION_JSON; importar estática org.springframework.web.servlet.function.RequestPredicates.*; importar estática org.springframework.web.servlet.function.RouterFunctions.route; Repositorio PersonRepository = ... Manejador PersonHandler = nuevo PersonHandler(repositorio); Función de enrutador<Respuesta del servidor> ruta = ruta() .GET("/persona/{id}", aceptar(APPLICATION_JSON), controlador::getPerson) .GET("/persona", aceptar(APPLICATION_JSON), controlador::listaPersonas) .POST("/ persona", handler::createPerson).build(); public class PersonHandler { // ... public ServerResponse listPeople(ServerRequest request) { // ... } public ServerResponse createPerson(ServerRequest request) { // ... } public ServerResponse getPerson(ServerRequest request) { // ... } }
Kotlin
importar org.springframework.web.servlet.function.router val repositorio: PersonRepository = ... val controlador = PersonHandler(repositorio) val ruta = enrutador {  aceptar(APPLICATION_JSON).nest { GET("/persona/{ id}", handler::getPerson) GET("/person", handler::listPeople) } POST("/person", handler::createPerson) } class PersonHandler(repositorio de valor privado: PersonRepository) { // ... fun listPeople(solicitud: ServerRequest): ServerResponse { // ... } fun createPerson(solicitud: ServerRequest): ServerResponse { // ... } fun getPerson(solicitud: ServerRequest): ServerResponse { // ... } }
  1. Crear enrutador usando el enrutador DSL.

Si está registrado RouterFunction como un bean, por ejemplo, al abrirlo en una clase con la anotación @Configuration, el servlet lo descubrirá automáticamente.

HandlerFunction

ServerRequest y ServerResponse son interfaces inmutables que brindan acceso a la solicitud y respuesta HTTP, incluidos encabezados, cuerpo, método y compatible con JDK 8. código de estado.

ServerRequest

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

El siguiente ejemplo extrae el cuerpo de la solicitud en una String:

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

El siguiente ejemplo recupera el cuerpo en una List<Person>, donde Los objetos Persona se decodifican desde el formato serializado, como el formato JSON o XML:

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

El siguiente ejemplo muestra cómo acceder a los parámetros:

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

ServerResponse

ServerResponse proporciona acceso a la respuesta HTTP y, dado que es inmutable, puede usar la compilación método para su creación. Puede utilizar la herramienta de compilación para configurar el estado de la respuesta, agregar encabezados de respuesta o pasar el cuerpo de la respuesta. El siguiente ejemplo genera una respuesta 200 (OK) con contenido con formato JSON:

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

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

Java
Ubicación URI = ... ServerResponse.created(ubicación).build();
Kotlin
ubicación val: URI = ... ServerResponse.created(ubicación).build() 

También puedes usar un resultado asincrónico como cuerpo en forma de CompletableFuture, Publisher o cualquier otro tipo. compatible con ReactiveAdapterRegistry . Por ejemplo:

Java
Mono<Persona> persona = webClient.get().retrieve().bodyToMono(Persona.clase); ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(persona);
Kotlin
val persona = webClient.get().retrieve().awaitBody<Person>() ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(persona)

Si no sólo el cuerpo, sino también el estado o los encabezados se basan en un tipo asincrónico, puede utilizar el método estático async en ServerResponse, que toma CompletableFuture<ServerResponse> , Publisher<ServerResponse> o cualquier otro tipo asincrónico admitido por ReactiveAdapterRegistry. Por ejemplo:

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

Los eventos enviados por el servidor se pueden enviar utilizando el método estático sse para ServerResponse. La función de compilación proporcionada por este método le permite enviar cadenas u otros objetos en formato JSON. Por ejemplo:

Java
public RouterFunction<ServerResponse> sse() { return route(GET("/sse"), solicitud -> ServerResponse.sse(sseBuilder -> { // Guarde el objeto sseBuilder en algún lugar... })); } // En algún otro hilo enviando la cadena sseBuilder.send("Hola mundo"); // O un objeto que se convertirá a JSON Person person = ... sseBuilder.send(person); // Configura el evento usando otros métodos sseBuilder.id("42") .event("sse event") .data(person); // y en algún momento estará listo sseBuilder.complete();
Kotlin
fun sse ( ): Función de enrutador<Respuesta del servidor> = enrutador { GET("/sse") { solicitud -> ServerResponse.sse { sseBuilder -> // Guarde el objeto sseBuilder en algún lugar... } } // En algún otro hilo enviando la cadena sseBuilder.send("Hola mundo") // O un objeto que se convertirá a JSON val person = ... sseBuilder.send ( person) // Configura el evento usando otros métodos sseBuilder.id("42") .event("sse event") .data(person) // y en algún momento estará listo sseBuilder.complete()

Clases de controlador

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

Java
HandlerFunction<ServerResponse> holaMundo = solicitud -> ServerResponse.ok().body("Hola mundo");
Kotlin
val helloWorld: (ServerRequest ) -> ServerResponse = { ServerResponse.ok().body("Hello World") }

Esto es conveniente, pero en la aplicación necesitamos varias funciones y Varias expresiones Lambda integradas pueden resultar confusas. 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
importar estática org.springframework.http.MediaType.APPLICATION_JSON; importar estática org.springframework.web.reactive.function.server.ServerResponse.ok; clase pública PersonHandler {repositorio final privado PersonRepository; public PersonHandler(repositorioPersonRepository) { this.repository = repositorio; } public ServerResponse listPeople(solicitud de ServerRequest) {  Lista<Persona> personas = repositorio.allPeople(); return ok().contentType(APPLICATION_JSON).body(personas); } public ServerResponse createPerson (solicitud ServerRequest) lanza una excepción {  Persona persona = request.body(Person.class); repositorio.savePerson(persona); devolver ok().build(); } public ServerResponse getPerson(solicitud ServerRequest) {  int personId = Integer.parseInt(request.pathVariable("id")); Persona persona = repositorio.getPerson(personId); if (persona! = nulo) { return ok().contentType(APPLICATION_JSON).body(persona); } else { return ServerResponse.notFound().build(); } } }
  1. listPeople es una función de controlador que devuelve todos los objetos Person encontrados en el repositorio en formato JSON.
  2. createPerson es una función de controlador que guarda un nuevo código de objeto Person. contenida en el cuerpo de la solicitud.
  3. getPerson es una función de controlador que devuelve una única persona identificada por la variable de ruta id. Recuperamos este objeto Persona del almacenamiento y generamos una respuesta JSON si lo encontramos. Si no se encuentra, devolvemos una respuesta 404 No encontrado.
Kotlin
class PersonHandler(repositorio privado de val: PersonRepository) { fun listPeople( solicitud: ServerRequest): ServerResponse {  val personas: Lista<Persona> = repositorio.allPeople() return ok().contentType(APPLICATION_JSON).body(people); } fun createPerson(solicitud: ServerRequest): ServerResponse {  val persona = request.body<Person>() repositorio.savePerson(persona) return ok().build() } fun getPerson(solicitud: ServerRequest): ServerResponse {  val personId = request.pathVariable("id") .toInt() return repository.getPerson(personId)?.let { ok().contentType(APPLICATION_JSON).body(it) } ?: ServerResponse.notFound().build() } }
  1. listPeople es una función de controlador que devuelve todos los objetos Person que se encuentran en el repositorio, en formato JSON.
  2. createPerson es una función de controlador que guarda un nuevo objeto Person contenido en el cuerpo de la solicitud.
  3. getPerson es una función de controlador que devuelve una única persona identificada por la variable de ruta id. Recuperamos este objeto Persona del almacenamiento y generamos una respuesta JSON si lo encontramos. Si no se encuentra, devolvemos una respuesta 404 No encontrado.

Validación

El punto final de la función puede usar los validadores de Spring para aplicar la validación al cuerpo de la solicitud. Por ejemplo, aquí hay una implementación Spring personalizada de Validator para Person:

Java
clase pública PersonHandler { validador final privado validador = nuevoValidadorPersona();  // ... public ServerResponse createPerson(ServerRequest request) { Persona persona = request.body(Person.class); validar(persona); repository.savePerson(persona); devolver ok().build(); } validar vacío privado (Persona persona) { Errores errores = new BeanPropertyBindingResult(persona, "persona"); validador.validar(persona, errores); if (errors.hasErrors()) { lanzar nueva ServerWebInputException(errors.toString());  } } }
  1. Crear una instancia de Validator.
  2. Aplicar validación.
  3. Lanzar una excepción para la respuesta 400.
Kotlin
class PersonHandler(repositorio de valor privado: PersonRepository) { validador de valor privado = PersonValidator()  // ... divertido createPerson(solicitud: ServerRequest): ServerResponse { val persona = request.body<Person>() validar(persona)  repository.savePerson(persona) return ok().build() } diversión privada validar(persona: Persona) { val errores: Errores = BeanPropertyBindingResult(persona, "persona") validator.validate(persona, errores ) if (errors. hasErrors()) { throw ServerWebInputException(errors.toString())  } } }
  1. Crear una instancia de Validator.
  2. Aplicar validación.
  3. Lanzar una excepción para la respuesta 400.

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

RouterFunction

Las funciones de enrutador se utilizan para enrutar solicitudes a la correspondiente HandlerFunction . 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 del 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> ruta = RouterFunctions.route() .GET("/hola-mundo", aceptar(MediaType.TEXT_PLAIN), solicitud -> ServerResponse.ok().body("Hola mundo")).build();
Kotlin
importar org.springframework.web.servlet.function.router val ruta = enrutador { GET("/hola -world", aceptar(TEXT_PLAIN)) { ServerResponse.ok().body("Hello World") } }

Puedes encadenar múltiples predicados de solicitud juntos 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 combinar varias funciones de enrutador en una:

  • agregar(RouterFunction) a la herramienta de compilación RouterFunctions.route()

  • 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
importar estática org.springframework.http.MediaType.APPLICATION_JSON; importar estática org.springframework.web.servlet.function.RequestPredicates.*; Repositorio PersonRepository = ... Manejador PersonHandler = nuevo PersonHandler(repositorio); Función de enrutador<Respuesta del servidor> otraRuta = ... Función de enrutador<Respuesta del servidor> ruta = ruta() .GET("/persona/{id}", aceptar(APPLICATION_JSON), controlador::getPerson)  .GET ("/persona", aceptar(APPLICATION_JSON), handler::listPeople)  .POST("/person", handler::createPerson)  .add(otherRoute)  .build( );
  1. GET /person/{id} con encabezado El Aceptar que coincide con el formato JSON se dirige a PersonHandler.getPerson
  2. GET /person con el encabezado Aceptar que coincide con el formato JSON se enruta 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
importar org.springframework.http.MediaType.APPLICATION_JSON importar org.springframework.web.servlet.function.router repositorio val: PersonRepository = ... val handler = PersonHandler(repositorio); val otherRoute = enrutador { } val ruta = enrutador { GET("/persona/{id}", aceptar(APPLICATION_JSON), handler::getPerson)  GET("/persona", aceptar(APPLICATION_JSON), controlador::listaPersonas)  POST("/persona", controlador: :createPerson)  }.y(otherRoute) 
  1. GET /person/{id} con encabezado Aceptar que coincide con el formato JSON se dirige a PersonHandler.getPerson
  2. GET /person con el encabezado Aceptar El que se ajusta al formato JSON se enruta 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 WebMvc.fn, los predicados de ruta se pueden compartir utilizando el método path en el generador 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> ruta = ruta() .path("/persona", constructor -> constructor  .GET("/{id}", aceptar (APPLICATION_JSON), controlador::getPerson) .GET(accept(APPLICATION_JSON), controlador::listPeople) .POST(handler::createPerson)) .build();
  1. Tenga en cuenta que el segundo parámetro path es el destino que acepta el constructor del enrutador.
Kotlin
importar org.springframework.web.servlet.function.router val ruta = enrutador { "/persona".nest { GET("/{id}", aceptar(APPLICATION_JSON), handler::getPerson) GET(accept(APPLICATION_JSON), handler::listPeople) POST(handler::createPerson) } }

Aunque el archivo adjunto está activado basado en ruta es el más común, puede 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< Respuesta del servidor> ruta = ruta() .path("/persona", b1 -> b1 .nest(aceptar(APPLICATION_JSON), b2 -> b2 .GET("/{id}", handler::getPerson) .GET(handler ::listPeople)) .POST(handler::createPerson)) .build();
Kotlin
 importar org.springframework.web.servlet.function.router val ruta = enrutador { "/persona".nest { aceptar(APPLICATION_JSON).nest { GET("/{id}", handler::getPerson) GET("", handler::listPeople) POST(handler::createPerson) } } }

Inicio del servidor

Normalmente, el enrutador funciona en una configuración basados en DispatcherHandler se lanzan a través de una configuración MVC, que aprovecha la configuración de Spring para declarar los componentes necesarios para manejar las solicitudes. La configuración de Java MVC 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 a la función compuesta resultante RouterFunction.

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

Los componentes anteriores permiten que los puntos finales funcionales encajen en el ciclo de vida de la solicitud DispatcherServlet y también (potencialmente) trabajen en paralelo con los controladores anotados, si se declaran. Esta es también la forma de habilitar puntos finales funcionales en el iniciador web Spring Boot.

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

Java
@Configuration @EnableMvc clase pública WebConfig implementa WebMvcConfigurer { @Bean public RouterFunction<?> routerFunctionA() { // ... } @Bean public RouterFunction<?> routerFunctionB() { // ... } // ... @Override public void configureMessageConverters(List<HttpMessageConverter<?>> convertidores) { // configurar la conversión de mensajes... } @Override public void addCorsMappings(registro CorsRegistry ) { // configurar CORS... } @Override public void configureViewResolvers(registro ViewResolverRegistry) { // configurar la resolución de vista para la representación HTML... } }
Kotlin
@Configuration @EnableMvc class
    WebConfig : WebMvcConfigurer { @Bean fun routerFunctionA(): RouterFunction<*> { // ... } @Bean fun
    routerFunctionB(): RouterFunction<*> { // ... } // ... anular divertido configureMessageConverters(converters:
    List<HttpMessageConverter<*>>) { // configurar la conversión de mensajes... } anular divertido
    addCorsMappings(registro: CorsRegistry) { // configurar CORS... } anular fun configureViewResolvers(registry:
    ViewResolverRegistry) { // configurar la resolución de vista para la representación HTML... } }

Funciones del controlador de filtro

Puede filtrar funciones de controlador utilizando los métodos before, after o filter en el generador de 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 las rutas de "nivel superior". Por ejemplo, considere el siguiente ejemplo:

Java
RouterFunction<ServerResponse> ruta = ruta() .path("/persona", b1 -> b1 .nest(aceptar(APPLICATION_JSON), b2 -> b2 .GET("/{id}", handler::getPerson) .GET(handler ::listPeople) .before(solicitud -> ServerRequest.from(solicitud)  .header("X-RequestHeader", "Valor" ) .build())) .POST(handler::createPerson)) .after((solicitud, respuesta) -> logResponse(respuesta))  .build();
  1. La adición del filtro antes encabezado de solicitud personalizado, se aplica solo a dos rutas GET.
  2. El filtro after, que registra la respuesta, se aplica a todas las rutas, incluidas las anidadas.
Kotlin
importar org.springframework.web.servlet.function.router val ruta = enrutador { "/persona".nest { GET("/{id}", controlador ::getPerson) GET(handler::listPeople) antes de {  ServerRequest.from(it) .header("X-RequestHeader", " Valor"). build() } } POST(handler::createPerson) después de { _, respuesta ->  logResponse(respuesta) } }
  1. El filtro antes, que agrega un encabezado de solicitud personalizado, se aplica solo a dos rutas GET.
  2. El filtro después , que registra la respuesta, se aplica a todas las rutas, incluidas las anidadas.

El método filter en la herramienta de construcción del enrutador acepta una HandlerFilterFunction: una función que acepta una ServerRequest y una 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 un filtro diferente 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 determinada ruta es válida. El siguiente ejemplo muestra cómo hacer esto:

Java
SecurityManager securityManager = ... Función de enrutador<Respuesta del servidor> ruta = ruta() .ruta("/persona", b1 -> b1 .nest(aceptar(APPLICATION_JSON), b2 -> b2 .GET("/{id}", controlador::getPerson) .GET(controlador ::listPeople)) .POST(handler::createPerson)) .filter((solicitud, siguiente) -> { if (securityManager.allowAccessTo(request.path())) { return next.handle(solicitud); } else { return ServerResponse.status(UNAUTHORIZED).build(); } }) .build();
Kotlin
importar org.springframework.web.servlet.function.router val securityManager: SecurityManager = ... val ruta = enrutador { ("/persona" y aceptar(APPLICATION_JSON)).nest { GET("/{id}", handler::getPerson) GET("", handler::listPeople) POST(handler::createPerson) filter { solicitud, siguiente -> if (securityManager.allowAccessTo(request.path())) {siguiente(solicitud)} else { estado(NO AUTORIZADO).build(); } } } }

El ejemplo anterior demostró que llamar a next.handle(ServerRequest) es opcional. Permitimos que la función ejecute -handler solo cuando se permita el acceso.

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

El soporte CORS para puntos finales de funciones se proporciona mediante un CorsFilter especial.