Spring WebFlux — núcleo reativo

I. Visão geral

O módulo spring-web contém o seguinte suporte básico para aplicações web responsivas:

  • Existem dois níveis de suporte para manipulação de solicitações do servidor.
    • HttpHandler: Convenção básica para manipulação de solicitações HTTP, com E/S sem bloqueio e contrapressão de fluxos reativos e adaptadores para Reactor Netty, Undertow, Tomcat, Jetty e qualquer contêiner Servlet.
    • API WebHandler: Uma API web geral de nível um pouco superior para lidar com solicitações, na qual são estabelecidos modelos de programação específicos, como controladores anotados e terminais funcionais.
  • Para clientes, há uma convenção ClientHttpConnector básica para executar solicitações HTTP com E/S sem bloqueio e contrapressão de fluxos reativos, bem como adaptadores para Reactor Netty, Jetty HttpClient reativo e Apache HttpComponents. O WebClient de nível superior usado em aplicativos é construído com base nesta convenção básica.

2. HttpHandler

HttpHandler é um contrato simples com um único método para lidar com solicitações e respostas. É intencionalmente mínimo e seu principal e único objetivo é ser uma abstração mínima para diferentes APIs de servidores HTTP.

A tabela a seguir descreve as APIs de servidor suportadas:

servidor

API do servidor usada

Suporte a fluxos reativos

Netty

API Netty

Reator Netty

Ressaca

API Ressaca

spring-web: Ponte da Ressaca para Fluxos Reativos

gato

E/S sem bloqueio de servlet; leitura e gravação da API Tomcat ByteBuffers vs byte[]

spring-web: E/S sem bloqueio de servlet para ponte de fluxos reativos

Molhe

E/S sem bloqueio de servlet; API Jetty grava ByteBuffers vs byte[]

spring-web: E/S sem bloqueio de servlet para ponte de fluxos reativos

Contêiner de servlet

E/S sem bloqueio de servlet

spring-web: E/S sem bloqueio de servlet para ponte de fluxos reativos

A tabela a seguir descreve as dependências do servidor (consulte também as versões suportadas):

servidor

ID do grupo

Nome do artefato

Reator Netty

io.projectreactor.netty

reator-netty

Ressaca

io.undertow

núcleo de ressaca

gato

org.apache.tomcat.embed

tomcat-embed-core

Molhe

org.eclipse.jetty

servidor jetty, servlet jetty

O trecho de código a seguir mostra o uso do adaptador HttpHandler em cada API do servidor:

1、Reator Netty

Java

HttpHandler handler = ...
ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(handler);
HttpServer.create().host(host).port(port).handle(adapter).bindNow();

2、Ressaca

Java

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

3、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();

4、Molhe

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();

5. Contêiner de servlet

Para implementar em qualquer contêiner Servlet como um WAR, você pode subclassificar e incluir AbstractReactiveWebInitializer no WAR. Esta classe agrupa um HttpHandler com ServletHttpHandlerAdapter e o registra como um Servlet.

3.WebHandlerAPI

O pacote org.springframework.web.server é baseado no contrato HttpHandler e fornece uma API da Web comum para lidar com solicitações por meio de uma cadeia de vários WebExceptionHandlers, vários WebFilters e um único componente WebHandler. Essa cadeia pode ser combinada com o WebHttpHandlerBuilder simplesmente apontando para o Spring ApplicationContext que detecta automaticamente o componente e/ou registrando o componente no construtor.

Embora o HttpHandler tenha um objetivo simples de abstrair o uso de diferentes servidores HTTP, a API WebHandler visa fornecer um conjunto mais amplo de funcionalidades comumente usadas em aplicações web, como:

  • Sessão do usuário e atributos。
  • Solicitar atributos。
  • Resolva o local ou o principal da solicitação.
  • Acesse dados de formulário analisados ​​e armazenados em cache.
  • Abstração de dados multipartes.
  • espere. . .

1. Tipo de feijão especial

A tabela a seguir lista os componentes que WebHttpHandlerBuilder pode detectar automaticamente no Spring ApplicationContext ou pode ser registrado diretamente com ele:

Feijão

Tipo de feijão

ilustrar

ilustrar

<qualquer>

WebExceptionHandler

0..N

Fornece tratamento de exceções da cadeia de instâncias do WebFilter e do WebHandler de destino. Para obter mais detalhes, consulte Exceção.

