• Hay dos niveles de soporte para manejar solicitudes del servidor.

    • HttpHandler: contrato básico para manejar solicitudes HTTP con E/S sin bloqueo y devolución de llamada según Reactive Streams de especificación, así como adaptadores para Reactor Netty, Undertow, Tomcat, Jetty y cualquier contenedor Servlet 3.1+.

    • WebHandler API: una API ligeramente superior nivel web Una API de propósito general para procesar solicitudes, sobre la cual se construyen modelos de programación específicos, como controladores anotados y puntos finales funcionales.

  • Para el cliente Por otro lado, existe el contrato base ClientHttpConnector para realizar solicitudes HTTP con E/S sin bloqueo y retroalimentación de acuerdo con la especificación Reactive Streams, así como adaptadores para Reactor Netty, reactivo Jetty HttpClient y Apache HttpComponents. El WebClient de nivel superior utilizado en las aplicaciones se basa en este contrato básico.

  • Se proporcionan códecs para que el cliente y el servidor serialicen y deserialicen el contenido de las solicitudes y respuestas HTTP.

HttpHandler

HttpHandler es un contrato simple con un único método para manejar la solicitud y la respuesta. Es intencionalmente estricto y su principal y único propósito es ser una simple abstracción de las distintas API del servidor HTTP.

La siguiente tabla describe las API del servidor admitidas:

Servidor nombre API de servidor utilizada Soporte para flujos reactivos

Netty

Netty API

Reactor Netty

Undertow

API de Undertow

spring-web: Conexión al puente Reactive Streams

Tomcat

E/S sin bloqueo basada en Servlet 3.1; API de Tomcat para leer y escribir ByteBuffers frente a byte[]

spring-web: E/S sin bloqueo de Servlet 3.1 a puente de flujos reactivos

Jetty

E/S sin bloqueo basada en Servlet 3.1; Jetty API para escribir ByteBuffers frente a byte[]

spring-web: puente de E/S sin bloqueo de Servlet 3.1 a flujos reactivos

Contenedor de Servlet 3.1

E/S sin bloqueo basada en Servlet 3.1

spring-web: E/S de Servlet 3.1 sin bloqueo al puente Reactive Streams

La siguiente tabla describe las dependencias del servidor (Consulte también versiones compatibles):

Nombre del servidor ID de grupo Nombre del artefacto

Reactor Netty

io.projectreactor.netty

reactor-netty

Undertow

io.undertow

undertow-core

Tomcat

org.apache.tomcat.embed

tomcat-embed-core

Jetty

org. eclipse.jetty

jetty-server, jetty-servlet

Los fragmentos de código a continuación se muestra el uso de los adaptadores HttpHandler con cada API de servidor:

Reactor Netty

Java

HttpHandler handler = ...
ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(handler);
HttpServer.create().host(host).port(port).handle(adapter).bind().block();
    
Kotlin

val handler: HttpHandler = ...
val adapter = ReactorHttpHandlerAdapter (handler)
HttpServer.create().host(host).port(port).handle(adapter).bind().block()

Undertow

Java

HttpHandler handler = ...
UndertowHttpHandlerAdapter adapter = new UndertowHttpHandlerAdapter(handler);
Undertow server = Undertow.builder().addHttpListener(port, host).setHandler(adapter).build();
server.start();    
Kotlin

val handler: HttpHandler = ...
val adapter = UndertowHttpHandlerAdapter(handler)
val server = Undertow.builder ().addHttpListener(port, host).setHandler(adapter).build()
server.start()

Tomcat

Java

HttpHandler handler = ...
Servlet servlet = new TomcatHttpHandlerAdapter(handler);
Tomcat server = new Tomcat();
File base = new File(System.getProperty("java.io.tmpdir"));
Context rootContext = server.addContext("", base.getAbsolutePath());
Tomcat.addServlet(rootContext, "main", servlet);
rootContext.addServletMappingDecoded("/", "main");
server.setHost(host);
server.setPort(port);
server.start();
Kotlin

val handler: HttpHandler = ...
val servlet = TomcatHttpHandlerAdapter(handler)
val server = Tomcat()
val base = File(System.getProperty("java.io.tmpdir"))
val rootContext = server.addContext("", base.absolutePath)
Tomcat.addServlet(rootContext, "main", servlet)
rootContext.addServletMappingDecoded("/", "main")
server.host = host
server.setPort(port)
server.start()

