https://blogs.oracle.com/japod/entry/how_to_use_jersey_client
http://www.baeldung.com/httpclient-connection-management
The PoolingHttpClientConnectionManager will create and manage a pool of connections for each route or target host we use. The default size of the pool of concurrent connections that can be open by the manager is 2 for each route or target host, and 20 for total open connections.
Using a Stale Connection Monitor Thread
Timeout and DNS Round Robin – something to be aware of
http://www.baeldung.com/unshorten-url-httpclient
an http client that doesn’t automatically follow redirects:
http://stackoverflow.com/questions/5273579/how-to-clone-a-detached-httpservletrequest-and-httpservletresponse-provided-by-t
http://www.baeldung.com/httpclient-connection-management
The PoolingHttpClientConnectionManager will create and manage a pool of connections for each route or target host we use. The default size of the pool of concurrent connections that can be open by the manager is 2 for each route or target host, and 20 for total open connections.
Notice the EntityUtils.consume(response.getEntity) call – necessary to consume the entire content of the response (entity) so that the manager can release the connection back to the pool.
org.apache.http.util.EntityUtils.consume(HttpEntity)
public static void consume(final HttpEntity entity) throws IOException {
if (entity == null) {
return;
}
if (entity.isStreaming()) {
final InputStream instream = entity.getContent();
if (instream != null) {
instream.close();
}
}
}
“If the Keep-Alive
header is not present in the response,HttpClient assumes the connection can be kept alive indefinitely.” (See the HttpClient Reference).ConnectionKeepAliveStrategy myStrategy = new ConnectionKeepAliveStrategy() { @Override public long getKeepAliveDuration(HttpResponse response, HttpContext context) { HeaderElementIterator it = new BasicHeaderElementIterator (response.headerIterator(HTTP.CONN_KEEP_ALIVE)); while (it.hasNext()) { HeaderElement he = it.nextElement(); String param = he.getName(); String value = he.getValue(); if (value != null && param.equalsIgnoreCase ( "timeout" )) { return Long.parseLong(value) * 1000 ; } } return 5 * 1000 ; } }; |
This strategy will first try to apply the host’s Keep-Alive policy stated in the header. If that information is not present in the response header it will keep alive connections for 5 seconds.
PoolingHttpClientConnectionManager connManager =
new
PoolingHttpClientConnectionManager();
CloseableHttpClient client = HttpClients.custom().setKeepAliveStrategy(myStrategy).
setConnectionManager(connManager).build();
The HTTP/1.1 Spec states that connections can be re-used if they have not been closed – this is known as connection persistence.
Once a connection is released by the manager it stays open for re-use. When using aBasicHttpClientConnectionManager, which can only mange a single connection, the connection must be released before it is leased back again:
The only timeout that can be set at the time when connection manager is configured is the socket timeout:
Connection eviction is used to detect idle and expired connections and close them; there are two options to do this.
- Relying on the HttpClient to check if the connection is stale before executing a request. This is an expensive option that is not always reliable.
- Create a monitor thread to close idle and/or closed connections.
Setting the HttpClient to Check for Stale Connections
1
2
3
4
| PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager(); CloseableHttpClient client = HttpClients.custom().setDefaultRequestConfig( RequestConfig.custom().setStaleConnectionCheckEnabled( true ).build()). setConnectionManager(connManager).build(); |
public
class
IdleConnectionMonitorThread
extends
Thread {
private
final
HttpClientConnectionManager connMgr;
private
volatile
boolean
shutdown;
public
IdleConnectionMonitorThread
(PoolingHttpClientConnectionManager connMgr) {
super
();
this
.connMgr = connMgr;
}
@Override
public
void
run() {
try
{
while
(!shutdown) {
synchronized
(
this
) {
wait(
1000
);
connMgr.closeExpiredConnections();
connMgr.closeIdleConnections(
30
, TimeUnit.SECONDS);
}
}
}
catch
(InterruptedException ex) {
shutdown();
}
}
public
void
shutdown() {
shutdown =
true
;
synchronized
(
this
) {
notifyAll();
}
}
}
A connection can be closed gracefully (an attempt to flush the output buffer prior to closing is made), or forcefully, by calling the shutdown method (the output buffer is not flushed). To properly close connections we need to do all of the following:
- consume and close the response (if closeable)
- close the client
- close and shut down the connection manager
connManager = new PoolingHttpClientConnectionManager(); CloseableHttpClient client = HttpClients.custom().setConnectionManager(connManager).build(); CloseableHttpResponse response = client.execute(get); EntityUtils.consume(response.getEntity()); response.close(); client.close(); connManager.close(); |
If the manager is shut down without connections being closed already – all connections will be closed and all resources released. It’s important to keep in mind that this will not flush any data that may have been ongoing in the existing connections.
http://www.baeldung.com/httpclient-timeout- the Connection Timeout (http.connection.timeout) – the time to establish the connection with the remote host
- the Socket Timeout (http.socket.timeout) – the time waiting for data – after the connection was established; maximum time of inactivity between two data packets
- the Connection Manager Timeout (http.connection-manager.timeout) – the time to wait for a connection from the connection manager/pool
The first two parameters – the connection and socket timeouts – are the most important, but setting a timeout for obtaining a connection is definitely important in high load scenarios, which is why the third parameter shouldn’t be ignored.
Note that the connection timeout will result in aorg.apache.http.conn.ConnectTimeoutException being thrown, while socket timeout will result in a java.net.SocketTimeoutException.
we need to set a hard timeout for the entire request.
int
hardTimeout =
5
;
// seconds
TimerTask task =
new
TimerTask() {
@Override
public
void
run() {
if
(getMethod !=
null
) {
getMethod.abort();
}
}
};
new
Timer(
true
).schedule(task, hardTimeout *
1000
);
HttpResponse response = httpClient.execute(getMethod);
Timeout and DNS Round Robin – something to be aware of
It is quite common that some larger domains will be using a DNS round robin configuration – essentially having the same domain mapped to multiple IP addresses. This introduces a new challenge for timeout against such a domain, simply because of the way HttpClient will try to connect to that domain that times out:
- HttpClient gets the list of IP routes to that domain
- it tries the first one – that times out (with the timeouts we configure)
- it tries the second one – that also times out
- and so on …
So, as you can see – the overall operation will not time out when we expect it to. Instead – it will time out when all the possible routes have timed out, and what it more – this will happen completely transparently for the client (unless you have your log configured at the DEBUG level). Here is a simple example you can run and replicate this issue:
1
2
3
4
5
6
7
8
9
| int timeout = 3 ; RequestConfig config = RequestConfig.custom(). setConnectTimeout(timeout * 1000 ). setConnectionRequestTimeout(timeout * 1000 ). setSocketTimeout(timeout * 1000 ).build(); CloseableHttpClient client = HttpClientBuilder.create().setDefaultRequestConfig(config).build(); response = client.execute(request); |
You will notice the retrying logic with a DEBUG log level:
1
2
3
4
5
6
7
8
9
10
11
12
| DEBUG o.a.h.i.c.HttpClientConnectionOperator - Connecting to www.google.com /173 .194.34.212:81 DEBUG o.a.h.i.c.HttpClientConnectionOperator - Connect to www.google.com /173 .194.34.212:81 timed out. Connection will be retried using another IP address DEBUG o.a.h.i.c.HttpClientConnectionOperator - Connecting to www.google.com /173 .194.34.208:81 DEBUG o.a.h.i.c.HttpClientConnectionOperator - Connect to www.google.com /173 .194.34.208:81 timed out. Connection will be retried using another IP address DEBUG o.a.h.i.c.HttpClientConnectionOperator - Connecting to www.google.com /173 .194.34.209:81 DEBUG o.a.h.i.c.HttpClientConnectionOperator - Connect to www.google.com /173 .194.34.209:81 timed out. Connection will be retried using another IP address ... |
an http client that doesn’t automatically follow redirects:
http://stackoverflow.com/questions/5273579/how-to-clone-a-detached-httpservletrequest-and-httpservletresponse-provided-by-t
Cloning HTTP request and response is possible via HttpServletResponseWrapper classhttp://docs.oracle.com/javaee/1.3/api/javax/servlet/http/HttpServletResponseWrapper.html. You can find an example of usage on Sun documentationhttps://web.archive.org/web/20120626033905/http://java.sun.com/j2ee/tutorial/1_3-fcs/doc/Servlets8.html.
Notice this was a workaround from (at that time) Sun to address this problem as it was never planned that you could modify request and response information before committed.
Chunked transfer encoding is a data transfer mechanism in version 1.1 of the Hypertext Transfer Protocol (HTTP) in which data is sent in a series of "chunks". It uses the Transfer-EncodingHTTP header in place of the Content-Length header, which the earlier version of the protocol would otherwise require.[1] Because the Content-Length header is not used, the sender does not need to know the length of the content before it starts transmitting a response to the receiver. Senders can begin transmitting dynamically-generated content before knowing the total size of that content.
The size of each chunk is sent right before the chunk itself so that the receiver can tell when it has finished receiving data for that chunk. The data transfer is terminated by a final chunk of length zero.