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.
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 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();
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.
I would like to use HTTP authentication with a Jersey 2 client using ApacheConnectorProvider, but I want to set it for each request (not as ClientConfig property). The reason is that I use the client for multiple connections, only some of which require HTTP authentication. I assume it is better to not recreate the Client object each time I want to send an HTTP request.
What I found so far:
1) from https://github.com/jersey/jersey/blob/master/connectors/apache-connector/src/test/java/org/glassfish/jersey/apache/connector/AuthTest.java
CredentialsProvider credentialsProvider = new org.apache.http.impl.client.BasicCredentialsProvider();
credentialsProvider.setCredentials(AuthScope.ANY,
new UsernamePasswordCredentials("name", "password")
);
ClientConfig cc = new ClientConfig();
cc.property(ApacheClientProperties.CREDENTIALS_PROVIDER, credentialsProvider).property(ApacheClientProperties.PREEMPTIVE_BASIC_AUTHENTICATION, true);
cc.connectorProvider(new ApacheConnectorProvider());
Client client = ClientBuilder.newClient(cc);
WebTarget r = client.target(getBaseUri());
r.request().get(String.class);
This would probably work, but as I reuse the same Client for multiple HTTP requests, not all using HTTP authentication, I'd need to recreate a Client object each time I send a request. I do not know if that is an expensive operation, so it could be a solution.
2) from https://jersey.java.net/documentation/latest/client.html#d0e4833
Response response = client.target("http://localhost:8080/rest/homer/contact").request()
.property(HttpAuthenticationFeature.HTTP_AUTHENTICATION_BASIC_USERNAME, "homer")
.property(HttpAuthenticationFeature.HTTP_AUTHENTICATION_BASIC_PASSWORD, "p1swd745").get();
This does not seem to work with ApacheConnectorProvider.
What is the right solution to my problem?
Thanks!