Embarcadero

Java

HttpHandler handler = ...
Servlet servlet = new JettyHttpHandlerAdapter(handler);
Server server = new Server();
ServletContextHandler contextHandler = new ServletContextHandler(server, "");
contextHandler.addServlet(new ServletHolder(servlet), "/");
contextHandler.start();
ServerConnector connector = new ServerConnector(server);
connector.setHost(host);
connector.setPort(port);
server.addConnector(connector);
server.start();
Kotlin

val handler: HttpHandler = ...
val servlet = JettyHttpHandlerAdapter(handler)
val server = Server()
val contextHandler = ServletContextHandler(server, "")
contextHandler.addServlet(ServletHolder(servlet), "/")
contextHandler.start(); val
connector = ServerConnector(server)
connector.host = host connector.port = port
server.addConnector(connector)
server.start()

Contenedor de Servlet 3.1+

Para implementar el archivo WAR en cualquier contenedor de Servlet 3.1+, puede expandirlo para incluir AbstractReactiveWebInitializer a un archivo WAR. Esta clase envuelve un HttpHandler en un ServletHttpHandlerAdapter y lo registra como un Servlet.

WebHandler API

El paquete org.springframework.web.server se basa en el contrato HttpHandler y proporciona una API web de propósito general para procesar solicitudes a través de una cadena de varios WebExceptionHandler, varios WebFilter y un componente WebHandler. La cadena se puede armar con WebHttpHandlerBuilder simplemente apuntando al ApplicationContext de Spring, donde los beans se definen automáticamente, y/o registrando los beans en el constructor.

Si bien el propósito de HttpHandler es simple: abstraer el uso de varios servidores HTTP, la API WebHandler tiene como objetivo proporcionar un conjunto más amplio de funciones. Comúnmente utilizado en aplicaciones web, como:

  • Sesión de usuario con atributos.

  • Solicitar atributos.

  • Se permite Locale o Principal para la solicitud.

  • Acceso a datos de formulario analizados y almacenados en caché .

  • Abstracciones para datos multicomponente.

  • y mucho más...

Tipos de beans especializados

La siguiente tabla enumera los componentes que WebHttpHandlerBuilder puede descubrir automáticamente en ApplicationContext de Spring, o que se pueden registrar directamente con it:

Nombre del frijol Tipo de frijol Contador Descripción

<any>

WebExceptionHandler

0 ..N

Proporciona manejo de excepciones desde la cadena de instancias WebFilter y la destino WebHandler.

<any>

WebFilter

0..N

Aplica lógica de estilo interceptación antes y después del resto de la cadena de filtro y el WebHandler de destino.

webHandler

WebHandler

1

Manejador de solicitudes.

webSessionManager

WebSessionManager

0..1

Dispatcher para instancias WebSession abiertas a través del método para ServidorWebExchange. DefaultWebSessionManager de forma predeterminada.

serverCodecConfigurer

ServerCodecConfigurer

0..1

Proporciona acceso a instancias de HttpMessageReader para analizar datos de formularios y datos de varias partes, que luego se exponen mediante métodos a ServerWebExchange. ServerCodecConfigurer.create() de forma predeterminada.

localeContextResolver

LocaleContextResolver

0..1

Resolvedor para LocaleContext, expuesto a través de un método para ServerWebExchange. AcceptHeaderLocaleContextResolver de forma predeterminada.

forwardedHeaderTransformer

ForwardedHeaderTransformer

0..1

Diseñado para procesar encabezados de reenviados tipos, ya sea extrayéndolos y eliminándolos, o eliminándolos únicamente. No se utiliza de forma predeterminada.

Datos del formulario

ServerWebExchange proporciona el siguiente método para acceso a los datos del formulario:

Java
Mono<MultiValueMap<String, String>> getFormData();
Kotlin
suspend fun getFormData(): MultiValueMap<String, String><

El DefaultServerWebExchange utiliza el HttpMessageReader configurado para analizar los datos del formulario (application/x-www-form-urlencoded) en un MultiValueMap. De forma predeterminada, FormHttpMessageReader está configurado para ser utilizado por el bean ServerCodecConfigurer.