<qualquer>

Filtro da Web

0..N

Aplique a lógica de interceptação antes e depois do restante da cadeia de filtros e do WebHandler de destino. Consulte Filtro para obter mais detalhes.

webHandler

WebHandler

1

O manipulador de solicitações.

webSessionManager

WebSessionManager

0..1

O gerenciador da instância WebSession, exposto por meio de um método ServerWebExchange. O padrão é DefaultWebSessionManager.

serverCodecConfigurer

ServerCodecConfigurer

0..1

Usado para acessar instâncias HttpMessageReader para analisar dados de formulário e dados multipartes e, em seguida, expô-los por meio de métodos no ServerWebExchange. Por padrão, ServerCodecConfigurer.create() é usado.

localeContextResolver

LocaleContextResolver

0..1

O analisador localeContext é exposto por meio de um método ServerWebExchange. O padrão é AcceptHeaderLocaleContextResolver.

forwardedHeaderTransformer

ForwardedHeaderTransformer

0..1

Usado para lidar com cabeçalhos de tipo encaminhado, você pode extraí-los e excluí-los ou apenas excluí-los. Não usado por padrão.

2. Dados do formulário

ServerWebExchange expõe os seguintes métodos para acessar dados de formulário:

Java

Mono<MultiValueMap<String, String>> getFormData();

DefaultServerWebExchange usa o HttpMessageReader configurado para analisar dados de formulário (application/x-www-form-urlencoded) em um MultiValueMap. Por padrão, FormHttpMessageReader é configurado para uso pelo bean ServerCodecConfigurer (consulte API do Web Handler).

3. Dados Multipartes

Veja o conteúdo correspondente na pilha de tecnologia Servlet

ServerWebExchange expõe os seguintes métodos para acessar dados multipartes:

Java

Mono<MultiValueMap<String, Part>> getMultipartData();

DefaultServerWebExchange usa o HttpMessageReader<MultiValueMap<String, Part>> configurado para analisar conteúdo multipart/form-data, multipart/mixed e multipart/relacionado em MultiValueMap. Por padrão, é DefaultPartHttpMessageReader, que não possui dependências de terceiros. Alternativamente, você pode usar SynchronossPartHttpMessageReader, que é baseado na biblioteca Synchronoss NIO Multipart. Ambos são configurados através do Bean ServerCodecConfigurer (consulte API Web Handler).

Para analisar dados multipartes de maneira streaming, você pode usar o Flux<PartEvent> retornado de PartEventHttpMessageReader em vez de usar @RequestPart, porque isso significa acesso semelhante ao mapa a uma única parte por nome e, portanto, os dados multipartes precisam ser ser totalmente analisado. Por outro lado, você pode usar @RequestBody para decodificar o conteúdo em Flux<PartEvent> sem coletar um MultiValueMap.

4、Cabeçalho encaminhado

Veja o conteúdo correspondente na pilha de tecnologia Servlet

Quando uma solicitação passa por um proxy (como um balanceador de carga), o host, a porta e o esquema podem mudar. Isso torna a criação de links para o host, porta e esquema corretos um desafio do ponto de vista do cliente.

A RFC 7239 define o cabeçalho HTTP encaminhado que os proxies podem usar para fornecer informações sobre a solicitação original. Existem outros cabeçalhos não padrão, incluindo X-Forwarded-Host, X-Forwarded-Port, X-Forwarded-Proto, X-Forwarded-SSL e X-Forwarded-Prefix.

ForwardedHeaderTransformer é um componente que modifica o host, a porta e o esquema da solicitação com base nos cabeçalhos encaminhados e, em seguida, exclui esses cabeçalhos. Se você declará-lo como um Bean denominado forwardedHeaderTransformer, ele será detectado e usado.

Existem considerações de segurança para cabeçalhos encaminhados porque os aplicativos não têm como saber se esses cabeçalhos foram adicionados por um proxy ou por um cliente malicioso. É por isso que os proxies nos limites de confiança devem ser configurados para descartar o tráfego encaminhado não confiável vindo de fora. Você também pode configurar ForwardedHeaderTransformer com removeOnly=true; nesse caso, ele removerá, mas não usará esses cabeçalhos.

