1. 概述

Spring 5 添加了一个全新的框架 - Spring WebFlux ,它支持我们的 Web 应用程序中的响应式编程。为了执行HTTP请求,我们可以使用 WebClient 接口,它提供了基于Reactor Project的功能API。

在本教程中,我们将重点关注 WebClient 的超时设置 。我们将讨论不同的方法,如何正确设置不同的超时,无论是在整个应用程序中全局设置还是特定于请求。

2. WebClient 和 HTTP 客户端

在继续之前,让我们快速回顾一下。 Spring WebFlux 包含它自己的客户端 WebClient,以响应式方式执行 HTTP 请求。 WebClient 还需要 HTTP 客户端库才能正常工作。 Spring 为其中一些提供了内置支持,但默认使用Reactor Netty

大多数配置(包括超时)都可以使用这些客户端来完成。

3. 通过 HTTP 客户端配置超时

正如我们之前提到的,在应用程序中设置不同 WebClient 超时的最简单方法是 使用底层 HTTP 客户端全局设置它们。 这也是最有效的方法。

由于 Netty 是 Spring WebFlux 的默认客户端库,因此我们将使用Reactor Netty HttpClient来介绍我们的示例。

3.1.响应超时

响应超时是我们 发送请求后等待接收响应的时间。 我们可以使用 responseTimeout() 方法为客户端配置它:

HttpClient client = HttpClient.create()
  .responseTimeout(Duration.ofSeconds(1)); 

在本例中,我们将超时配置为 1 秒。 Netty默认不设置响应超时。

之后,我们可以将 HttpClient 提供给 Spring WebClient

WebClient webClient = WebClient.builder()
  .clientConnector(new ReactorClientHttpConnector(httpClient))
  .build();

完成此操作后, WebClient 会继承底层 HttpClient 为所有发送的请求提供的 所有配置

3.2.连接超时

连接超时是 客户端和服务器之间必须建立连接的时间段 ** 我们可以使用不同的通道选项键和option()方法来执行配置:

HttpClient client = HttpClient.create()
  .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000);

// create WebClient...

提供的值以毫秒为单位,因此我们将超时配置为 10 秒。 Netty 默认将该值设置为 30 秒。

此外,我们可以配置 keep-alive 选项,该选项将在连接空闲时发送 TCP 检查探测:

HttpClient client = HttpClient.create()
  .option(ChannelOption.SO_KEEPALIVE, true)
  .option(EpollChannelOption.TCP_KEEPIDLE, 300)
  .option(EpollChannelOption.TCP_KEEPINTVL, 60)
  .option(EpollChannelOption.TCP_KEEPCNT, 8);

// create WebClient...

因此,我们启用了保持活动检查,以在闲置 5 分钟后以 60 秒的间隔进行探测。我们还将连接下降之前的最大探测数量设置为 8。

当连接未在给定时间内建立或断开时,将引发 ConnectTimeoutException

3.3.读写超时

读超时是指 在一定时间内没有读取到数据, 而写超时是 指写操作在特定时间无法完成。 HttpClient 允许配置额外的处理程序来配置这些超时:

HttpClient client = HttpClient.create()
  .doOnConnected(conn -> conn
    .addHandler(new ReadTimeoutHandler(10, TimeUnit.SECONDS))
    .addHandler(new WriteTimeoutHandler(10)));

// create WebClient...

在这种情况下,我们通过 doOnConnected() 方法配置了连接回调,并在其中创建了额外的处理程序。为了配置超时,我们添加了 ReadTimeOutHandlerWriteTimeOutHandle r实例。我们将它们都设置为 10 秒。

这些处理程序的构造函数接受两种参数变体。对于第一个,我们提供了一个带有 TimeUnit 规范的数字,而第二个将给定的数字转换为秒。

底层 Netty 库 相应地提供了 ReadTimeoutExceptionWriteTimeoutException 类来处理错误

3.4. SSL/TLS 超时

握手超时是 系统在停止操作之前尝试建立 SSL 连接的持续时间 。我们可以通过 secure() 方法设置 SSL 配置:

HttpClient.create()
  .secure(spec -> spec.sslContext(SslContextBuilder.forClient())
    .defaultConfiguration(SslProvider.DefaultConfigurationType.TCP)
    .handshakeTimeout(Duration.ofSeconds(30))
    .closeNotifyFlushTimeout(Duration.ofSeconds(10))
    .closeNotifyReadTimeout(Duration.ofSeconds(10)));

