Spring docs says it is required to configure http client for WebClient manually to set timeouts: https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html#webflux-client-builder-reactor-timeout.
But since WebClient returns reactive Mono, it's possible (api-wise) to apply .timeout method.
Does it have the same effect?
Moreover, when one uses .timeout method, Reactor's TimeoutException is expected. Will the same error appear in the stream if configuration is done manually i.e. will doOnError(TimeoutException.class, ...) work?
My findings
Setting a timeout in a http client specific way will lead to http client specific exception i.e. WebClient doesn't wrap exceptions:
#Test
void test() {
var host = "localhost";
var endpoint = "/test";
var port = 8089;
var timeout = Duration.ofSeconds(3);
WireMockServer wireMockServer = new WireMockServer(wireMockConfig().port(8089));
wireMockServer.start();
WireMock.configureFor(host, wireMockServer.port());
WireMock.stubFor(get(urlEqualTo(endpoint))
.willReturn(aResponse().withFixedDelay((int) timeout.toMillis())));
HttpClient httpClient = HttpClient.create()
.tcpConfiguration(client ->
client.doOnConnected(conn -> conn
.addHandlerLast(new ReadTimeoutHandler((int) (timeout.toSeconds() / 2)))
.addHandlerLast(new WriteTimeoutHandler((int) (timeout.toSeconds() / 2)))));
WebClient webClient = WebClient.builder()
.baseUrl(format("http://%s:%d", host, port))
.clientConnector(new ReactorClientHttpConnector(httpClient)).build();
webClient.get().uri(endpoint).retrieve().bodyToMono(Recommendation.class).block();
}
This will lead to io.netty.handler.timeout.ReadTimeoutException.
.timeout(timeout.dividedBy(2)).block() leads to regular TimeoutException (java.util.concurrent) but it's still an open question whether a web client takes care about connections afterwards (probably not).
My solution is to use http client specific configuration to ensure native and correct way to utilize connections while adding new handler that wraps http client related exception into more generic ones (or java.util.concurrent.TimeoutException) so that WebClient clients won't depend on provider exceptions.
Related
I use a httpClient of apache in java to call REST APIs which is configured as below in a static block of class, what I want is to change the connection timeout per request. is it possible? how?
static {
PoolingHttpClientConnectionManager pool = new PoolingHttpClientConnectionManager();
pool.setMaxTotal(ChatSettings.HTTP_CLIENT_THREAD_POOL_SIZE);
httpClient = HttpClients
.custom()
.setConnectionManager(pool)
.setDefaultRequestConfig(
RequestConfig
.custom()
.setConnectTimeout(DEFAULT_HTTP_TIMEOUT)
.setSocketTimeout(DEFAULT_HTTP_TIMEOUT)
.build()
)
.build();
}
One thing you can implement for sure is to follow this example: https://github.com/apache/httpcomponents-client/blob/5.1.x/httpclient5/src/test/java/org/apache/hc/client5/http/examples/ClientAbortMethod.java
It demonstrates how a client can fire a request, but eventually change it's mind and tell the server it no longer wants to receive the response.
Combine this with a 'watchdog' thread that fires after some time/when the time runs out.
I have to call different urls with different connection timeout with webclient. But i found that we can set connect timeout globally only and not per resquest .. what to do please to set this timeout on a request without creating a new weblient each time.
You need to instantiate one webclient per url. For each webclient, you can set the connection timeout :
HttpClient httpClient = HttpClient.create()
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000);
WebClient client = WebClient.builder()
.baseUrl("http://yourendpoint:8080")
.clientConnector(new ReactorClientHttpConnector(httpClient))
.build();
Then reuse this webclient for all the calls to the configured url.
I know that a read and a connection timeouts can be configured in reactor-netty HttpClient, like:
public WebClient xsdWebClient() {
HttpClient httpClient = createHttpClient(config.getConnectionTimeout(), config.getReadTimeout());
return WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(httpClient.followRedirect(true)))
.baseUrl(config.getHost())
.build();
}
private static HttpClient createHttpClient(int connectionTimeout, int readTimeout) {
return HttpClient.create()
.option(
ChannelOption.CONNECT_TIMEOUT_MILLIS,
(int) TimeUnit.SECONDS.toMillis(connectionTimeout))
.doOnConnected(c -> c.addHandlerLast(new ReadTimeoutHandler(readTimeout)));
}
But what are the default read and connection timeouts for reactor-netty HttpClient?
I would recommend you to use the response timeout configuration provided by Reactor Netty instead of ReadTimeoutHandler. You can configure the response timeout either globally on HttpClient level for all requests or per request.
The default values for various timeouts provided by Reactor Netty you can find in the reference documentation.
Referring to answer given by one of the devs of reactor-netty, the read default time is 10 seconds.
I have 2 microservices. First one is simply echo service which returns after 3 seconds delay. Second one calls first over spring webclient. When I simulate 501 concurrent users to request second microservice, first 500 users get response in 3 seconds and the last one gets response in 6 seconds. Means spring webclient supports only 500 concurrent connections by default. Corresponding value is configurable in apache client. But I can't find how to configure it in spring webclient.
Similar situation with WebSocket, it supports only 256 concurrent connections. How to configure it?
Tests are done with jMeter. If I test first microservice directly - there is no limit.
public TestController(WebClient.Builder webClientBuilder) {
this.webClient = webClientBuilder.baseUrl("http://localhost:8081").build();
}
#GetMapping("/test1")
public Mono<String> test1() {
return webClient
.get().uri("/test/HTTP")
.retrieve().bodyToMono(String.class);
}
Thanks Mark, solution from you link works.
Either this:
HttpClient httpClient = HttpClient.create(ConnectionProvider
.elastic("myConnectionProvider"));
this.webClient = webClientBuilder.clientConnector(new ReactorClientHttpConnector(httpClient))
.baseUrl("http://localhost:8081").build();
Or that:
HttpClient httpClient = HttpClient.create(ConnectionProvider
.fixed("myConnectionProvider", 1000));
this.webClient = webClientBuilder.clientConnector(new ReactorClientHttpConnector(httpClient))
.baseUrl("http://localhost:8081").build();
I'm developing an SSE-client using WebFlux. What I want to achieve is to setup the client in a way, that
it automatically reconnects to the SSE-server, if the SSE-server was temporarily not available
it can also connect during run time, in case the SSE-server wasn't present from the start.
However, as far as I understand JavaScript-based clients, they are basically able to fulfill (1) and (2).
My question: is there a way to configure a WebFlux client to show the desired behaviors (1) and (2) (ideally "declaratively" via a builder and fluent coding style)? - I didn't find any information on how to do this, however, I assume that those are standard requirements for an SSE-client.
Here the basic code I'm using for my tests:
final SslContext sslContext
= SslContextBuilder
.forClient()
.trustManager(InsecureTrustManagerFactory.INSTANCE)
.build();
final HttpClient httpClient
= HttpClient
.create()
.secure(t -> t.sslContext(sslContext));
final WebClient client
= WebClient
.builder()
.clientConnector(new ReactorClientHttpConnector(httpClient))
.build();
final ParameterizedTypeReference<ServerSentEvent<String>> type = new ParameterizedTypeReference<ServerSentEvent<String>>() {};
final Flux<ServerSentEvent<String>> eventStream
= client.get()
.uri(endpointURL)
.exchange()
.flatMapMany(it -> it.bodyToFlux(type))
.repeat();
eventStream
.subscribe(
this::updateInformation
, error -> LOGGER.error("Error - {}", error.getMessage())
, () -> LOGGER.info("Got event stream completion signal."));