解决 Spring WebFlux DataBufferLimitException

1、简介

本文将将会介绍在 Spring WebFlux 应用中出现 DataBufferLimitException 的原因,以及解决办法。

2、原因

在讲解决方案前,先解释一下这个异常的原因。

2.1、DataBufferLimitException 是啥?

Spring WebFlux 限制 了编解码器(codec)中 Buffer 的内存大小,以避免应用出现内存问题。默认情况下,配置为 262,144 字节。如果超出限制,就会导致 DataBufferLimitException 异常。

2.2、编解码器是啥?

spring-webspring-core 模块通过非阻塞 I/O 和 Reactive Stream 背压提供了将字节内容序列化和反序列化为更高级对象的支持。编解码器 提供了一种替代 Java 序列化的方法。其中一个 优点 是,通常对象不需要实现 Serializable 接口。

3、服务端

先看服务端的 DataBufferLimitException 是如何发生的。

3.1、问题复现

尝试向 Spring WebFlux 服务器发送大小为 390 KB 的 JSON 请求体以触发异常。

使用 curl 命令向服务器发送 POST 请求:

curl --location --request POST 'http://localhost:8080/1.0/process' \
  --header 'Content-Type: application/json' \
  --data-binary '@/tmp/390KB.json'

你可以看到,抛出了 DataBufferLimitException 异常:

org.springframework.core.io.buffer.DataBufferLimitException: Exceeded limit on max bytes to buffer : 262144
  at org.springframework.core.io.buffer.LimitedDataBufferList.raiseLimitException(LimitedDataBufferList.java:99) ~[spring-core-5.3.23.jar:5.3.23]
  Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: 
Error has been observed at the following site(s):
  *__checkpoint ⇢ HTTP POST "/1.0/process" [ExceptionHandlingWebHandler]

3.2、解决办法

可以使用 WebFluxConfigurer 接口来配置这个阈值。

添加一个新的配置类,名为 WebFluxConfiguration

@Configuration
public class WebFluxConfiguration implements WebFluxConfigurer {
    @Override
    public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
        configurer.defaultCodecs().maxInMemorySize(500 * 1024);
    }
}

也可以在 application properties/yaml 中配置:

spring:
  codec:
    max-in-memory-size: 500KB

配置属性可以参考 官方文档

4、客户端

接着,看看客户端的 DataBufferLimitException 是如何发生的。

4.1、问题复现

使用 WebFlux 的 WebClient 复现异常。

创建一个 Handler,尝试往服务器发起 POST 请求,Body 大小为 390 KB:

public Mono<Users> fetch() {
    return webClient
      .post()
      .uri("/1.0/process")
      .body(BodyInserters.fromPublisher(readRequestBody(), Users.class))
      .exchangeToMono(clientResponse -> clientResponse.bodyToMono(Users.class));
}

你可以看到,抛出了相同的 DataBufferLimitException 异常,但是这次是由于 webClient 发送的请求体(Body)大小超出了限制导致的。

org.springframework.core.io.buffer.DataBufferLimitException: Exceeded limit on max bytes to buffer : 262144
  at org.springframework.core.io.buffer.LimitedDataBufferList.raiseLimitException(LimitedDataBufferList.java:99) ~[spring-core-5.3.23.jar:5.3.23]
  Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: 
Error has been observed at the following site(s):
    *__checkpoint ⇢ Body from POST http://localhost:8080/1.0/process [DefaultClientResponse]
    *__checkpoint ⇢ Handler com.baeldung.spring.reactive.springreactiveexceptions.handler.TriggerHandler@428eedd9 [DispatcherHandler]
    *__checkpoint ⇢ HTTP POST "/1.0/trigger" [ExceptionHandlingWebHandler]

4.2、解决办法

可以通过编程式配置、创建 WebClient,以修改此阈值,如下:

@Bean("progWebClient")
    public WebClient getProgSelfWebClient() {
        return WebClient
          .builder()
          .baseUrl(host)
          .exchangeStrategies(ExchangeStrategies
        .builder()
        .codecs(codecs -> codecs
            .defaultCodecs()
            .maxInMemorySize(500 * 1024))
        .build())
            .build();
}

同样,也可以在配置中进行修改:

spring:
  codec:
    max-in-memory-size: 500KB

需要注意的是,配置中的属性,会影响整个应用,包括服务器可客户端。

因此,如果只想为特定的 WebClient 配置此限制,那么这不是一个理想的解决方案。此外,这种方法存在一个注意事项。用于创建 WebClientBuilder 必须像下面这样由 Spring 自动装配:

@Bean("webClient")
  public WebClient getSelfWebClient(WebClient.Builder builder) {
  return builder.baseUrl(host).build();
}

5、总结

在本文中,我们了解了什么是 DataBufferLimitException,以及如何在服务端和客户端通过编程或属性配置的方式来解决这个异常。


参考:https://www.baeldung.com/spring-webflux-databufferlimitexception