Set a Timeout in Spring 5 Webflux WebClient – 在Spring 5 Webflux WebClient中设置超时时间

最后修改: 2021年 4月 23日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

Spring 5 added a completely new framework – Spring WebFlux, which supports reactive programming in our web applications. To perform HTTP requests, we can use the WebClient interface, which provides a functional API based on the Reactor Project.

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

In this tutorial, we’ll focus on timeout settings for our WebClient. We’ll discuss different methods, how to set the different timeouts properly, both globally in the whole application and specific to a request.

在本教程中,我们将重点讨论WebClient的超时设置。我们将讨论不同的方法,如何正确地设置不同的超时,包括在整个应用程序中的全局超时和针对某个请求的超时。

2. WebClient and HTTP Clients

2.<网络插件和HTTP客户端

Before we move on, let’s make a quick recap. Spring WebFlux includes its own client, the WebClient class, to perform HTTP requests in a reactive way. The WebClient also requires an HTTP client library to work properly. Spring delivers built-in support for some of them, but the Reactor Netty is used by default.

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

Most of the configurations, including timeouts, can be done using those clients.

大部分的配置,包括超时,都可以用这些客户端来完成。

3. Configuring Timeouts via HTTP Client

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

As we mentioned previously, the easiest way to set different WebClient timeouts in our application is to set them globally using an underlying HTTP client. It’s also the most efficient way to do this.

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

As Netty is a default client library for the Spring WebFlux, we’ll cover our examples using the Reactor Netty HttpClient class.

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

3.1. Response Timeout

3.1. 响应超时

The response timeout is the time we wait to receive a response after sending a request. We can use the responseTimeout() method to configure it for the client:

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

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

In this example, we configure the timeout for 1 second. Netty doesn’t set the response timeout by default.

在这个例子中,我们把超时时间配置为1秒。Netty默认不会设置响应超时。

After that, we can supply the HttpClient to the Spring WebClient:

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

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

After doing that, the WebClient inherits all the configurations provided by the underlying HttpClient for all requests sent.

这样做之后,WebClient 继承底层HttpClient提供的所有配置,用于发送所有请求。

3.2. Connection Timeout

3.2.连接超时

The connection timeout is a period within which a connection between a client and a server must be establishedWe can use different channel options keys and the option() method to perform the configuration:

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

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

// create WebClient...

The value provided is in milliseconds, so we configured the timeout for 10 seconds. Netty sets that value to 30 seconds by default.

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

Moreover, we can configure the keep-alive option, which will send TCP check probes when the connection is idle:

此外,我们可以配置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...

So, we’ve enabled keep-alive checks to probe after 5 minutes of being idle, at 60 seconds intervals. We also set the maximum number of probes before the connection dropping to 8.

因此,我们在空闲5分钟后启用了保持在线检查,以60秒的间隔进行探测。我们还将连接中断前的最大探测次数设置为8次。

When the connection is not established in a given time or dropped, a ConnectTimeoutException is thrown.

当连接在给定的时间内没有建立或被放弃时,会抛出一个ConnectTimeoutException

3.3. Read and Write Timeout

3.3.读写超时

A read timeout occurs when no data was read within a certain period of time, while the write timeout when a write operation cannot finish at a specific time. The HttpClient allows to configure additional handlers to configure those timeouts:

在一定时间内没有读取数据时,就会出现读超时,而当在特定时间内不能完成写操作时,就会出现写超时。HttpClient允许配置额外的处理程序来配置这些超时。

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

// create WebClient...

In this situation, we configured a connected callback via the doOnConnected() method, where we created additional handlers. To configure timeouts we added ReadTimeOutHandler and WriteTimeOutHandler instances. We set both of them to 10 seconds.

在这种情况下,我们通过doOnConnected()方法配置了一个连接的回调,在这里我们创建了额外的处理程序。为了配置超时,我们添加了ReadTimeOutHandlerWriteTimeOutHandler>实例。我们把它们都设置为10秒。

The constructors for these handlers accept two variants of parameters. For the first one, we provided a number with the TimeUnit specification, while the second converts the given number to seconds.

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

The underlying Netty library delivers ReadTimeoutException and WriteTimeoutException classes accordingly to handle errors.

底层的Netty库提供了ReadTimeoutExceptionWriteTimeoutException类来处理错误

