如何在Spring5WebFlux WebClient中设置超时

如何在Spring5WebFlux WebClient中设置超时,spring,reactor,spring-webflux,reactor-netty,Spring,Reactor,Spring Webflux,Reactor Netty,我正在尝试在我的WebClient上设置超时,以下是当前代码: SslContext sslContext = SslContextBuilder.forClient().trustManager(InsecureTrustManagerFactory.INSTANCE).build(); ClientHttpConnector httpConnector = new ReactorClientHttpConnector(opt -> { opt.sslContext(sslCo

我正在尝试在我的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();

但是我不知道如何在我的webclient中设置httpClient

WebFlux
webclient
不使用Apache Commons HTTP客户端。尽管您可以通过自定义
ClientHttpConnector
实现一个解决方案。现有的
ReactorClientHttpConnector
基于Netty。因此,考虑使用NETY选项来配置客户端,例如:

ReactorClientHttpConnector connector =
            new ReactorClientHttpConnector(options ->
                    options.option(ChannelOption.SO_TIMEOUT, this.applicationConfig.getHttpClientConnectTimeout()));

更新

我们还可以使用
ReadTimeoutHandler

.onChannelInit(channel -> 
        channel.pipeline()
           .addLast(new ReadTimeoutHandler(this.applicationConfig.getHttpClientConnectTimeout())))
以下是我是如何做到的(感谢@Artem)


要设置读取和连接超时,我使用下面的方法,因为使用NIO的通道无法使用SO_超时选项(并为通道“[id:0xa716fcb2]”提供警告
未知通道选项“SO_超时”。


ReactorClientHttpConnector API在版本中已更改

因此,我执行以下操作(Kotlin语法,基于@joshiste示例):

更新2021

HttpClient.from在上一版本的被动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()

随着SpringWebFlux的更新,下面是一个适用于Java的解决方案(基于for Kotlin的):

更新2021

由于
HttpClient.from(TcpClient)
现在已不受欢迎,因此更容易:

返回WebClient.builder()
.baseUrl(您的_URL)
.clientConnector(新的ReactorClientHttpConnector(HttpClient.create())
.option(ChannelOption.CONNECT\u超时\u毫秒,秒*1000)
.doOnConnected(c->c.addHandlerLast(新的ReadTimeoutHandler(秒))
.addHandlerLast(新的WriteTimeoutHandler(秒щщ)))
.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();

在SpringWebFlux 5.1.8中,我遇到了一些问题,在执行多个后续测试(使用
WebClient

)时,使用的答案产生了下面的错误消息

强制关闭其注册任务未被用户接受的频道 事件循环
未能提交侦听器通知任务。事件循环关闭

添加连接提供程序和循环资源解决了我的问题:

final ConnectionProvider theTcpClientPool=ConnectionProvider.elastic(“tcp客户端池”);
final LoopResources theTcpClientLoopResources=LoopResources.create(“tcp客户端循环”);
最终TCP客户端theTcpClient=TCP客户端
.创建(客户端池)
.option(ChannelOption.CONNECT\u TIMEOUT\u MILLIS,5000)
.runOn(客户端循环资源)
.doOnConnected(连接->{
connection.addHandlerLast(新的ReadTimeoutHandler(mTimeoutInMillisec,TIMEUNT.毫秒));
addHandlerLast(新的WriteTimeoutHandler(mtimeoutinMillissec,TimeUnit.millists));
});
WebClient theWebClient=WebClient.builder()
.baseUrl(mVfwsServerBaseUrl)
.clientConnector(新的ReactorClientHttpConnector(HttpClient.from(theTcpClient)))
.build();

您可以提供一个自定义的
ReactorNettyHttpClientMapper
,而不是创建自己的
WebClient.Builder
,它将应用于默认的
WebClient.Builder

.onChannelInit(channel -> 
        channel.pipeline()
           .addLast(new ReadTimeoutHandler(this.applicationConfig.getHttpClientConnectTimeout())))
@配置
类MyAppConfiguration{
@豆子
有趣的reactorNettyHttpClientMapper():reactorNettyHttpClientMapper{
返回reactornettyhtpclientmapper{httpClient->
httpClient.tcpConfiguration{tcpClient->
tcpClient.option(ChannelOption.CONNECT\u TIMEOUT\u MILLIS,30\u 000)
.doOnConnected{连接->
connection.addHandlerLast(ReadTimeoutHandler(60))
.addHandlerLast(WriteTimeoutHandler(60))
}
}
}
}
}

您可以使用重载block()方法,该方法接受Mono对象上的超时。 或者有一个timeout()方法可直接用于Mono对象

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
.post()
.body(BodyInserters.fromObject(body))
.标题(标题设置器)
.retrieve()
.bodyToMono()
.timeout(持续时间.of秒(30))///指定超时持续时间
doOnNext先生{
logger.info{“记录某物”}
}
.onErrorMap{可丢弃->
logger.error{“进行一些转换”}
丢弃的
}

这似乎就是我要找的,只是一个简单的问题是连接超时还是请求超时。知道如何设置连接池大小吗?感谢您的帮助,
reactor.ipc.netty.options.ClientOptions.Builder中有
poolResources()
requestTimeout
确实等于
ChannelOption。因此_TIMEOUT
connectTimeoutMillis
绝对是关于连接的。是的,我看到了poolResources(),我不得不承认我不知道如何使用它:/任何想法?我尝试了:options.poolResources(poolResources.fixed(“myPool”,this.applicationConfig.getHttpClientMaxPoolSize());这是正确的方法吗?api似乎已经改变了,下面是它现在的工作方式。这应该是正确的答案。c、 太好了!但是WebFlux 5.1中的ReactorClientHttpConnector已更改。这不适用于Spring 5.1,ReactorClientHttpConnector不允许再设置选项!非常有用,但是
.option(ChannelOption.CONNECT\u TIMEOUT\u MILLIS,SECONDS*10)
我认为10应该是1000。使用您的解决方案时,我面临
HttpClient类型的(TcpClient)方法不推荐使用
。看见
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();
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();
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
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
}