14

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.

Malvon
  • 1,591
  • 3
  • 19
  • 42
Abhishek Pawnikar
  • 141
  • 1
  • 1
  • 4

4 Answers4

20

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
Brian Clozel
  • 56,583
  • 15
  • 167
  • 176
  • We are not able to set socket time (ChannelOption.SO_TIMEOUT, 600000) and not even using ReadTimeoutHandler. My REST request taking a long time more than 30 seconds to respond so I need to set socket timeout – Abhishek Pawnikar Mar 07 '18 at 09:41
  • my answer doesn't mention that option, does it? – Brian Clozel Mar 07 '18 at 09:42
  • The requestTimeout is indeed equal to the ChannelOption.SO_TIMEOUT. The connectTimeoutMillis is definitely about connection. – Abhishek Pawnikar Mar 07 '18 at 09:47
  • SO_TIMEOUT is for blocking IO in Netty, so it doesn't apply here. I've edited my answer with more information. – Brian Clozel Mar 07 '18 at 10:08
  • @BrianClozel Thank you for sharing the updated option to set read timeout and connect timeout applicable for Reactor Netty 0.8 and Spring Framework 5.1 – samaitra Jan 21 '19 at 22:06
  • @BrianClozel, how can I configure the Idle Timeout in reactor Netty? – Dina Bogdan Jul 03 '19 at 14:59
  • I believe you can add an `IdleStateHandler` instead of the `ReadTimeoutHandler` / `WriteTimeoutHandler`, because it's combining both approaches. – Brian Clozel Jul 05 '19 at 08:09
  • FYI: HttpClient.from() is now deprecated – im_bhatman Nov 05 '20 at 05:59
  • @BrianClozel Since this solution is deprecated what is the correct way of implementing it? I see a public final HttpClient responseTimeout(Duration timeout) method on HttpClient but not sure if this will suffice (At best I guess this is readtimeout but how to specicfy connecttimeout then ?) ? Thnx – simpleusr Nov 27 '20 at 11:57
  • @im_bhatman Since this solution is deprecated what is the correct way of implementing it? I see a public final HttpClient responseTimeout(Duration timeout) method on HttpClient but not sure if this will suffice (At best I guess this is readtimeout but how to specicfy connecttimeout then ?) ? Thnx – simpleusr Nov 27 '20 at 11:57
  • @simpleusr you can use simple HttpClient.create() and use direct .responseTimeout() on this to configure. This will work with the old as well as the new netty implementation. (implementation is given separately in the answer). – im_bhatman Nov 30 '20 at 16:41
7

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.

im_bhatman
  • 896
  • 1
  • 18
  • 28
1

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(...)))
Jin Kwon
  • 20,295
  • 14
  • 115
  • 184
0

Refer the below code block to set timeout and retry using webclient

.retrieve()
            .onStatus(
                   (HttpStatus::isError), // or the code that you want
                    (it -> handleError(it.statusCode().getReasonPhrase())) //handling error request
           )
            .bodyToMono(String.class)
            .timeout(Duration.ofSeconds(5))
            .retryWhen(
                    Retry.backoff(retryCount, Duration.ofSeconds(5))
                            .filter(throwable -> throwable instanceof TimeoutException)
           )