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

import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.servlet.function.RequestPredicates.*;
import static org.springframework.web.servlet.function.RouterFunctions.route;
PersonRepository repository = ...
PersonHandler handler = new PersonHandler(repository);
RouterFunction<ServerResponse> route = route()
    .GET("/person/{id}", accept(APPLICATION_JSON), handler::getPerson)
    .GET("/person", accept(APPLICATION_JSON), handler::listPeople)
    .POST("/person", handler::createPerson)
    .build();
public class PersonHandler {
    // ...
    public ServerResponse listPeople(ServerRequest request) {
        // ...
    }
    public ServerResponse createPerson(ServerRequest request) {
        // ...
    }
    public ServerResponse getPerson(ServerRequest request) {
        // ...
    }
}
Kotlin

import org.springframework.web.servlet.function.router
val repository: PersonRepository = ...
val handler = PersonHandler(repository)
val route = router { 
    accept(APPLICATION_JSON).nest {
        GET("/person/{id}", handler::getPerson)
        GET("/person", handler::listPeople)
    }
    POST("/person", handler::createPerson)
}
class PersonHandler(private val repository: PersonRepository) {
    // ...
    fun listPeople(request: ServerRequest): ServerResponse {
        // ...
    }
    fun createPerson(request: ServerRequest): ServerResponse {
        // ...
    }
    fun getPerson(request: ServerRequest): ServerResponse {
        // ...
    }
}
  1. 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
String string = request.body (String.class);
Kotlin
val string = request.body<String>()

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

Java

List<Person> people = 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

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

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

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

Java

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

val location: URI = ...
ServerResponse.created(location).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<Person> person = webClient.get().retrieve().bodyToMono(Person.class);
ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person);
Kotlin

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

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("Name", 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"), request -> ServerResponse.sse(sseBuilder -> {
                // Guarde el objeto sseBuilder en algún lugar...
            }));
}
// En algún otro hilo enviando la cadena
sseBuilder.send("Hello world");
// 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( ): RouterFunction<ServerResponse> = router {
    GET("/sse") { request -> ServerResponse.sse { sseBuilder ->
        // Guarde el objeto sseBuilder en algún lugar...
    }
}
// En algún otro hilo enviando la cadena
sseBuilder.send("Hello world")
// En algún otro hilo enviando la cadena
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> helloWorld =
    request -> ServerResponse.ok().body("Hello World");
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

import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.reactive.function.server.ServerResponse.ok;
public class PersonHandler {
    private final PersonRepository repository;
    public PersonHandler(PersonRepository repository) {
        this.repository = repository;
    }
    public ServerResponse listPeople(ServerRequest request) { 
        List<Person> people = repository.allPeople();
        return ok().contentType(APPLICATION_JSON).body(people);
    }
    public ServerResponse createPerson(ServerRequest request) throws Exception { 
        Person person = request.body(Person.class);
        repository.savePerson(person);
        return ok().build();
    }
    public ServerResponse getPerson(ServerRequest request) { 
        int personId = Integer.parseInt(request.pathVariable("id"));
        Person person = repository.getPerson(personId);
        if (person != null) {
            return ok().contentType(APPLICATION_JSON).body(person);
        }
        else {
            return ServerResponse.notFound().build();
        }
    }
}
  1. listPeople 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 Person del almacenamiento y generamos una respuesta JSON si lo encontramos. Si no se encuentra, devolvemos una respuesta 404 No encontrado.
Kotlin

class PersonHandler(private val repository: PersonRepository) {
    fun listPeople(request : ServerRequest): ServerResponse { 
        val people: List<Person> = repository.allPeople()
        return ok().contentType(APPLICATION_JSON).body(people);
    } fun createPerson(request: ServerRequest): ServerResponse { 
        val person = request.body<Person>()
        repository.savePerson(person)
        return ok().build()
    }
    fun getPerson(request: ServerRequest): ServerResponse { 
        val personId = request.pathVariable("id").toInt()
        return repository.getPerson(personId)?.let { ok().contentType(APPLICATION_JSON).body(it) }
                ?: ServerResponse.notFound().build()
    }
}
  1. listPeople 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

public class PersonHandler {
    private final Validator validator = new PersonValidator() ; 
    // ...
    public ServerResponse createPerson(ServerRequest request) {
        Person person = request.body(Person.class);
        validate(person); 
        repository.savePerson(person);
        return ok().build();
    }
    private void validate(Person person) {
        Errors errors = new BeanPropertyBindingResult(person, "person");
        validator.validate(person, errors);
        if (errors.hasErrors()) {
            throw new ServerWebInputException(errors.toString()); 
        }
    }
}
  1. Crear una instancia de Validator.
  2. Aplicar validación.
  3. Lanzar una excepción para la respuesta 400.
Kotlin

