HttpClient executes requests multiple time if request timed out - java

HttpClient executes request 4 times if it times out. If it does not time out then it is working fine. Is it related to HttpClient?

I found that it is HttpClient's default behaviour to execute requests 4 times if it fails. I am not sure about other kind of failures but at least with time out.
To disable this behaviour do this :
DefaultHttpClient client = new DefaultHttpClient();
// Disable default behavior of HttpClient of retrying requests in case of failure
((AbstractHttpClient) client).setHttpRequestRetryHandler(new DefaultHttpRequestRetryHandler(0, false));
Here retry count is set to 0 to disable retry.
I found solution from this blog.

This resolved the issue for me. Using httpclient 4.3 and above.
HttpClientBuilder.create().disableAutomaticRetries().build();

Apache HttpClient tries to connect 5 times in case of transport exception. Here is what doc says:
HttpClient will automatically retry up to 5 times those methods that
fail with a transport exception while the HTTP request is still being
transmitted to the target server (i.e. the request has not been fully
transmitted to the server).
To change this behaviour you need to implement HttpMethodRetryHandler interface

Related

How to set socket timeout in Java HTTP Client

We want to migrate all our apache-httpclient-4.x code to java-http-client code to reduce dependencies. While migrating them, i ran into the following issue under java 11:
How to set the socket timeout in Java HTTP Client?
With apache-httpclient-4.x we can set the connection timeout and the socket timeout like this:
DefaultHttpClient httpClient = new DefaultHttpClient();
int timeout = 5; // seconds
HttpParams httpParams = httpClient.getParams();
httpParams.setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, timeout * 1000);
httpParams.setParameter(CoreConnectionPNames.SO_TIMEOUT, timeout * 1000);
With java-http-client i can only set the connection timeout like this:
HttpClient httpClient = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(5))
.build()
But i found no way to set the socket timeout. Is there any way or an open issue to support that in the future?
You can specify it at the HttpRequest.Builder level via the timeout method:
HttpClient httpClient = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(5))
.build();
HttpRequest httpRequest = HttpRequest.newBuilder()
.uri(URI.create("..."))
.timeout(Duration.ofSeconds(5)) //this
.build();
httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
If you've got connected successfully but not able to receive a response at the desired amount of time, java.net.http.HttpTimeoutException: request timed out will be thrown (in contrast with java.net.http.HttpConnectTimeoutException: HTTP connect timed out which will be thrown if you don't get a successful connection).
There doesn't seem to be a way to specify a timeout on the flow of packets (socket timeout) on the Java Http Client.
I found an enhancement request on OpenJDK which seems to cover this possibility - https://bugs.openjdk.org/browse/JDK-8258397
Content from the link
The HttpClient lets you set a connection timeout (HttpClient.Builder) and a request timeout (HttpRequest.Builder). However the request timeout will be cancelled as soon as the response headers have been read. There is currently no timeout covering the reception of the body.
A possibility for the caller is to make use of the CompletableFuture API (get/join will accept a timeout, or CF::orTimeout can be called).
IIRC - in that case, it will still be the responsibility of the caller to cancel the request. We might want to reexamine and possibility change that.
The disadvantage here is that some of our BodyHandlers (ofPublisher, ofInputStream) will return immediately - so the CF API won't help in this case.
This might be a good thing (or not).
Another possibility could be to add a body timeout on HttpRequest.Builder. This would then cover all cases - but do we really want to timeout in the case of ofInputStream or ofPublisher if the caller doesn't read the body fast enough?

How to add a fix to avoid 504 Gateway timeout error