Na versão 5.1, ForwardedHeaderFilter foi descontinuado e substituído por ForwardedHeaderTransformer para que os cabeçalhos encaminhados possam ser processados ​​antecipadamente antes de criar a troca. Se um filtro estiver configurado, ele será removido da lista de filtros e usará o ForwardedHeaderTransformer.

4. Filtrar

Veja o conteúdo correspondente na pilha de tecnologia Servlet

Na API WebHandler, você pode usar o WebFilter para aplicar lógica de interceptação antes e depois dos filtros e outras cadeias de processamento do WebHandler de destino. Ao usar a configuração do WebFlux, registrar um WebFilter é tão simples quanto declará-lo como Spring Bean e (opcionalmente) expressar a prioridade usando @Order na declaração do bean ou implementando Ordered.

1. CORS

Veja o conteúdo correspondente na pilha de tecnologia Servlet

Spring WebFlux fornece suporte refinado para configuração CORS por meio de anotações no controlador. No entanto, ao usá-lo com Spring Security, recomendamos confiar no CorsFilter integrado, que deve ser solicitado antes da cadeia de filtros do Spring Security.

Consulte as seções CORS e CORS WebFilter para obter mais detalhes.

5. Exceção

Veja o conteúdo correspondente na pilha de tecnologia Servlet

Na API WebHandler, você pode usar um WebExceptionHandler para manipular exceções de uma cadeia de instâncias WebFilter e WebHandlers de destino. Ao usar a configuração do WebFlux, registrar um WebExceptionHandler é tão simples quanto declará-lo como um Spring Bean e (opcionalmente) expressar a prioridade usando @Order na declaração do bean ou implementando Ordered.

A tabela a seguir descreve as implementações WebExceptionHandler disponíveis:

Manipulador de Exceções

ilustrar

ResponseStatusExceptionHandler

Fornece tratamento para exceções do tipo ResponseStatusException, definindo a resposta para o código de status HTTP da exceção.

WebFluxResponseStatusExceptionHandler

Uma extensão de ResponseStatusExceptionHandler que também pode determinar o código de status HTTP anotado @ResponseStatus de qualquer exceção.

Este manipulador é declarado na configuração do WebFlux.

6. Codecs

Veja o conteúdo correspondente na pilha de tecnologia Servlet

Os módulos spring-web e spring-core fornecem suporte para serialização e desserialização do conteúdo de bytes de objetos de alto nível por meio de E/S sem bloqueio e contrapressão de fluxos reativos. Este suporte é descrito abaixo:

  • Encoder e Decoder são contratos de baixo nível para codificação e decodificação de conteúdo independentemente de HTTP.
  • HttpMessageReader e https://docs.spring.io/spring-framework/docs/6.0.8-SNAPSHOT/javadoc-api/org/springframework/http/codec/HttpMessageWriter.html[HttpMessageWriter] são usados ​​para codificar e decodificar contrato.
  • Um codificador pode ser empacotado com EncoderHttpMessageWriter para torná-lo adequado para uso em aplicativos da Web, e um decodificador pode ser empacotado com DecoderHttpMessageReader.
  • DataBuffer abstrai diferentes representações de buffer de bytes (como Netty ByteBuf, java.nio.ByteBuffer, etc.) e é o objeto de trabalho de todos os codecs. Consulte Data Buffers e Codecs na seção "Spring Core" para saber mais sobre este tópico.

spring-core 模块提供 byte[]、ByteBuffer、DataBuffer、Resource 和 String 编码器和解码器的实现。spring-web 模块提供了 Jackson JSON、Jackson Smile、JAXB2、Protocol Buffers 和其他编码器和解码器,以及针对表单数据、multipart 内容、server-sent event 和其他的web专用HTTP消息读写器实现。

ClientCodecConfigurer and ServerCodecConfigurer are typically used to configure and customize the codecs to use in an application. See the section on configuring HTTP消息编解码器.

ClientCodecConfigurer 和 ServerCodecConfigurer 通常被用来配置和定制应用中使用的编解码器。参见配置 HTTP消息编解码器 的章节。

1、Jackson JSON

当Jackson库存在时,JSON和二进制JSON( Smile)都被支持。