Datos multiparte

ServerWebExchange proporciona el siguiente método para acceder a datos de varias partes:

Java
Mono<MultiValueMap<String, Part>> getMultipartData();
Kotlin
suspend fun getMultipartData(): MultiValueMap<String, Part>

DefaultServerWebExchange utiliza el HttpMessageReader<MultiValueMap<String, Part>> configurado para analizar el contenido de multipart/form-data en MultiValueMap. El valor predeterminado es DefaultPartHttpMessageReader, que no tiene dependencias de terceros. Como alternativa, puede utilizar SynchronossPartHttpMessageReader, que se basa en Synchronoss NIO Multipart biblioteca. Ambos se configuran utilizando el bean ServerCodecConfigurer.

Para el análisis en streaming de datos de varias partes, puede utilizar Flux<Part> devuelto por HttpMessageReader<Part>. Por ejemplo, en un controlador anotado, el uso de @RequestPart implica un acceso tipo Map a componentes individuales por nombre y, por lo tanto, requiere un análisis completo de los datos de varias partes. Por el contrario, puede utilizar la anotación @RequestBody para decodificar el contenido en Flux<Part> sin recopilarlo en MultiValueMap.

Encabezados reenviados

Si la solicitud pasa por servidores proxy (como equilibradores de carga), el host, el puerto y el esquema pueden cambiar. Esto dificulta que el cliente cree enlaces que apunten al host, puerto y esquema correctos.

RFC 7239 define un encabezado HTTP Reenviado que los servidores proxy pueden utilizar para proporcionar información sobre la solicitud original. Hay otros encabezados no estándar, incluidos X-Forwarded-Host, X-Forwarded-Port, X-Forwarded-Proto, X -Forwarded-Ssl y X-Forwarded-Prefix.

ForwardedHeaderTransformer es un componente que cambia el host, el puerto y la solicitud. esquema basado en los encabezados reenviados y luego elimina esos encabezados. Si lo declara como un bean llamado forwardedHeaderTransformer, será detectado y utilizado.

Existen algunas precauciones de seguridad para los encabezados reenviados, ya que la aplicación no puede saber si se agregaron los encabezados. por un servidor proxy, como se esperaba, o por un cliente malicioso. Esta es la razón por la que el proxy en el límite de confianza debe configurarse para eliminar el tráfico directo no confiable proveniente del exterior. También puede configurar ForwardedHeaderTransformer para usar removeOnly=true, en cuyo caso eliminará pero no usará los encabezados.

En la versión 5.1, ForwardedHeaderFilter ha quedado obsoleto y reemplazado por ForwardedHeaderTransformer, por lo que los encabezados reenviados se pueden procesar antes, antes de que se cree el intercambio. Si el filtro está configurado de todos modos, se elimina de la lista de filtros y en su lugar se utiliza ForwardedHeaderTransformer.

Filtros

En el WebHandler API, puede utilizar WebFilter para aplicar lógica de estilo interceptación antes y después del resto de la cadena de procesamiento del filtro y el WebHandler de destino. Cuando se utiliza la configuración de WebFlux, registrar un WebFilter es extremadamente simple: lo declaramos como un Spring Bean y (opcionalmente) expresamos el nivel de precedencia usando la anotación @Order en el bean. declaración o implementando la Clase ordenada.

CORS

Spring WebFlux proporciona soporte de configuración CORS detallado a través de anotaciones del controlador. Sin embargo, si lo está utilizando con Spring Security, le recomendamos confiar en el CorsFilter integrado, que debe estar en orden delante de toda la cadena de filtros de Spring Security.

Excepciones

En la API WebHandler, puede usar WebExceptionHandler para manejar excepciones de una cadena de instancias WebFilter y un objetivo. WebHandler . Cuando se utiliza una configuración WebFlux, registrar un WebExceptionHandler es tan simple como declararlo como un Spring Bean y (opcionalmente) expresar precedencia usando la anotación @Order en la declaración del bean o mediante implementando una clase Ordered.

La siguiente tabla describe las implementaciones de WebExceptionHandler disponibles:

Manejador de excepciones Descripción

ResponseStatusExceptionHandler

Proporciona manejo de excepciones de tipo ResponseStatusException estableciendo la respuesta al código de estado HTTP correspondiente a la excepción.