3.4. SSL/TLS Timeout

3.4.SSL/TLS超时

The handshake timeout is the duration in time that the system tries to establish an SSL connection before halting the operation. We can set the SSL configuration via the secure() method:

握手超时是系统在停止操作前尝试建立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...

As above, we set a handshake timeout to 30 seconds (default: 10s), while close_notify flush (default: 3s) and read (default: 0s) timeouts to 10 seconds. All methods are delivered by the SslProvider.Builder interface.

如上所述,我们将握手超时设置为30秒(默认:10s),而close_notify冲洗(默认:3s)和读取(默认:0s)超时为10秒。所有方法都由SslProvider.Builder接口提供。

The SslHandshakeTimeoutException is used when a handshake failed due to a configured timeout.

SslHandshakeTimeoutException当握手因配置的超时而失败时使用

3.5. Proxy Timeout

3.5.代理人超时

HttpClient also supports proxy functionality. If the connection establishment attempt to the peer does not finish within the proxy timeout, the connection attempt fails. We set this timeout during the proxy() configuration:

HttpClient也支持代理功能。如果与对等体的连接建立尝试没有在代理超时内完成,则连接尝试失败。我们在proxy()配置中设置了这个超时。

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

// create WebClient...

We used connectTimeoutMillis() to set the timeout to 30 seconds when the default value is 10.

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

The Netty library also implements its own ProxyConnectException in case of any fails.

Netty库也实现了它的自己的ProxyConnectException,以应对任何失败

4. Request-Level Timeouts

4.请求级别的超时

In the previous section, we configured different timeouts globally using HttpClient. However, we can also set the response request-specific timeouts independently of the global settings.

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

4.1. Response Timeout – Using HttpClientRequest

4.1.响应超时 – 使用HttpClientRequest

As previously, we can configure the response timeout also at the request level:

如前所述,我们也可以在请求层面配置响应超时

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

In the case above, we used the WebClient’s httpRequest() method to get access to the native HttpClientRequest from the underlying Netty library. Next, we used it to set the timeout value to 2 seconds.

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

This kind of response timeout setting overrides any response timeout on the HttpClient level. We can also set this value to null to remove any previously configured value.

这种响应超时设置覆盖了HttpClient级别上的任何响应超时。我们也可以把这个值设置为null,以删除以前配置的任何值。

4.2. Reactive Timeout – Using Reactor Core

4.2.反应性超时–使用反应器核心

Reactor Netty uses Reactor Core as its Reactive Streams implementation. To configure another timeout, we can use the timeout() operator provided by Mono and Flux publishers:

Reactor Netty使用Reactor Core作为其Reactive Streams实现。要配置另一个超时,我们可以使用 timeout() 操作器,该操作器由MonoFlux发布者提供。

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

In that situation, the TimeoutException will appear in case no item arrives within the given 5 seconds.

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

Keep in mind that it’s better to use the more specific timeout configuration options available in Reactor Netty since they provide more control for a specific purpose and use case.

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

The timeout() method applies to the whole operation, from establishing the connection to the remote peer to receiving the response. It doesn’t override any HttpClient related settings.

timeout()方法适用于整个操作,从建立与远程同行的连接到接收响应。它没有覆盖任何HttpClient相关设置。

5. Exception Handling

5.异常处理

We’ve just learned about different timeout configurations. Now it’s time to quickly talk about exception handling. Each type of timeout delivers a dedicated exception, so we can easily handle them using Ractive Streams and onError blocks:

我们刚刚了解了不同的超时配置。现在是时候快速谈论一下异常处理了。每种类型的超时都提供了一个专门的异常,所以我们可以轻松地使用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"))
  ...

We can reuse any previously described exceptions and write our own handling methods using Reactor.

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

Moreover, we can also add some logic according to the HTTP status:

此外,我们还可以根据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)
  ...

6. Conclusion

6.结语

In this tutorial, we learned how to configure timeouts in Spring WebFlux on our WebClient using Netty examples.

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

We quickly talked about different timeouts and the ways to set them correctly at the HttpClient level and also how to apply them to our global settings. Then, we worked with a single request to configure the response timeout at a request-specific level. Finally, we showed different methods to handle occurring exceptions.

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

All of the code snippets mentioned in the article can be found over on GitHub.

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