Jackson2Decoder 的工作原理如下:

  • Jackson的异步、非阻塞解析器被用来将字节块流聚集到 TokenBuffer 中,每个字节块代表一个JSON对象。
  • 每个 TokenBuffer 被传递给 Jackson 的 ObjectMapper,以创建一个更高层次的对象。
  • 当解码到一个单值 publisher(例如 Mono)时,有一个 TokenBuffer。
  • 当解码到一个多值 publisher(如 Flux)时,每个 TokenBuffer 在收到足够的字节时就被传递给 ObjectMapper,以形成一个完整的对象。输入的内容可以是一个JSON数组,或任何 以行为单位的JSON 格式,如NDJSON,JSON行,或JSON文本序列。

Jackson2Encoder 的工作原理如下:

  • 对于一个单一的值 publisher(例如 Mono),只需通过 ObjectMapper 将其序列化。
  • 对于一个有 application/json 的多值 publisher,默认情况下用 Flux#collectToList() 来收集值,然后将得到的集合序列化。
  • 对于具有流媒体类型(如 application/x-ndjson 或 application/stream+x-jackson-smile)的多值 publisher,使用 以行为单位的JSON 格式对每个值进行编码、写入和刷出。其他流媒体类型可以在 encoder 上注册。
  • 对于SSE来说,Jackson2Encoder 在每个事件中被调用,output被刷出,以确保无延迟的交付。

默认情况下,Jackson2Encoder 和 Jackson2Decoder 都不支持 String 类型的元素。相反,默认的假设是一个字符串或一个字符串序列代表序列化的JSON内容,由 CharSequenceEncoder 来渲染。如果你需要的是从 Flux<String> 渲染一个JSON数组,使用 Flux#collectToList() 并编码一个 Mono<List<String>。

2、Form Data

FormHttpMessageReader 和 FormHttpMessageWriter 支持对 application/x-www-form-urlencoded 内容进行解码和编码。

在服务器端,表单内容经常需要从多个地方访问,ServerWebExchange 提供了一个专门的 getFormData() 方法,它通过 FormHttpMessageReader 解析内容,然后缓存结果以便重复访问。见 WebHandler API 部分的 表单(Form)数据 。

一旦使用 getFormData(),就不能再从请求体中读取原始的内容。由于这个原因,应用程序应该始终通过 ServerWebExchange 来访问缓存的表单数据,而不是从原始请求体中读取。

3、Multipart

MultipartHttpMessageReader 和 MultipartHttpMessageWriter 支持对 "multipart/form-data"、"multipart/mixed" 和 "multipart/related" 内容进行解码和编码。反过来, MultipartHttpMessageReader 委托给另一个 HttpMessageReader 来进行实际的解析到 Flux<Part>,然后简单地将这些 part 收集到一个 MultiValueMap 中。默认情况下,使用 DefaultPartHttpMessageReader,但这可以通过 ServerCodecConfigurer 改变。关于 DefaultPartHttpMessageReader 的更多信息,请参阅 DefaultPartHttpMessageReader 的javadoc。

在服务器端,如果 multipart 表单内容可能需要从多个地方访问,ServerWebExchange 提供了一个专门的 getMultipartData() 方法,该方法通过 MultipartHttpMessageReader 解析内容,然后缓存结果以便重复访问。参见 WebHandler API 部分的 Multipart Data。

一旦使用了 getMultipartData(),就不能再从请求体中读取原始的内容。由于这个原因,应用程序必须坚持使用 getMultipartData() 来重复、类似 map 的访问 part,或者依靠 SynchronossPartHttpMessageReader 来一次性访问 Flux<Part>。

4、边界(Limits)

缓存部分或全部 input stream 的 Decoder 和 HttpMessageReader 实现可以被配置为在内存中缓冲的最大字节数的限制。在某些情况下,缓冲的发生是因为输入被聚合并表示为一个单一的对象—​例如,一个带有 @RequestBody byte[] 的 controller 方法,x-www-form-urlencoded 数据,等等。缓冲也可能发生在流媒体中,当分割输入流时—​例如,限定的文本,JSON对象的流,等等。对于这些流的情况,限制适用于与流中一个对象相关的字节数。

为了配置缓冲区的大小,你可以检查一个给定的 Decoder 或 HttpMessageReader 是否暴露了一个 maxInMemorySize 属性,如果是的话,Javadoc 会有关于默认值的细节。在服务器端, ServerCodecConfigurer 提供了一个设置所有编解码器的单一位置,参见 HTTP消息编解码器。在客户端,所有编解码器的限制可以在 WebClient.Builder 中改变。