WebFluxResponseStatusExceptionHandler

La extensión ResponseStatusExceptionHandler, que también puede determinar el código de estado HTTP a partir de la anotación @ResponseStatus para cualquier excepción.

Este controlador está declarado en WebFlux Config.

Códecs

Módulos spring-web y spring-core proporcionan un medio para admitir la serialización y deserialización de contenido de bytes hacia y desde objetos de nivel superior a través de E/S inversa sin bloqueo de la especificación Reactive Streams. Estos soportes se describen a continuación:

  • Encoder y Decoder son contratos de bajo nivel para codificar y decodificar contenido independientemente de HTTP protocolo.

  • HttpMessageReader y HttpMessageWriter son contratos para codificar y decodificar el contenido de mensajes HTTP.

  • Encoder se puede empaquetar en EncoderHttpMessageWriter para adaptarlo para su uso en una aplicación web y en Decoder se puede incluir en DecoderHttpMessageReader.

  • DataBuffer abstrae varias representaciones de buffers de bytes (por ejemplo, ByteBuf, java.nio.ByteBuffer para Netty, etc.), y esto es con lo que funcionan todos los códecs.

El módulo spring-core contiene implementaciones del codificador y decodificador byte[], ByteBuffer, DataBuffer, Recurso y Cadena. El módulo spring-web contiene Jackson JSON, Jackson Smile, JAXB2, Protocol Buffers y otros codificadores y decodificadores junto con implementaciones de lectores y escritores de mensajes HTTP centrados en la web para datos de formularios, contenido de varias partes y eventos enviados. servidor, etc.

ClientCodecConfigurer y ServerCodecConfigurer se utilizan normalmente para configurar y configurar códecs para su uso en una aplicación.

Jackson JSON

El formato JSON y el JSON binario (Smile) son compatibles donde bibliotecas Jackson disponibles.

Jackson2Decoder funciona de la siguiente manera:

  • El analizador asincrónico y sin bloqueo de la biblioteca Jackson se utiliza para combinar una flujo de fragmentos de bytes en instancias de TokenBuffer, cada una de las cuales es un objeto JSON.

  • Cada TokenBuffer se pasa a la biblioteca ObjectMapper Jackson para crear un objeto de nivel superior.

  • Al decodificar a un editor de valor único (como Mono), hay un TokenBuffer.

  • Al decodificar a un editor de valores múltiples (por ejemplo, Flux ), cada TokenBuffer se pasa a un ObjectMapper tan pronto como se hayan recibido suficientes bytes de información para un objeto completamente formado. El contenido de entrada puede ser una matriz JSON o cualquier formato línea JSON delimitada como NDJSON, líneas JSON o texto JSON. Secuencias.

Jackson2Encoder funciona así:

  • Para un editor con un solo valor (por ejemplo, Mono), simplemente serialícelo mediante ObjectMapper.

  • Para un editor de valores múltiples con application/json de forma predeterminada, recopile los valores usando Flux#collectToList() y luego serialice la colección resultante.

  • Para un editor de valores múltiples con un tipo de medio de transmisión por secuencias, como application/x-ndjson o application/stream+x-jackson-smile, codifique, escriba y restablezca cada uno. valor por separado usando el formato JSON con delimitación de líneas. Se pueden registrar otros tipos de medios de transmisión con el codificador.

  • En el caso de SSE (eventos enviados por el servidor), se llama a Jackson2Encoder para cada evento. y la salida se restablece para garantizar la transmisión sin demora.

Predeterminado y Jackson2Encoder y Jackson2Decoder no admiten elementos String. En cambio, de forma predeterminada, se supone que la cadena o secuencia de cadenas es contenido JSON serializado, que se representará mediante CharSequenceEncoder. Si necesita generar una matriz JSON desde Flux<String>, use Flux#collectToList() y codifique Mono<List<String>>.

Datos de formulario

FormHttpMessageReader y FormHttpMessageWriter admiten la decodificación y codificación de application/x -www- content form-urlencoded.

En el lado del servidor, donde a menudo se debe acceder al contenido del formulario desde varios lugares, ServerWebExchange proporciona un método especial getFormData(), que analiza el contenido a través de FormHttpMessageReader y luego almacena en caché el resultado para volver a acceder.

