如何在 Spring 5 WebFlux WebClient 中设置超时
How to set a timeout in Spring 5 WebFlux WebClient
我正在尝试在我的 WebClient 上设置超时,这是当前代码:
SslContext sslContext = SslContextBuilder.forClient().trustManager(InsecureTrustManagerFactory.INSTANCE).build();
ClientHttpConnector httpConnector = new ReactorClientHttpConnector(opt -> {
opt.sslContext(sslContext);
HttpClientOptions option = HttpClientOptions.builder().build();
opt.from(option);
});
return WebClient.builder().clientConnector(httpConnector).defaultHeader("Authorization", xxxx)
.baseUrl(this.opusConfig.getBaseURL()).build();
我需要添加超时和池策略,我在想类似的事情:
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
cm.setMaxTotal(this.applicationConfig.getHttpClientMaxPoolSize());
cm.setDefaultMaxPerRoute(this.applicationConfig.getHttpClientMaxPoolSize());
cm.closeIdleConnections(this.applicationConfig.getServerIdleTimeout(), TimeUnit.MILLISECONDS);
RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(this.applicationConfig.getHttpClientSocketTimeout())
.setConnectTimeout(this.applicationConfig.getHttpClientConnectTimeout())
.setConnectionRequestTimeout(this.applicationConfig.getHttpClientRequestTimeout()).build();
CloseableHttpClient httpClient = HttpClients.custom().setDefaultRequestConfig(requestConfig).setConnectionManager(cm).build();
但我不知道如何在我的网络客户端中设置 httpClient
WebFlux WebClient
不使用 Apache Commons HTTP 客户端。尽管您可以通过自定义 ClientHttpConnector
实现一种解决方案。现有的ReactorClientHttpConnector
是基于Netty的。因此,考虑使用 Netty 选项来配置客户端,例如:
ReactorClientHttpConnector connector =
new ReactorClientHttpConnector(options ->
options.option(ChannelOption.SO_TIMEOUT, this.applicationConfig.getHttpClientConnectTimeout()));
或
.onChannelInit(channel -> channel.config().setConnectTimeoutMillis(this.applicationConfig.getHttpClientConnectTimeout()))
更新
我们也可以使用ReadTimeoutHandler
:
.onChannelInit(channel ->
channel.pipeline()
.addLast(new ReadTimeoutHandler(this.applicationConfig.getHttpClientConnectTimeout())))
这是我的做法(感谢@Artem)
SslContext sslContext = SslContextBuilder.forClient().trustManager(InsecureTrustManagerFactory.INSTANCE).build();
ClientHttpConnector httpConnector = new ReactorClientHttpConnector(options -> {
options.sslContext(sslContext);
options.option(ChannelOption.SO_TIMEOUT, this.applicationConfig.getHttpClientRequestTimeout());
options.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, this.applicationConfig.getHttpClientConnectTimeout());
options.poolResources(PoolResources.fixed("myPool", this.applicationConfig.getHttpClientMaxPoolSize()));
});
return WebClient.builder().clientConnector(httpConnector).defaultHeader("Authorization", "xxxx")
.baseUrl(this.config.getBaseURL()).build();
要设置读取和连接超时,我使用下面的方法,因为 SO_TIMEOUT 选项不适用于使用 NIO 的通道(并给出警告 Unknown channel option 'SO_TIMEOUT' for channel '[id: 0xa716fcb2]'
)
ReactorClientHttpConnector connector = new ReactorClientHttpConnector(
options -> options.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 2000)
.compression(true)
.afterNettyContextInit(ctx -> {
ctx.addHandlerLast(new ReadTimeoutHandler(5000, TimeUnit.MILLISECONDS));
}));
return WebClient.builder()
.clientConnector(connector)
.build();
ReactorClientHttpConnector API 版本已更改 Spring WebFlux 5.1。
所以我执行以下操作(Kotlin 语法,基于@joshiste 示例):
val tcpClient = TcpClient.create()
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10_000)
.doOnConnected { connection ->
connection.addHandlerLast(ReadTimeoutHandler(10))
.addHandlerLast(WriteTimeoutHandler(10))
}
val myWebClient = webClientBuilder
.clientConnector(ReactorClientHttpConnector(HttpClient.from(tcpClient)))
.baseUrl(myEndPoint)
.build()
2021 年更新
HttpClient.from 在 Reactive Netty 的最新版本中已弃用。它正在复制 tcpClient 的配置。现在我们可以直接配置httpClient了。
val httpClient = HttpClient.create()
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10_000)
.doOnConnected { connection ->
connection.addHandlerLast(ReadTimeoutHandler(10))
.addHandlerLast(WriteTimeoutHandler(10))
}
val myWebClient = webClientBuilder
.clientConnector(ReactorClientHttpConnector(httpClient))
.baseUrl(myEndPoint)
.build()
随着 Spring Webflux 的更新,这里有一个适用于 Java 的解决方案(基于 Kotlin 的 ):
TcpClient timeoutClient = TcpClient.create()
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, SECONDS*1000)
.doOnConnected(
c -> c.addHandlerLast(new ReadTimeoutHandler(SECONDS))
.addHandlerLast(new WriteTimeoutHandler(SECONDS)));
return webClientBuilder.baseUrl(YOUR_URL)
.clientConnector(new ReactorClientHttpConnector(HttpClient.from(timeoutClient)))
.build();
2021 年更新
由于 HttpClient.from(TcpClient)
已弃用,现在更简单了:
return WebClient.builder()
.baseUrl(YOUR_URL)
.clientConnector(new ReactorClientHttpConnector(HttpClient.create()
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, SECONDS * 1000)
.doOnConnected(c -> c.addHandlerLast(new ReadTimeoutHandler(SECONDS))
.addHandlerLast(new WriteTimeoutHandler(SECONDS)))))
.build();
根据上面的评论,如果你想添加一个套接字超时,只需将它作为另一个选项添加到同一个 timeoutClient 中。
TcpClient timeoutClient = TcpClient.create()
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, SECONDS*10) //Connect Timeout
.option(ChannelOption.SO_TIMEOUT,1000) // Socket Timeout
.doOnConnected(
c -> c.addHandlerLast(new ReadTimeoutHandler(SECONDS))
.addHandlerLast(new WriteTimeoutHandler(SECONDS)));
return webClientBuilder.baseUrl(YOUR_URL)
.clientConnector(new ReactorClientHttpConnector(HttpClient.from(timeoutClient)))
.build();
使用 Spring Webflux 5.1.8 我 运行 在执行多个使用 WebClient
的后续测试时使用 的答案产生以下错误消息的问题.
Force-closing a channel whose registration task was not accepted by an
event loop
Failed to submit a listener notification task. Event loop shut down?
添加连接提供程序和循环资源解决了我的问题:
final ConnectionProvider theTcpClientPool = ConnectionProvider.elastic("tcp-client-pool");
final LoopResources theTcpClientLoopResources = LoopResources.create("tcp-client-loop");
final TcpClient theTcpClient = TcpClient
.create(theTcpClientPool)
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
.runOn(theTcpClientLoopResources)
.doOnConnected(theConnection -> {
theConnection.addHandlerLast(new ReadTimeoutHandler(mTimeoutInMillisec, TimeUnit.MILLISECONDS));
theConnection.addHandlerLast(new WriteTimeoutHandler(mTimeoutInMillisec, TimeUnit.MILLISECONDS));
});
WebClient theWebClient = WebClient.builder()
.baseUrl(mVfwsServerBaseUrl)
.clientConnector(new ReactorClientHttpConnector(HttpClient.from(theTcpClient)))
.build();
与其创建您自己的 WebClient.Builder
,不如提供自定义 ReactorNettyHttpClientMapper
,它将应用于默认 WebClient.Builder
:
@Configuration
class MyAppConfiguration {
@Bean
fun reactorNettyHttpClientMapper(): ReactorNettyHttpClientMapper {
return ReactorNettyHttpClientMapper { httpClient ->
httpClient.tcpConfiguration { tcpClient ->
tcpClient.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 30_000)
.doOnConnected { connection ->
connection.addHandlerLast(ReadTimeoutHandler(60))
.addHandlerLast(WriteTimeoutHandler(60))
}
}
}
}
}
您可以使用接受 Mono 对象超时的重载 block() 方法。
或者 Mono 对象上有直接可用的 timeout() 方法。
WebClient webClient = WebClient.builder()
.baseUrl( "https://test.com" )
.defaultHeader( HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE )
.build();
webClient.post()
.uri( "/services/some/uri" )
.body( Mono.just( YourEntityClassObject ), YourEntityClass.class )
.retrieve()
.bodyToMono( String.class )
.timeout(Duration.ofMillis( 5000 )) // option 1
.block(Duration.ofMillis( 5000 )); // option 2
Kotlin 语法!!
webClient
.post()
.body(BodyInserters.fromObject(body))
.headers(headersSetter)
.retrieve()
.bodyToMono<SomeClass>()
.timeout(Duration.ofSeconds(30)) /// specify timeout duration
.doOnNext {
logger.info{ "log something"}
}
.onErrorMap { throwable ->
logger.error{"do some transformation"}
throwable
}
我正在尝试在我的 WebClient 上设置超时,这是当前代码:
SslContext sslContext = SslContextBuilder.forClient().trustManager(InsecureTrustManagerFactory.INSTANCE).build();
ClientHttpConnector httpConnector = new ReactorClientHttpConnector(opt -> {
opt.sslContext(sslContext);
HttpClientOptions option = HttpClientOptions.builder().build();
opt.from(option);
});
return WebClient.builder().clientConnector(httpConnector).defaultHeader("Authorization", xxxx)
.baseUrl(this.opusConfig.getBaseURL()).build();
我需要添加超时和池策略,我在想类似的事情:
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
cm.setMaxTotal(this.applicationConfig.getHttpClientMaxPoolSize());
cm.setDefaultMaxPerRoute(this.applicationConfig.getHttpClientMaxPoolSize());
cm.closeIdleConnections(this.applicationConfig.getServerIdleTimeout(), TimeUnit.MILLISECONDS);
RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(this.applicationConfig.getHttpClientSocketTimeout())
.setConnectTimeout(this.applicationConfig.getHttpClientConnectTimeout())
.setConnectionRequestTimeout(this.applicationConfig.getHttpClientRequestTimeout()).build();
CloseableHttpClient httpClient = HttpClients.custom().setDefaultRequestConfig(requestConfig).setConnectionManager(cm).build();
但我不知道如何在我的网络客户端中设置 httpClient
WebFlux WebClient
不使用 Apache Commons HTTP 客户端。尽管您可以通过自定义 ClientHttpConnector
实现一种解决方案。现有的ReactorClientHttpConnector
是基于Netty的。因此,考虑使用 Netty 选项来配置客户端,例如:
ReactorClientHttpConnector connector =
new ReactorClientHttpConnector(options ->
options.option(ChannelOption.SO_TIMEOUT, this.applicationConfig.getHttpClientConnectTimeout()));
或
.onChannelInit(channel -> channel.config().setConnectTimeoutMillis(this.applicationConfig.getHttpClientConnectTimeout()))
更新
我们也可以使用ReadTimeoutHandler
:
.onChannelInit(channel ->
channel.pipeline()
.addLast(new ReadTimeoutHandler(this.applicationConfig.getHttpClientConnectTimeout())))
这是我的做法(感谢@Artem)
SslContext sslContext = SslContextBuilder.forClient().trustManager(InsecureTrustManagerFactory.INSTANCE).build();
ClientHttpConnector httpConnector = new ReactorClientHttpConnector(options -> {
options.sslContext(sslContext);
options.option(ChannelOption.SO_TIMEOUT, this.applicationConfig.getHttpClientRequestTimeout());
options.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, this.applicationConfig.getHttpClientConnectTimeout());
options.poolResources(PoolResources.fixed("myPool", this.applicationConfig.getHttpClientMaxPoolSize()));
});
return WebClient.builder().clientConnector(httpConnector).defaultHeader("Authorization", "xxxx")
.baseUrl(this.config.getBaseURL()).build();
要设置读取和连接超时,我使用下面的方法,因为 SO_TIMEOUT 选项不适用于使用 NIO 的通道(并给出警告 Unknown channel option 'SO_TIMEOUT' for channel '[id: 0xa716fcb2]'
)
ReactorClientHttpConnector connector = new ReactorClientHttpConnector(
options -> options.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 2000)
.compression(true)
.afterNettyContextInit(ctx -> {
ctx.addHandlerLast(new ReadTimeoutHandler(5000, TimeUnit.MILLISECONDS));
}));
return WebClient.builder()
.clientConnector(connector)
.build();
ReactorClientHttpConnector API 版本已更改 Spring WebFlux 5.1。
所以我执行以下操作(Kotlin 语法,基于@joshiste 示例):
val tcpClient = TcpClient.create()
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10_000)
.doOnConnected { connection ->
connection.addHandlerLast(ReadTimeoutHandler(10))
.addHandlerLast(WriteTimeoutHandler(10))
}
val myWebClient = webClientBuilder
.clientConnector(ReactorClientHttpConnector(HttpClient.from(tcpClient)))
.baseUrl(myEndPoint)
.build()
2021 年更新
HttpClient.from 在 Reactive Netty 的最新版本中已弃用。它正在复制 tcpClient 的配置。现在我们可以直接配置httpClient了。
val httpClient = HttpClient.create()
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10_000)
.doOnConnected { connection ->
connection.addHandlerLast(ReadTimeoutHandler(10))
.addHandlerLast(WriteTimeoutHandler(10))
}
val myWebClient = webClientBuilder
.clientConnector(ReactorClientHttpConnector(httpClient))
.baseUrl(myEndPoint)
.build()
随着 Spring Webflux 的更新,这里有一个适用于 Java 的解决方案(基于 Kotlin 的
TcpClient timeoutClient = TcpClient.create()
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, SECONDS*1000)
.doOnConnected(
c -> c.addHandlerLast(new ReadTimeoutHandler(SECONDS))
.addHandlerLast(new WriteTimeoutHandler(SECONDS)));
return webClientBuilder.baseUrl(YOUR_URL)
.clientConnector(new ReactorClientHttpConnector(HttpClient.from(timeoutClient)))
.build();
2021 年更新
由于 HttpClient.from(TcpClient)
已弃用,现在更简单了:
return WebClient.builder()
.baseUrl(YOUR_URL)
.clientConnector(new ReactorClientHttpConnector(HttpClient.create()
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, SECONDS * 1000)
.doOnConnected(c -> c.addHandlerLast(new ReadTimeoutHandler(SECONDS))
.addHandlerLast(new WriteTimeoutHandler(SECONDS)))))
.build();
根据上面的评论,如果你想添加一个套接字超时,只需将它作为另一个选项添加到同一个 timeoutClient 中。
TcpClient timeoutClient = TcpClient.create()
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, SECONDS*10) //Connect Timeout
.option(ChannelOption.SO_TIMEOUT,1000) // Socket Timeout
.doOnConnected(
c -> c.addHandlerLast(new ReadTimeoutHandler(SECONDS))
.addHandlerLast(new WriteTimeoutHandler(SECONDS)));
return webClientBuilder.baseUrl(YOUR_URL)
.clientConnector(new ReactorClientHttpConnector(HttpClient.from(timeoutClient)))
.build();
使用 Spring Webflux 5.1.8 我 运行 在执行多个使用 WebClient
的后续测试时使用
Force-closing a channel whose registration task was not accepted by an event loop
Failed to submit a listener notification task. Event loop shut down?
添加连接提供程序和循环资源解决了我的问题:
final ConnectionProvider theTcpClientPool = ConnectionProvider.elastic("tcp-client-pool");
final LoopResources theTcpClientLoopResources = LoopResources.create("tcp-client-loop");
final TcpClient theTcpClient = TcpClient
.create(theTcpClientPool)
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
.runOn(theTcpClientLoopResources)
.doOnConnected(theConnection -> {
theConnection.addHandlerLast(new ReadTimeoutHandler(mTimeoutInMillisec, TimeUnit.MILLISECONDS));
theConnection.addHandlerLast(new WriteTimeoutHandler(mTimeoutInMillisec, TimeUnit.MILLISECONDS));
});
WebClient theWebClient = WebClient.builder()
.baseUrl(mVfwsServerBaseUrl)
.clientConnector(new ReactorClientHttpConnector(HttpClient.from(theTcpClient)))
.build();
与其创建您自己的 WebClient.Builder
,不如提供自定义 ReactorNettyHttpClientMapper
,它将应用于默认 WebClient.Builder
:
@Configuration
class MyAppConfiguration {
@Bean
fun reactorNettyHttpClientMapper(): ReactorNettyHttpClientMapper {
return ReactorNettyHttpClientMapper { httpClient ->
httpClient.tcpConfiguration { tcpClient ->
tcpClient.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 30_000)
.doOnConnected { connection ->
connection.addHandlerLast(ReadTimeoutHandler(60))
.addHandlerLast(WriteTimeoutHandler(60))
}
}
}
}
}
您可以使用接受 Mono 对象超时的重载 block() 方法。 或者 Mono 对象上有直接可用的 timeout() 方法。
WebClient webClient = WebClient.builder()
.baseUrl( "https://test.com" )
.defaultHeader( HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE )
.build();
webClient.post()
.uri( "/services/some/uri" )
.body( Mono.just( YourEntityClassObject ), YourEntityClass.class )
.retrieve()
.bodyToMono( String.class )
.timeout(Duration.ofMillis( 5000 )) // option 1
.block(Duration.ofMillis( 5000 )); // option 2
Kotlin 语法!!
webClient
.post()
.body(BodyInserters.fromObject(body))
.headers(headersSetter)
.retrieve()
.bodyToMono<SomeClass>()
.timeout(Duration.ofSeconds(30)) /// specify timeout duration
.doOnNext {
logger.info{ "log something"}
}
.onErrorMap { throwable ->
logger.error{"do some transformation"}
throwable
}