对于 Multipart 解析,maxInMemorySize 属性限制了非文件部分(part)的大小。对于文件 part,它决定了该 part 被写入磁盘的阈值。对于写入磁盘的文件 part,有一个额外的 maxDiskUsagePerPart 属性来限制每个 part 的磁盘空间量。还有一个 maxParts 属性,用于限制 multipart 请求中的总 part 数量。要在 WebFlux 中配置这三个属性,你需要向 ServerCodecConfigurer 提供一个预先配置的 MultipartHttpMessageReader 实例。

5、流(Stream)

参见 Servlet 技术栈中的相应内容

当流式HTTP响应(例如,text/event-stream,application/x-ndjson)时,定期发送数据是很重要的,以便可靠地尽早检测到一个断开连接的客户端,而不是更晚。这样的发送可以是一个仅有 comment 的、空的SSE事件或任何其他 "无操作" 的数据,这将有效地作为一个心跳。

6、DataBuffer

DataBuffer 是 WebFlux 中字节缓冲区的代表。本参考文献的Spring Core部分在 Data Buffer 和 Codec 部分有更多的介绍。需要理解的关键点是,在一些服务器上,如Netty,字节缓冲区是池化和引用计数的,在消耗时必须释放以避免内存泄漏。

WebFlux应用程序一般不需要关注这些问题,除非它们直接消费或生产数据缓冲区(data buffer),而不是依靠编解码器来转换为更高级别的对象,或者它们选择创建自定义编解码器。对于这种情况,请查看 Data Buffer 和 Codec 中的信息,特别是 使用 DataBuffer 的部分。

七、日志

参见 Servlet 技术栈中的相应内容

Spring WebFlux中的 DEBUG 级别日志被设计成紧凑、简约和人性化的。它专注于高价值的信息,这些信息可以反复使用,而其他信息只有在调试某个特定问题时才会有用。

TRACE 级别的日志通常遵循与 DEBUG 相同的原则(例如也不应该是火线),但可以用于调试任何问题。此外,一些日志信息在 TRACE 与 DEBUG 下可能显示不同的细节。

好的日志来自于使用日志的经验。如果你发现任何不符合既定目标的地方,请让我们知道。

1、日志 ID

在WebFlux中,一个请求可以在多个线程上运行,线程ID对于关联属于特定请求的日志消息没有用。这就是为什么WebFlux的日志消息默认以特定请求的ID为前缀。

在服务器端,日志ID存储在 ServerWebExchange 属性中( LOG_ID_ATTRIBUTE),而基于该ID的完全格式化的前缀可以从 ServerWebExchange#getLogPrefix() 获得。在 WebClient 端,日志ID存储在 ClientRequest attribute 中( LOG_ID_ATTRIBUTE),而完全格式化的前缀可以从 ClientRequest#logPrefix() 中获得。

2、敏感数据

参见 Servlet 技术栈中的相应内容

DEBUG 和 TRACE 日志可以记录敏感信息。这就是为什么表单参数和 header 在默认情况下是被屏蔽的,你必须明确地完全启用它们的日志。

下面的例子显示了如何对服务器端的请求进行处理:

Java

@Configuration
@EnableWebFlux
class MyConfig implements WebFluxConfigurer {

    @Override
    public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
        configurer.defaultCodecs().enableLoggingRequestDetails(true);
    }
}

下面的例子显示了如何对客户端的请求进行处理:

Java

Consumer<ClientCodecConfigurer> consumer = configurer ->
        configurer.defaultCodecs().enableLoggingRequestDetails(true);

WebClient webClient = WebClient.builder()
        .exchangeStrategies(strategies -> strategies.codecs(consumer))
        .build();

3、Appender

SLF4J 和 Log4J 2 等日志库提供了避免阻塞的异步 logger。虽然这些都有自己的缺点,比如可能会丢弃无法排队记录的消息,但它们是目前在响应式、非阻塞式应用中使用的最佳可用选项。

4、自定义编解码器

应用程序可以注册自定义编解码器,以支持额外的媒体类型,或默认编解码器不支持的特定行为。

开发者表达的一些配置选项在默认的编解码器上被强制执行。自定义编解码器可能希望得到一个与这些偏好相一致的机会,比如 强制执行缓冲限制 或 记录敏感数据。

下面的例子显示了如何对客户端的请求进行处理:

Java

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

Guess you like

Origin blog.csdn.net/leesinbad/article/details/132888128