Después de usar getFormData() el contenido original sin formato ya no se puede leer desde el cuerpo de la solicitud. Por este motivo, se espera que las aplicaciones pasen secuencialmente a través de ServerWebExchange para acceder a los datos del formulario almacenados en caché en lugar de leer desde el cuerpo de la solicitud sin procesar.

Multicomponente

MultipartHttpMessageReader y MultipartHttpMessageWriter admiten la decodificación y codificación de contenido "multipart/form-data". A su vez, el MultipartHttpMessageReader delega a otro HttpMessageReader para realizar el análisis real en un Flux<Part> y luego simplemente ensambla las piezas en un MultiValueMap código>. El valor predeterminado es DefaultPartHttpMessageReader, pero se puede cambiar usando ServerCodecConfigurer. Para obtener más información sobre DefaultPartHttpMessageReader, consulte javadoc en DefaultPartHttpMessageReader.

En el lado del servidor, donde el contenido de un formulario de varias partes puede Es necesario acceder desde varios lugares, ServerWebExchange proporciona un método especial getMultipartData() que analiza el contenido a través de MultipartHttpMessageReader y luego almacena en caché el resultado para volver a acceder.

Después de usar el parámetro getMultipartData(), el contenido original sin procesar ya no se puede leer desde el cuerpo de la solicitud. Por este motivo, las aplicaciones deben usar constantemente el parámetro getMultipartData() para acceder a los componentes varias veces como un mapa, o confiar en SynchronossPartHttpMessageReader para acceder a Flux<Part>.

Limitaciones

Las implementaciones de Decoder y HttpMessageReader que almacenan en buffer parte o la totalidad del flujo de entrada se pueden configurar con un límite en el número máximo de bytes para almacenar en el buffer en la memoria. En algunos casos, el almacenamiento en búfer se produce porque los datos de entrada se agregan y se representan como un único objeto; por ejemplo, un método de controlador con la anotación @RequestBody byte[], x-www-form-urlencoded codificados en urlen, etc. El almacenamiento en búfer también puede ocurrir en la transmisión si la secuencia de entrada está dividida (por ejemplo, texto delimitado, una secuencia JSON de objetos, etc.). Para estos casos de transmisión, el límite se aplica a la cantidad de bytes asociados con un solo objeto en la transmisión.

Para configurar los tamaños del búfer, puede verificar si un Decoder o HttpMessageReader se está abriendo.code> maxInMemorySize y, de ser así, el Javadoc contendrá detalles sobre los valores predeterminados. Del lado del servidor, ServerCodecConfigurer proporciona una única ubicación desde la que se pueden instalar todos los códecs. En el lado del cliente, el límite para todos los códecs se puede cambiar en WebClient.Builder.

Para el análisis de múltiples componentes, la propiedad maxInMemorySize limita el tamaño de los componentes que no son archivos. Para los componentes de archivos, define el umbral en el que el componente se escribe en el disco. Para los componentes de archivos escritos en el disco, existe una propiedad adicional maxDiskUsagePerPart que limita la cantidad de espacio en disco para cada componente. También hay una propiedad maxParts para limitar el número total de componentes en una solicitud de varias partes. Para configurar los tres componentes en WebFlux, debe especificar una instancia preconfigurada de MultipartHttpMessageReader en ServerCodecConfigurer.

Streaming

Al transmitir datos en una respuesta HTTP (por ejemplo, text/event-stream, application/x-ndjson), es importante transmitir los datos periódicamente para poder detectar con precisión un cliente desconectado lo antes posible. De esta manera, solo puede enviar un comentario, un evento SSE vacío o cualquier otro dato de "operaciones vacías" que sirva efectivamente como un mensaje de latido.

DataBuffer

DataBuffer es una representación de un búfer de bytes en WebFlux. Es importante comprender que en algunos servidores, como Netty, los buffers de bytes se agrupan y cuentan por referencia, y deben desasignarse cuando se consumen para evitar pérdidas de memoria.

Para aplicaciones WebFlux, generalmente no hay necesidad preocuparse por tales problemas, a menos que consuman y produzcan buffers de datos directamente en lugar de usar códecs para convertir hacia y desde objetos de nivel superior, o a menos que creen sus propios códecs.

Registro

