如何在 Spring WebClient 中设置和处理超时?
How to set and handle timeout in Spring WebClient?
Spring 文档说需要为 WebClient 手动配置 http 客户端以设置超时:https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html#webflux-client-builder-reactor-timeout。
但是由于 WebClient returns 响应式 Mono,可以(api 明智地)应用 .timeout
方法。
有同样的效果吗?
此外,当使用.timeout
方法时,Reactor的TimeoutException
是预期的。如果手动完成配置,流中是否会出现相同的错误,即 doOnError(TimeoutException.class, ...)
会工作吗?
我的发现
以 http 客户端特定方式设置超时将导致 http 客户端特定异常,即 WebClient
不包装异常:
@Test
void test() {
var host = "localhost";
var endpoint = "/test";
var port = 8089;
var timeout = Duration.ofSeconds(3);
WireMockServer wireMockServer = new WireMockServer(wireMockConfig().port(8089));
wireMockServer.start();
WireMock.configureFor(host, wireMockServer.port());
WireMock.stubFor(get(urlEqualTo(endpoint))
.willReturn(aResponse().withFixedDelay((int) timeout.toMillis())));
HttpClient httpClient = HttpClient.create()
.tcpConfiguration(client ->
client.doOnConnected(conn -> conn
.addHandlerLast(new ReadTimeoutHandler((int) (timeout.toSeconds() / 2)))
.addHandlerLast(new WriteTimeoutHandler((int) (timeout.toSeconds() / 2)))));
WebClient webClient = WebClient.builder()
.baseUrl(format("http://%s:%d", host, port))
.clientConnector(new ReactorClientHttpConnector(httpClient)).build();
webClient.get().uri(endpoint).retrieve().bodyToMono(Recommendation.class).block();
}
这将导致 io.netty.handler.timeout.ReadTimeoutException
。
.timeout(timeout.dividedBy(2)).block()
导致常规 TimeoutException
(java.util.concurrent
),但 Web 客户端是否会关心之后的连接(可能不会)仍然是一个悬而未决的问题。
我的解决方案是使用 http 客户端特定配置来确保使用连接的本机和正确方式,同时添加新的处理程序,将与 http 客户端相关的异常包装成更通用的异常(或 java.util.concurrent.TimeoutException
),以便 WebClient
客户将不依赖于提供者异常。
Spring 文档说需要为 WebClient 手动配置 http 客户端以设置超时:https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html#webflux-client-builder-reactor-timeout。
但是由于 WebClient returns 响应式 Mono,可以(api 明智地)应用 .timeout
方法。
有同样的效果吗?
此外,当使用.timeout
方法时,Reactor的TimeoutException
是预期的。如果手动完成配置,流中是否会出现相同的错误,即 doOnError(TimeoutException.class, ...)
会工作吗?
我的发现
以 http 客户端特定方式设置超时将导致 http 客户端特定异常,即 WebClient
不包装异常:
@Test
void test() {
var host = "localhost";
var endpoint = "/test";
var port = 8089;
var timeout = Duration.ofSeconds(3);
WireMockServer wireMockServer = new WireMockServer(wireMockConfig().port(8089));
wireMockServer.start();
WireMock.configureFor(host, wireMockServer.port());
WireMock.stubFor(get(urlEqualTo(endpoint))
.willReturn(aResponse().withFixedDelay((int) timeout.toMillis())));
HttpClient httpClient = HttpClient.create()
.tcpConfiguration(client ->
client.doOnConnected(conn -> conn
.addHandlerLast(new ReadTimeoutHandler((int) (timeout.toSeconds() / 2)))
.addHandlerLast(new WriteTimeoutHandler((int) (timeout.toSeconds() / 2)))));
WebClient webClient = WebClient.builder()
.baseUrl(format("http://%s:%d", host, port))
.clientConnector(new ReactorClientHttpConnector(httpClient)).build();
webClient.get().uri(endpoint).retrieve().bodyToMono(Recommendation.class).block();
}
这将导致 io.netty.handler.timeout.ReadTimeoutException
。
.timeout(timeout.dividedBy(2)).block()
导致常规 TimeoutException
(java.util.concurrent
),但 Web 客户端是否会关心之后的连接(可能不会)仍然是一个悬而未决的问题。
我的解决方案是使用 http 客户端特定配置来确保使用连接的本机和正确方式,同时添加新的处理程序,将与 http 客户端相关的异常包装成更通用的异常(或 java.util.concurrent.TimeoutException
),以便 WebClient
客户将不依赖于提供者异常。