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:
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 { // ... } }
- 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>
:
Mono<String> cadena = request.bodyToMono(String.class);
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:
Flux<Persona> personas = request.bodyToFlux(Person.class);
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:
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()
El siguiente ejemplo muestra cómo acceder a los datos del formulario:
Mono<MultiValueMap<Cadena, Cadena>> map = request.formData();
val map = request.awaitFormData()
El siguiente ejemplo muestra cómo acceder a datos de varias partes como un mapa:
Mono<MultiValueMap<String, Part>> map = request.multipartData();
val map = request.awaitMultipartData()
El siguiente ejemplo muestra cómo acceder a varios componentes, uno a la vez, en modo de transmisión:
Flux<Part> partes = request.body(BodyExtractors.toParts());
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:
Mono<Person> person = ...
ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person, Person.class);
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:
URI location = ...
ServerResponse.created(location).build();
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:
ServerResponse.ok().hint(Jackson2CodecSupport.JSON_VIEW_HINT, MyJacksonView.class).body(...);
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:
HandlerFunction<ServerResponse> helloWorld =
request -> ServerResponse.ok().bodyValue("Hello World");
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
:
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
es una función de controlador que devuelve todos los objetosPerson
encontrados, en la tienda, en formato JSON.createPerson
es una función de controlador que guarda un nuevo objetoPerson
contenido en el cuerpo de la solicitud. Tenga en cuenta quePersonRepository.savePerson(Person)
devuelveMono<Void>
: unMono
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étodobuild(Publisher<Void>)
se utiliza para enviar una respuesta cuando se recibe una señal de finalización (es decir, cuando se guardaPersona
).getPerson
es una función de controlador que devuelve un único objeto Persona identificado por la variable de rutaid
. Recuperamos este objetoPersona
del almacenamiento y generamos una respuesta JSON si lo encontramos. Si no se encuentra, se utilizaswitchIfEmpty(Mono<T>)
para devolver una respuesta 404 No encontrado.
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
es una función de controlador que devuelve todos los objetosPerson
que se encuentran en la tienda en formato JSON.createPerson
es una función de controlador que almacena un nuevo objetoPerson
contenido en el cuerpo de la solicitud. Tenga en cuenta quePersonRepository.savePerson(Person)
devuelveMono<Void>
: unMono
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étodobuild(Publisher<Void>)
se utiliza para enviar una respuesta cuando se recibe una señal de finalización (es decir, cuando se guardaPersona
).getPerson
es una función de controlador que devuelve un único objeto Persona identificado por la variable de rutaid
. Recuperamos este objetoPersona
del almacenamiento y generamos una respuesta JSON si lo encontramos. Si no se encuentra, se utilizaswitchIfEmpty(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
:
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());
}
}
}
- Crear una instancia de
Validador
. - Aplicar validación.
- Lanzar una excepción cuando la respuesta sea 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())
}
}
}
- Crear una instancia de
Validador
. - Aplicar validación.
- 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
:
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")
}
}
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)
enRouterFunctions.route()
constructorRouterFunction.and(RouterFunction)
RouterFunction.andRoute(RequestPredicate, HandlerFunction)
- abreviatura deRouterFunction.and()
conRouterFunctions.route()
anidado.
El siguiente ejemplo muestra un diseño de cuatro rutas:
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}
con un encabezadoAccept
que coincide con JSON se enruta aPersonHandler.getPerson
GET /person
con un encabezadoAccept
que se ajuste al formato JSON se envía aPersonHandler. listPeople
POST /person
sin predicados adicionales se asigna aPersonHandler.createPerson
yotherRoute
es una función de enrutador que se crea en otro lugar y se agrega a la ruta construida.
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}
con El encabezadoAceptar
que coincide con JSON se enruta aPersonHandler.getPerson
GET /person
con un encabezadoAccept
que coincide con el formato JSON, enviado aPersonHandler.listPeople
POST /person
sin predicados adicionales se asigna aPersonHandler.createPerson
, yotherRoute
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:
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();
- Tenga en cuenta que el segundo parámetro es
path
es el destinatario que recibe el generador de enrutadores.
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
:
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)
}
}
}
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 beansRouterFunction<?>
en la configuración de Spring, los ordena, los combina usandoRouterFunction.andOther
y enruta las solicitudes alRouterFunction
compuesto resultante.HandlerFunctionAdapter
: un adaptador simple que permite queDispatcherHandler
llame a laHandlerFunction
que se asignó a la solicitud.ServerResponseResultHandler
: procesa el resultado de una llamada aHandlerFunction
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:
@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...
}
}
@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:
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();
antes
filtro que agrega un filtro personalizado solicitud de título, se aplica solo a dos rutas del método GET.- El filtro
after
, que registra la respuesta, se aplica a todas las rutas, incluidas las anidadas.
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)
}
}
}
- El filtro
antes
, que agrega un encabezado de solicitud personalizado, se aplica solo a las dos rutas del método GET. - 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:
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();
}
}
}
}
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)
.
CorsWebFilter
especial.