-
Notifications
You must be signed in to change notification settings - Fork 14
/
HttpClientFactory.java
126 lines (113 loc) · 5.34 KB
/
HttpClientFactory.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
package cz.cvut.kbss.ontodriver.rdf4j.connector.init;
import cz.cvut.kbss.ontodriver.config.DriverConfiguration;
import org.apache.http.HttpConnection;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.ServiceUnavailableRetryStrategy;
import org.apache.http.client.config.CookieSpecs;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.protocol.HttpContext;
import org.eclipse.rdf4j.http.client.SharedHttpClientSessionManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.HttpURLConnection;
class HttpClientFactory {
/**
* Creates a customized {@link HttpClient} instance.
* <p>
* The client's configuration is basically the same as the default one used by RDF4J, but it sets a timeout on
* connection requests from the connection pool, so that an application with exhausted connection pool fails
* gracefully instead of potentially going into a deadlock.
*
* @param configuration Driver configuration
*
* @return HttpClient instance with connection pool request timeout
*/
static HttpClient createHttpClient(DriverConfiguration configuration) {
final RequestConfig customRequestConfig = RequestConfig.custom()
.setCookieSpec(CookieSpecs.STANDARD)
.setConnectionRequestTimeout(1000).build();
return HttpClientBuilder.create()
.evictExpiredConnections()
.setRetryHandler(new RetryHandlerStale())
.setServiceUnavailableRetryStrategy(new ServiceUnavailableRetryHandler())
.useSystemProperties()
.setDefaultRequestConfig(customRequestConfig).build();
}
/**
* Copied from {@link SharedHttpClientSessionManager}
*/
private static class RetryHandlerStale implements HttpRequestRetryHandler {
private final Logger logger = LoggerFactory.getLogger(RetryHandlerStale.class);
@Override
public boolean retryRequest(IOException ioe, int count, HttpContext context) {
// only try this once
if (count > 1) {
return false;
}
HttpClientContext clientContext = HttpClientContext.adapt(context);
HttpConnection conn = clientContext.getConnection();
if (conn != null) {
synchronized (this) {
if (conn.isStale()) {
try {
logger.warn("Closing stale connection");
conn.close();
return true;
} catch (IOException e) {
logger.error("Error closing stale connection", e);
}
}
}
}
return false;
}
}
/**
* Copied from {@link SharedHttpClientSessionManager}
*/
private static class ServiceUnavailableRetryHandler implements ServiceUnavailableRetryStrategy {
private final Logger logger = LoggerFactory.getLogger(ServiceUnavailableRetryHandler.class);
@Override
public boolean retryRequest(HttpResponse response, int executionCount, HttpContext context) {
// only retry on `408`
if (response.getStatusLine().getStatusCode() != HttpURLConnection.HTTP_CLIENT_TIMEOUT) {
return false;
}
// when `keepAlive` is disabled every connection is fresh (with the default `useSystemProperties` http
// client configuration we use), a 408 in that case is an unexpected issue we don't handle here
String keepAlive = System.getProperty("http.keepAlive", "true");
if (!"true".equalsIgnoreCase(keepAlive)) {
return false;
}
// worst case, the connection pool is filled to the max and all of them idled out on the server already
// we then need to clean up the pool and finally retry with a fresh connection. Hence, we need at most
// pooledConnections+1 retries.
// the pool size setting used here is taken from `HttpClientBuilder` when `useSystemProperties()` is used
int pooledConnections = Integer.parseInt(System.getProperty("http.maxConnections", "5"));
if (executionCount > (pooledConnections + 1)) {
return false;
}
HttpClientContext clientContext = HttpClientContext.adapt(context);
HttpConnection conn = clientContext.getConnection();
synchronized (this) {
try {
logger.info("Cleaning up closed connection");
conn.close();
return true;
} catch (IOException e) {
logger.error("Error cleaning up closed connection", e);
}
}
return false;
}
@Override
public long getRetryInterval() {
return 1000;
}
}
}