// create WebClient...

如上所述,我们将握手超时设置为 30 秒(默认:10 秒),而 close_notify 刷新(默认:3 秒)和读取(默认:0 秒)超时设置为 10 秒。所有方法均由 SslProvider.Builder 接口传递。

当握手由于配置的超时而失败时,将使用 SslHandshakeTimeoutException

3.5.代理超时

HttpClient 还支持代理功能。如果 与对等方的连接建立尝试未在代理超时内完成,则连接尝试失败 。我们在 proxy() 配置期间设置此超时:

HttpClient.create()
  .proxy(spec -> spec.type(ProxyProvider.Proxy.HTTP)
    .host("proxy")
    .port(8080)
    .connectTimeoutMillis(30000));

// create WebClient...

当默认值为 10 时,我们使用 connectTimeoutMillis() 将超时设置为 30 秒。

Netty 库还实现了 自己的 ProxyConnectException 以防出现任何失败

4. 请求级超时

在上一节中,我们使用 HttpClient 全局配置了不同的超时。但是,我们还可以 独立于全局设置来设置响应请求特定的超时

4.1.响应超时 – 使用 HttpClientRequest

与之前一样,我们 也可以在请求级别配置响应超时

webClient.get()
  .uri("https://baeldung.com/path")
  .httpRequest(httpRequest -> {
    HttpClientRequest reactorRequest = httpRequest.getNativeRequest();
    reactorRequest.responseTimeout(Duration.ofSeconds(2));
  });

在上面的例子中,我们使用 WebClient 的 httpRequest() 方法从底层 Netty 库访问本 HttpClientRequest 。接下来,我们用它来将超时值设置为 2 秒。

这种响应超时设置 会覆盖 HttpClient 级别上的任何响应超时 。我们还可以将此值设置为 null 以删除任何先前配置的值。

4.2.反应超时 – 使用 Reactor Core

Reactor Netty 使用 Reactor Core 作为其 Reactive Streams 实现。要配置另一个超时,我们可以使用 Mono 和 Flux 发布者 提供timeout() 运算符

webClient.get()
  .uri("https://baeldung.com/path")
  .retrieve()
  .bodyToFlux(JsonNode.class)
  .timeout(Duration.ofSeconds(5));

在这种情况下, 如果在给定的 5 秒内没有任何项目到达,则会出现 TimeoutException

请记住,最好使用 Reactor Netty 中提供的更具体的超时配置选项,因为它们为特定目的和用例提供了更多控制。

timeout() 方法适用于整个操作, 从建立到远程对等点的连接到接收 响应。它 不会覆盖 任何 HttpClient 相关设置。

5. 异常处理

我们刚刚了解了不同的超时配置。现在是时候快速讨论一下异常处理了。每种类型的超时都会提供一个专用的异常,因此我们可以 使用 Ractive Streams 和 onError 块轻松处理它们

webClient.get()
  .uri("https://baeldung.com/path")
  .retrieve()
  .bodyToFlux(JsonNode.class)
  .timeout(Duration.ofSeconds(5))
  .onErrorMap(ReadTimeoutException.class, ex -> new HttpTimeoutException("ReadTimeout"))
  .onErrorReturn(SslHandshakeTimeoutException.class, new TextNode("SslHandshakeTimeout"))
  .doOnError(WriteTimeoutException.class, ex -> log.error("WriteTimeout"))
  ...

我们可以重用之前描述的任何异常,并使用 Reactor 编写我们自己的处理方法。

此外,我们还可以 根据HTTP状态添加一些逻辑

webClient.get()
  .uri("https://baeldung.com/path")
  .onStatus(HttpStatus::is4xxClientError, resp -> {
    log.error("ClientError {}", resp.statusCode());
    return Mono.error(new RuntimeException("ClientError"));
  })
  .retrieve()
  .bodyToFlux(JsonNode.class)
  ...

六,结论

在本教程中,我们学习了 如何 使用 Netty 示例在 WebClient 上的 Spring WebFlux 中配置超时。

我们快速讨论了不同的超时以及在 HttpClient 级别正确设置它们的方法,以及如何将它们应用到我们的全局设置。然后,我们使用单个请求来配置特定于请求的级别的响应超时。最后,我们展示了处理发生的异常的不同方法。

本文中提到的所有代码片段都可以在 GitHub 上找到。