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 |
|
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
HttpHandler handler = ...
ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(handler);
HttpServer.create().host(host).port(port).handle(adapter).bind().block();
val handler: HttpHandler = ...
val adapter = ReactorHttpHandlerAdapter (handler)
HttpServer.create().host(host).port(port).handle(adapter).bind().block()
Undertow
HttpHandler handler = ...
UndertowHttpHandlerAdapter adapter = new UndertowHttpHandlerAdapter(handler);
Undertow server = Undertow.builder().addHttpListener(port, host).setHandler(adapter).build();
server.start();
val handler: HttpHandler = ...
val adapter = UndertowHttpHandlerAdapter(handler)
val server = Undertow.builder ().addHttpListener(port, host).setHandler(adapter).build()
server.start()
Tomcat
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();
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
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();
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
oPrincipal
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> |
|
0 ..N |
Proporciona manejo de excepciones desde la cadena de instancias |
<any> |
|
0..N |
Aplica lógica de estilo interceptación antes y después del resto de la cadena de filtro y el |
|
|
1 |
Manejador de solicitudes. |
|
|
0..1 |
Dispatcher para instancias |
|
|
0..1 |
Proporciona acceso a instancias de |
|
|
0..1 |
Resolvedor para |
|
|
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:
Mono<MultiValueMap<String, String>> getFormData();
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:
Mono<MultiValueMap<String, Part>> getMultipartData();
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.
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 |
---|---|
|
Proporciona manejo de excepciones de tipo |
|
La extensió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
yDecoder
son contratos de bajo nivel para codificar y decodificar contenido independientemente de HTTP protocolo.HttpMessageReader
yHttpMessageWriter
son contratos para codificar y decodificar el contenido de mensajes HTTP.Encoder
se puede empaquetar enEncoderHttpMessageWriter
para adaptarlo para su uso en una aplicación web y enDecoder
se puede incluir enDecoderHttpMessageReader
.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 bibliotecaObjectMapper
Jackson para crear un objeto de nivel superior.Al decodificar a un editor de valor único (como
Mono
), hay unTokenBuffer
.Al decodificar a un editor de valores múltiples (por ejemplo,
Flux
), cadaTokenBuffer
se pasa a unObjectMapper
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 medianteObjectMapper
.Para un editor de valores múltiples con
application/json
de forma predeterminada, recopile los valores usandoFlux#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
oapplication/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:
@Configuration
@EnableWebFlux
class MyConfig implements WebFluxConfigurer {
@Override
public void configureHttpMessageCodecs(ServerCodecConfigurer configurer ) {
configurer.defaultCodecs().enableLoggingRequestDetails(true);
}
}
@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:
Consumer<ClientCodecConfigurer> ; consumer = configurer ->
configurer.defaultCodecs().enableLoggingRequestDetails(true);
WebClient webClient = WebClient.builder()
.exchangeStrategies(strategies -> strategies.codecs(consumer))
.build();
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:
WebClient webClient = WebClient.builder()
.codecs(configurer -> {
CustomDecoder decoder = new CustomDecoder();
configurer.customCodecs().registerWithDefaultConfig(decoder);
})
.build();
val webClient = WebClient.builder()
.codecs({ configurer ->
val decoder = CustomDecoder()
configurer.customCodecs().registerWithDefaultConfig(decoder)
})
.build()
GO TO FULL VERSION