在Spring 5 Webflux WebClient中设置超时

2023/05/13

1. 概述

Spring 5添加了一个全新的框架Spring WebFlux,它支持我们的Web应用程序中的响应式编程。要执行HTTP请求,我们可以使用WebClient接口,该接口提供了基于Reactor的函数式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 连接超时

连接超时是必须在客户端和服务器之间建立连接的时间段,我们可以使用不同的ChannelOption key和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...

因此,我们启用了keep-alive检查,以便在空闲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()方法配置了一个连接的回调,我们在其中创建了额外的处理程序。为了配置超时,我们添加了ReadTimeOutHandlerWriteTimeOutHandler实例,我们将它们都设置为10秒。

这些处理程序的构造函数有两种重载。对于第一种,我们提供时间量和单位,而第二种将给定的数字转换为秒。

底层的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 flush(默认3秒)和read(默认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...

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

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

4. 请求级别超时

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

4.1 响应超时–使用HttpClientRequest

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

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

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

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

4.2 反应超时-使用Reactor

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

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

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

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

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

5. 异常处理

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

webClient.get()
    .uri("https://tuyucheng.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)
    ...

6. 总结

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

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

与往常一样,本教程的完整源代码可在GitHub上获得。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章