4

Currently my post and get requests are handled through WebClients which has a common connection and read timeout in Spring Boot. I have 5 different classes each requiring its own set of connection and read timeout. I don't want to create 5 different WebClients, rather use the same Webclient but while sending a post or a get request from a particular class, specify the required connection and read timeout. Is there any way to implement this?

My current WebClient:

    @Bean
    public WebClient getWebClient(WebClient.Builder builder){

        HttpClient httpClient = HttpClient.newConnection()
                .tcpConfiguration(tcpClient -> {
                    tcpClient = tcpClient.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, connectionTimeout*1000);
                    tcpClient = tcpClient.doOnConnected(conn -> conn
                            .addHandlerLast(new ReadTimeoutHandler(readTimeout, TimeUnit.SECONDS)));
                    return tcpClient;
                }).wiretap(true);

        ClientHttpConnector connector = new ReactorClientHttpConnector(httpClient);

        return builder.clientConnector(connector).build();
    }

A post request I'm using:

public WebClientResponse httpPost(String endpoint, String requestData, Map<String, Object> requestHeader) {

        ClientResponse res = webClient.post().uri(endpoint)
                .body(BodyInserters.fromObject(requestData))
                .headers(x -> {
                    if(requestHeader != null && !requestHeader.isEmpty()) {
                        for (String s : requestHeader.keySet()) {
                            x.set(s, String.valueOf(requestHeader.get(s)));
                        }
                    }
                })
                .exchange()
                .doOnSuccess(x -> log.info("response code = " + x.statusCode()))
                .block();

        return convertWebClientResponse(res);
    }
Aditya K
  • 135
  • 2
  • 4
  • 10
  • You can have timeout members in WebClient and its respective setter methods. Before sending each request, you can change the timeout values using setters and then call appropriate Get and Post requests. – kiner_shah Nov 16 '21 at 07:07

3 Answers3

4

You can configure request-level timeout in WebClient.

 webClient.get()
   .uri("https://baeldung.com/path")
   .httpRequest(httpRequest -> {
   HttpClientRequest reactorRequest = httpRequest.getNativeRequest();
   reactorRequest.responseTimeout(Duration.ofSeconds(2));
 });

Now what you can do is that based on the request you can add those values either from the properties file or you can hard code them.

Reference:- https://www.baeldung.com/spring-webflux-timeout

Manish Kumar
  • 595
  • 2
  • 15
  • so in Duration.ofSeconds() I pass the required timeout. But in my configuration I added tcpClient options for connection and read timeout, I'm still unsure how to add that using this – Aditya K Nov 16 '21 at 07:17
  • That is your default timeout. This will override them. – Manish Kumar Nov 16 '21 at 07:25
  • yes but I need tcp level timeouts. It's not the same thing right – Aditya K Nov 16 '21 at 07:27
  • it's the same. the request will open a TCP connection using the provided configuration. You can add some sort of strategy pattern as well to modify this. – Manish Kumar Nov 16 '21 at 07:32
0

You can try timeout with webClient like below,

webClient.post()
   .uri(..)
   .body(..)
   .retrieve()
   .
   .
   .
   .timeout(Duration.ofMillis(30);

30 is just example.

Amol Damodar
  • 359
  • 2
  • 4
  • 3
    This is a timeout executed by the Reactor stack, not by Spring WebClient. It has nothing to do with timeouts regarding HTTP requests. – Krzysztof Tomaszewski Dec 21 '22 at 08:56
  • 1
    Thanks for that clarification, @KrzysztofTomaszewski . The book [_Cloud Native Spring in Action_](https://www.manning.com/books/cloud-native-spring-in-action) (Manning) on page 281 make it look like this sort of thing sets up a timeout for the `GET` request itself. Looks like the book needs to be corrected/clarified. I wonder what Reactor does when the timeout is reached, though; does it cancel the underlying HTTP request somehow? – Garret Wilson Apr 22 '23 at 19:28
  • I'm curious about it too. – Krzysztof Tomaszewski Apr 24 '23 at 16:07
0
@Bean
public WebClient getWebClient() {
    HttpClient httpClient = HttpClient.create()
            .tcpConfiguration(client ->
                    client.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 4000)
                            .doOnConnected(conn -> conn
                                    .addHandlerLast(new ReadTimeoutHandler(4)) 
                                    .addHandlerLast(new WriteTimeoutHandler(4))));
    ClientHttpConnector connector = new ReactorClientHttpConnector(httpClient.wiretap(true));
    return WebClient.builder()
            .clientConnector(connector)
            .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) // if you need a default header
            .build();
}

or you can use what @Amol has suggested

matt_bj_ninja
  • 51
  • 1
  • 3
  • change ReadTimeoutHandler and WriteTimeoutHandler values doesn't change read\write timeouts from default value 10sec – Roberto Aug 09 '23 at 11:26