El registro en el nivel DEBUG en Spring WebFlux está diseñado para ser compacto, simple y amigable para los humanos. Se centra en los fragmentos de información más importantes que se utilizarán una y otra vez, a diferencia de otros que sólo se utilizan al depurar un problema específico.

Registro en el nivel TRACE generalmente sigue los mismos principios que DEBUG (y, por ejemplo, tampoco debe sobrecargarse), pero puede usarse para depurar cualquier problema. Además, algunos mensajes de registro pueden presentar diferentes niveles de detalle en los niveles TRACE y DEBUG.

El registro adecuado depende de la experiencia con los registros. Si nota algo que no cumple con los objetivos establecidos, háganoslo saber.

ID de registro

En WebFlux, una sola solicitud se puede ejecutar en varios subprocesos y el ID del subproceso Es inútil comparar mensajes de registro relacionados con una solicitud específica. Esta es la razón por la que los mensajes de registro de WebFlux tienen como prefijo predeterminado el ID de solicitud específico.

En el lado del servidor, el ID de registro se almacena en el atributo ServerWebExchange (LOG_ID_ATTRIBUTE) y un prefijo completamente formateado basado en este identificador está disponible en ServerWebExchange#getLogPrefix(). En el lado WebClient, el ID de registro se almacena en el atributo ClientRequest (LOG_ID_ATTRIBUTE), y el formato completo El prefijo está disponible en ClientRequest#logPrefix().

Datos confidenciales

Registro en DEBUG y los niveles TRACE pueden registrar información confidencial. Esta es la razón por la que los parámetros y encabezados de los formularios están enmascarados de forma predeterminada y es necesario habilitar explícitamente el registro completo de ellos.

El siguiente ejemplo muestra cómo hacer esto para solicitudes del lado del servidor:

Java

@Configuration
@EnableWebFlux
class MyConfig implements WebFluxConfigurer {
@Override
public void
configureHttpMessageCodecs(ServerCodecConfigurer configurer ) {
configurer.defaultCodecs().enableLoggingRequestDetails(true);
}
}
Kotlin

@Configuration
@EnableWebFlux
class  MyConfig : WebFluxConfigurer {
 override fun  configureHttpMessageCodecs(configurer: ServerCodecConfigurer) {
configurer.defaultCodecs().enableLoggingRequestDetails(true)
}
}

El siguiente ejemplo muestra cómo hacer esto para solicitudes del lado del cliente:

Java

Consumer<ClientCodecConfigurer> ; consumer = configurer ->
configurer.defaultCodecs().enableLoggingRequestDetails(true);
WebClient webClient = WebClient.builder()
.exchangeStrategies(strategies -> strategies.codecs(consumer))
.build();
Kotlin

val consumer: (ClientCodecConfigurer) -> Unit = { configurer -> configurer.defaultCodecs().enableLoggingRequestDetails(true) }
val webClient = WebClient.builder()
.exchangeStrategies( { strategies -> strategies.codecs(consumer) })
.build()

Anexores

Registro de bibliotecas como SLF4J y Log4J 2 proporciona registradores asincrónicos que evitan el bloqueo. Si bien tienen sus inconvenientes, como la posibilidad de que falten mensajes que no se pueden poner en cola para su registro, son las mejores opciones disponibles para usar en aplicaciones reactivas y sin bloqueo.

Códecs personalizados

Las aplicaciones pueden registrar códecs personalizados para admitir tipos de transferencia de datos adicionales o lógica operativa específica que no son compatibles con los códecs predeterminados.

Algunas opciones de configuración expresadas por los desarrolladores se aplican a los códecs predeterminados. Es posible que sea necesario obtener la capacidad de los códecs personalizados para cumpla con estas configuraciones, como forzar el límite de almacenamiento en búfer o el registro de datos confidenciales.

El siguiente ejemplo muestra cómo hacer esto para solicitudes del lado del cliente:

Java

WebClient webClient = WebClient.builder()
.codecs(configurer -> {
        CustomDecoder decoder = new CustomDecoder();
        configurer.customCodecs().registerWithDefaultConfig(decoder);
})
.build();
Kotlin

val webClient = WebClient.builder()
.codecs({ configurer ->
        val decoder = CustomDecoder()
        configurer.customCodecs().registerWithDefaultConfig(decoder)
})
.build()