class PersonHandler(private val repository: PersonRepository) {
    private val validator = PersonValidator()  /
    / ...
    fun createPerson(request: ServerRequest): ServerResponse {
        val person = request.body<Person>() validate(person) 
        repository.savePerson(person)
        return ok().build()
    }
    private fun validate(person: Person) {
        val errors: Errors = BeanPropertyBindingResult(person, "person")
        validator.validate(person, errors)
        if (errors. hasErrors()) {
            throw ServerWebInputException(errors.toString()) 
        }
    }
}
  1. 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> route = RouterFunctions.route()
    .GET("/hello-world", accept(MediaType.TEXT_PLAIN),
        request -> ServerResponse.ok().body("Hello World")).build();
Kotlin

import org.springframework.web.servlet.function.router
val route = router {
    GET("/hello-world", accept(TEXT_PLAIN)) {
        ServerResponse.ok().body("Hello World")
    }
}

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:

  • add(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

import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.servlet.function.RequestPredicates.*;
PersonRepository repository = ...
PersonHandler handler = new PersonHandler(repository);
RouterFunction<ServerResponse> otherRoute = ...
RouterFunction<ServerResponse> route = route() .
    GET("/person/{id}", accept(APPLICATION_JSON), handler::getPerson) 
    .GET ("/person", accept(APPLICATION_JSON), handler::listPeople) 
    .POST("/person", handler::createPerson) 
    .add(otherRoute) 
    .build();
  1. GET /person/{id} 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
import org.springframework.http.MediaType.APPLICATION_JSON
import org.springframework.web.servlet.function.router
val repository: PersonRepository = ... val handler = PersonHandler(repository);
val otherRoute = router { }
val route = router {
    GET("/person/{id}", accept(APPLICATION_JSON), handler::getPerson) 
    GET("/person", accept(APPLICATION_JSON), handler::listPeople) 
    POST("/person", handler: :createPerson) 
}.and(otherRoute) 
  1. GET /person/{id} 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 /person , 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> 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 path es el destino que acepta el constructor del enrutador.
Kotlin

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

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<ServerResponse> route = route()
    .path("/person", b1 -> b1
        .nest(accept(APPLICATION_JSON), b2 -> b2
            .GET("/{id}", handler::getPerson)
            .GET(handler::listPeople))
        .POST(handler::createPerson))
    .build();
Kotlin
 
import org.springframework.web.servlet.function.router
val route = router {
    "/person".nest {
        accept(APPLICATION_JSON).nest {
            GET("/{id}", handler::getPerson)
            GET("", handler::listPeople)
            POST(handler::createPerson)
        }
    }
}

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
public class WebConfig implements WebMvcConfigurer {
    @Bean
    public RouterFunction<?> routerFunctionA() {
        // ...
    } @Bean
    public RouterFunction<?> routerFunctionB() {
        // ...
    }
    // ...
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        // configurar la conversión de mensajes...
    }
    @Override
    public void addCorsMappings(CorsRegistry registry ) {
        // configurar CORS...
    }
    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        // configurar la resolución de vista para la representación HTML
    }
}}
Kotlin

@Configuration
@EnableMvcclass WebConfig : WebMvcConfigurer {
    @Bean
    fun routerFunctionA(): RouterFunction<*> {
        // ...
    }
    @Bean
    fun routerFunctionB(): RouterFunction<*> {
        // ...
    }
    // ...
    override fun configureMessageConverters(converters: List<HttpMessageConverter<*>>) {
        // configurar la conversión de mensajes...
    }
    override fun addCorsMappings(registry: CorsRegistry) {
        // configurar CORS...
    }
    override 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> 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. La adición del filtro before 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

import org.springframework.web.servlet.function.router
val route = router {
    "/person".nest {
        GET("/{id}", handler::getPerson)
        GET(handler::listPeople)
        before { 
            ServerRequest.from(it)
                    .header("X-RequestHeader", "Value"). build()
        }
}
    POST(handler::createPerson)
    after { _, response -> 
        logResponse(response)
    }
}
  1. El filtro before, que agrega un 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.

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 = ...
RouterFunction<ServerResponse> route = route()
    .path("/person", b1 -> b1
        .nest(accept(APPLICATION_JSON), b2 -> b2
            .GET("/{id}", handler::getPerson)
            .GET(handler ::listPeople))
        .POST(handler::createPerson))
    .filter((request, next) -> {
        if (securityManager.allowAccessTo(request.path())) {
            return next.handle(request);
        }
        else {
            return ServerResponse.status(UNAUTHORIZED).build();
        }
    })
    .build();
Kotlin

import org.springframework.web.servlet.function.router
val securityManager: SecurityManager = ...
val route = router {
    ("/person" and accept(APPLICATION_JSON)).nest {
        GET("/{id}", handler::getPerson)
        GET("", handler::listPeople)
        POST(handler::createPerson)
        filter { request, next ->
            if (securityManager.allowAccessTo(request.path())) {
                next(request)
            }
            else {
                status(UNAUTHORIZED).build();
            }
        }
    }
}

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.