I am getting 504 Gateway timeout error from my GET method call to another service.
Recently I added a fix by increasing the timeout period but that didn't help.
This is what I have tried
public void getUserInformation(final Integer userId) {
HttpClient httpClient = getBasicAuthDefaultHttpClient();
HttpGet httpGet = new HttpGet("http://xxxx/users/"+userId);
httpGet.addHeader("userid", userid);
httpGet.addHeader("secret", secret);
try {
HttpResponse response = httpClient.execute(httpGet);
HttpEntity entity = response.getEntity();
if (entity != null && HttpStatus.OK.value() ==
response.getStatusLine().getStatusCode()) {
ObjectMapper objectMapper = new ObjectMapper();
userInfo = objectMapper.readValue(entity.getContent(),
UserInfo.class);
} else {
logger.error("Call to the service failed: response code:
{}", response.getStatusLine().getStatusCode());
}
} catch (Exception e) {
logger.error("Exception: "+ e);
}
}
public HttpClient getBasicAuthDefaultHttpClient() {
CredentialsProvider provider = new BasicCredentialsProvider();
UsernamePasswordCredentials creds = new
UsernamePasswordCredentials(user, password);
provider.setCredentials(AuthScope.ANY, creds);
//Fix to avoid HTTP 504 ERROR (GATEWAY TIME OUT ERROR) for ECM calls
RequestConfig.Builder requestBuilder = RequestConfig.custom();
requestBuilder.setConnectTimeout(30 * 1000);
requestBuilder.setConnectionRequestTimeout(30 * 1000);
HttpClientBuilder builder = HttpClientBuilder.create();
builder.setDefaultRequestConfig(requestBuilder.build());
builder.setDefaultCredentialsProvider(provider).build();
return builder.build();
}
I am calling this process within a loop to process records, this works for most of the records but fails for few userId's in that.
But what I noticed is everything will work fine when I run only the failed records, not sure whats the problem in this case.
I thought of calling the method again when I receive 504 to invoke it again hoping to receive 200 next time.
Not sure is this the good idea. Any advice would be greatly appreciated.
According to the description of the 504 Gateway Timeout status code, it is returned when you have a chain of servers that communicate to process the request and one of the nodes (not the server you are calling but some later one) is not able to process the request in a timely fashion.
I would presume that the situation you are in could be depicted as follows.
CLIENT -> USERS SERVICE -> SOME OTHER SERVICE
The problem is that SOME OTHER SERVICE is taking too long to process your request. The USERS SERVICE gives up at some point in time and returns you this specific status code to indicate that.
As far as I know, there is little you could do to mitigate the problem. You need to get in touch with the owners of the USERS SERVICE and ask them to increase their timeout or the owners of SOME OTHER SERVICE and ask them to improve their performance.
As for why such an error could occur from time to time. It is possible that you, in combination with other clients, are transitively overloading SOME OTHER SERVICE, causing it to process requests slower and slower. Or it could be that SOME OTHER SERVICE has throttling or rate limiting enabled to prevent Denial of Service attacks. By making too many requests to the USERS SERVICE it is possible that you are consuming the quota it has.
Of course, all of these are speculations, without knowing you actual scenario.
I faced the same sometime back, below are the checks i did to resolve this. I will add more details to the above analogy.
Client-> Users Service -> Some Other Service
Client checks:
httpclient - I see you are using RequestConfig.Builder to set the client timeout.(that will do in your case)
jerseyclient - If someone uses jersey client it can be configured as How to set the connection and read timeout with Jersey 2.x?
'Some Other Service' checks:
If throttling/rate limiting is set to avoid DOS attacks. Then you need to increase the timeouts on Some Other Service. I used tomcat server on AWS: Changed the idle timeout in your yaml file
metadata:
annotations:
#below for openshift which worked for me
haproxy.router.openshift.io/timeout:20000
#below for kubernetes timeout in ELB
service.beta.kubernetes.io/aws-load-balancer-connection-idle-timeout:20000
Also changed the connector timeout on tomcat
<Connector connectionTimeout="20000" port="8080" protocol="HTTP/1.1" redirectPort="8443"/>
Voila! It worked for me.

ssl handshake on every request in multiThreaded client

Architecture is midTier Liberty server that receives http requests and brokers to various back ends, some REST, some just JSON. When I configure for SSL (only thru envVars which is quite cool) ... it appears I get a full handShake w/every request. Additionally, the server side uses a different thread with each request (may be related). This is Liberty so it is multiThreaded. Servlet has static ref to POJO that does all apache httpClient work. Not using HttpClientContext (in this case). Basic flow is at end (struggling w/formatting for post legality)
EnvVars are:
-Djavax.net.ssl.keyStore=/root/lWasServers/certs/zosConnKey.jks
-Djavax.net.ssl.trustStore=/root/lWasServers/certs/zosConnTrust.jks
-Djavax.net.ssl.keyStorePassword=fredpwd
-Dhttp.maxConnections=40
Looked at many similar problems, but again, right now this flow does not use client context. Hoping I'm missing something simple. Code being appended on first response as I continue to struggle here w/FF in RHEL.
private static PoolingHttpClientConnectionManager cm = null ;
private static CloseableHttpClient httpClient = null ;
// ....
cm = new PoolingHttpClientConnectionManager();
cm.setMaxTotal(512);
cm.setDefaultMaxPerRoute(256) ;
httpClient = HttpClients.custom().setConnectionManager(cm).build() ;
// ...
responseBody = httpClient.execute(httpGet, responseHandler);
If a persistent HTTP connection is stateful and is associated with a particular security context or identity, such as SSL key or NTLM user name, HttpClient tries to make sure this connection cannot be accidentally re-used within a different security context or by a different user. Usually the most straight-forward way of letting HttpClient know that requests are logically related and belong to the same session is by executing those requests with the same HttpContext instance. See HttpClient tutorial for details. One can also disable connection state tracking if HttpClient can only be accessed by a single user or within the same security context. Use with caution.
OK, while I'm not exactly an expert at reading the ssl trace, I do believe I have resolved it. I am on a thread but that is controlled by the server. I now pass the HttpSession in and keep a reference to the HttpClientConnection that I now create for each session. I pool these HttpClientConnection objects (rudimentary pooling, basically just get/release). So all calls w/in an http session use the same HttpClientContext. Now it appears that I am NOT handShaking all the time. There may have been a better way to do it, but this does indeed work, I have a few gremlins to look into (socket timeouts in < 1 millisecond?) ... but I'm confident that I'm non longer handShaking with each request (only each time I end up creating a new context) ... so this is all good. Thanks,

HttpClient 4 redirect handling different inside Tomcat

I've got an HttpClient instance that fetches a remote resource. I configure it to handle redirects.
HttpParams params = new BasicHttpParams();
params.setIntParameter(CoreConnectionPNames.SO_TIMEOUT,
SOCKET_TIMEOUT);
params.setIntParameter(CoreConnectionPNames.CONNECTION_TIMEOUT,
CONNECTION_TIMEOUT);
params.setLongParameter(ClientPNames.CONN_MANAGER_TIMEOUT,
CONN_MANAGER_TIMEOUT_VALUE);
params.setParameter(ClientPNames.COOKIE_POLICY,
CookiePolicy.BROWSER_COMPATIBILITY);
params.setBooleanParameter(ClientPNames.HANDLE_REDIRECTS, true);
params.setBooleanParameter(ClientPNames.REJECT_RELATIVE_REDIRECT,
false);
params.setIntParameter(ClientPNames.MAX_REDIRECTS, 4);
httpclient = new DefaultHttpClient(cm, params);
When I'm calling it from inside a webapp (Tomcat6) I get the 301 response. When I call it from JSE environment I get the 200 final response (redirects get handled). My first suspect was classloading issues, but printing out the source of HttpClient class shows that both times it's loaded from httpclient-4.2.5.jar
Any ideas how else I can debug this?
Run HttpClient with the context / wire logging turned on as described here and compare HTTP message exchanged in both environments.
The HttpClient instance was shared throughout the webapp, including SolrJ (Solr client), which set the "follow redirect" param to false. I figured this out by creating a copy of the RequestDirector with extra logging lines. I could have simply looked for all calls of HttpClient.getParams(). The more you know.

HTTPClient sends out two requests when using Basic Auth?

I have been using HTTPClient version 4.1.2 to try to access a REST over HTTP API that requires Basic Authentication. Here is client code:
DefaultHttpClient httpClient = new DefaultHttpClient(new ThreadSafeClientConnManager());
// Enable HTTP Basic Auth
httpClient.getCredentialsProvider().setCredentials(
new AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT),
new UsernamePasswordCredentials(this.username, this.password));
HttpHost proxy = new HttpHost(this.proxyURI.getHost(), this.proxyURI.getPort());
httpClient.getParams().setParameter(ConnRouteParams.DEFAULT_PROXY, proxy);
When I construct a POST request, like this:
HttpPost request = new HttpPost("http://my/url");
request.addHeader(new BasicHeader("Content-type", "application/atom+xml; type=entry")); // required by vendor
request.setEntity(new StringEntity("My content"));
HttpResponse response = client.execute(request);
I see in Charles Proxy that there are two requests being sent. One without the Authorization: Basic ... header and one with it. The first one fails with a 401, as you would expect, but the second goes through just fine with a 201.
Does anyone know why this happens? Thanks!
EDIT:
I should make clear that I have already looked at this question, but as you can see I set the AuthScope the same way and it didn't solve my problem. Also, I am creating a new HttpClient every time I made a request (though I use the same ConnectionManager), but even if I use the same HttpClient for multiple requests, the problem still persists.
EDIT 2:
So it looks like what #LastCoder was suggesting is the way to do. See this answer to another question. The problem stems from my lack of knowledge around the HTTP spec. What I'm looking to do is called "preemptive authentication" and the HttpClient docs mention it here. Thankfully, the answer linked to above is a much shorter and cleaner way to do it.
Rather than using .setCredentials() why don't you just encode USERNAME:PASSWORD and add the authentication header with .addHeader()
This means that your server/target endpoint is creating a new session for every client request. This forces every request of yours to go through a hand-shake, which means the clients first makes the call and realizes that it needs authorization, then it follows with the authorization. What you need to do is send the authorization preemptively as follows:
httpClient.getParams().setAuthenticationPreemptive(true);
Just to understand the process you may log your client request headers, to give you an idea of what your client is sending and receiving:
See if this works.

Categories

Resources