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() 方法配置了连接回调,并在其中创建了额外的处理程序。为了配置超时,我们添加了 ReadTimeOutHandler 和 WriteTimeOutHandle r实例。我们将它们都设置为 10 秒。
这些处理程序的构造函数接受两种参数变体。对于第一个,我们提供了一个带有 TimeUnit 规范的数字,而第二个将给定的数字转换为秒。
底层 Netty 库 相应地提供了 ReadTimeoutException 和 WriteTimeoutException 类来处理错误 。
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 上找到。