Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

set timeout in Spring WebFlux webclient

I'm using Spring Webflux WebClient to make a REST call from my Spring boot application. And every time getting a timeout in 30 seconds.

Here is some code I tried to set socket timeout in WebClient of Spring webfulx.

 - ReactorClientHttpConnector connector = new ReactorClientHttpConnector(options -> options
           .option(ChannelOption.SO_TIMEOUT, 600000).option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 600000));
 - ReactorClientHttpConnector connector = new ReactorClientHttpConnector(
           options -> options.afterChannelInit(chan -> {
                chan.pipeline().addLast(new ReadTimeoutHandler(600000));
            }));
 - ReactorClientHttpConnector connector1 = new ReactorClientHttpConnector(options -> options
            .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 600000).afterNettyContextInit(ctx -> {
                ctx.addHandlerLast(new ReadTimeoutHandler(600000, TimeUnit.MILLISECONDS));
            }));

And tried to add this above connector setting in “WebClient” by using “clientConnector” method.

And also tried to set timeout as below:

webClient.get().uri(builder -> builder.path("/result/{name}/sets")
                    .queryParam("q", "kind:RECORDS")
                    .queryParam("offset", offset)
                    .queryParam("limit", RECORD_COUNT_LIMIT)
                    .build(name))
            .header(HttpHeaders.AUTHORIZATION, accessToken)
            .exchange().timeout(Duration.ofMillis(600000))
            .flatMap(response -> handleResponse(response, name, offset));

None of the above options is working for me.

I'm using org.springframework.boot:spring-boot-gradle-plugin:2.0.0.M7 which interally have dependecy of org.springframework:spring-webflux:5.0.2.RELEASE.

Please suggest here and let me know if I'm doing anything wrong here.

like image 490
Abhishek Pawnikar Avatar asked Feb 26 '18 16:02

Abhishek Pawnikar


People also ask

What is the default timeout for WebClient?

java example code the WebClient build using the default builder without any specific configuration. Hence it falls back to the default connect and read timeout, which is 30 seconds each. Modern applications do not wait for 30 seconds for anything.

Is WebClient part of WebFlux?

WebClient is part of Spring 5's reactive web framework called Spring WebFlux. To use WebClient, you need to include the spring-webflux module in your project. Go to http://start.spring.io.


3 Answers

I've tried reproducing the issue and couldn't. Using reactor-netty 0.7.5.RELEASE.

I'm not sure about which timeout you're talking about.

Connection timeout can be configured with ChannelOption.CONNECT_TIMEOUT_MILLIS. I'm getting 10 seconds between the "connecting" log message and the actual error:

WebClient webClient = WebClient.builder()
    .clientConnector(new ReactorClientHttpConnector(options -> options
        .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000)))
    .build();

webClient.get().uri("http://10.0.0.1/resource").exchange()
    .doOnSubscribe(subscription -> logger.info("connecting"))
    .then()
    .doOnError(err -> logger.severe(err.getMessage()))
    .block();

If you're talking about read/write timeouts, then you can look at Netty's ReadTimeoutHandler and WriteTimeoutHandler.

A full example could look like this:

ReactorClientHttpConnector connector = new ReactorClientHttpConnector(options ->
        options.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10)
                .onChannelInit(channel -> {
                        channel.pipeline().addLast(new ReadTimeoutHandler(10))
                                .addLast(new WriteTimeoutHandler(10));
                return true;
        }).build());

As of Reactor Netty 0.8 and Spring Framework 5.1, the configuration now looks like this:

TcpClient tcpClient = TcpClient.create()
                 .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 1000)
                 .doOnConnected(connection ->
                         connection.addHandlerLast(new ReadTimeoutHandler(10))
                                   .addHandlerLast(new WriteTimeoutHandler(10)));
WebClient webClient = WebClient.builder()
    .clientConnector(new ReactorClientHttpConnector(HttpClient.from(tcpClient)))
    .build();

Maybe adding the following to your application.properties will provide more information on what's happening at the HTTP level:

logging.level.reactor.ipc.netty.channel.ContextHandler=debug
logging.level.reactor.ipc.netty.http.client.HttpClient=debug
like image 70
Brian Clozel Avatar answered Oct 07 '22 16:10

Brian Clozel


Since HttpClient.from(tcpClient) is now deprecated in the latest netty (v0.9.x and will be removed in v1.1.0). You can use responseTimeout() and ignore too many HTTP connection configurations which you see in other code and this implementation works with the old as well as the new one.

Create HttpClient

HttpClient httpClient = HttpClient.create().responseTimeout(Duration.ofMillis(500)); // 500 -> timeout in millis

Add httpClient to webclient using webClient builder fxn .clientConnector()

WebClient
.builder()
.baseUrl("http://myawesomeurl.com")
.clientConnector(new ReactorClientHttpConnector(httpClient))
.build();

Also, the majority of the implementation available over the website is not deprecated to make sure that you are not using the deprecated one you can check out this link.

FYI: For the older netty versions (i.e < v0.9.11 version) responseTimeout() uses tcpConfiguration() under the hood, which is deprecated in the new versions. But responseTimeout() uses the new implementation in >=v0.9.11, so even if you change the netty version of your project in the future your code is not going to break.

NOTE: If you are using the old netty version which comes by default with spring sometimes, you probably can use Brian's implementation too. (Not sure though)

If you want to read more about responseTimeout() and how does it work, you can check the source code here and here or the github gist here.

like image 38
im_bhatman Avatar answered Oct 07 '22 16:10

im_bhatman


Per HttpClient.from(TcpClient) method, as @im_bhatman mentioned, has been deprecated, here comes a way using the good-old way.

// works for Spring Boot 2.4.0, 2.4.1, and 2.4.2
// doesn't work for Spring Boot 2.3.6, 2.3.7, and 2.3.8
HttpClient httpClient = HttpClient.create()
        //.responseTimeout(Duration.ofSeconds(READ_TIMEOUT_SECONDS))
        .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, CONNECT_TIME_MILLIS)
        .doOnConnected(c -> {
                c.addHandlerLast(new ReadTimeoutHandler(READ_TIMEOUT_SECONDS))
                 .addHandlerLast(new WriteTimeoutHandler(WRITE_TIMEOUT_SECONDS));
        });
ClientConnector clientConnector = new ReactorClientHttpConnector(httpClient);
WebClient webClient = WebClient.builder()
        .clientConnector(clientConnector)
        ...
        .build();

I'm not sure about the difference of

  • HttpClient#responseTimeout(...)
  • HttpClient#doOnConnected(c -> c.addHandler(new ReadTimeoutHandler(...)))
like image 29
Jin Kwon Avatar answered Oct 07 '22 18:10

Jin Kwon