Skip to content
This repository was archived by the owner on May 30, 2024. It is now read-only.

Commit 91b188e

Browse files
committed
Abstract feature request code into new class
1 parent 6a8c04f commit 91b188e

File tree

2 files changed

+131
-100
lines changed

2 files changed

+131
-100
lines changed
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
package com.launchdarkly.client;
2+
3+
import com.google.gson.Gson;
4+
import com.google.gson.reflect.TypeToken;
5+
import org.apache.http.HttpStatus;
6+
import org.apache.http.client.cache.CacheResponseStatus;
7+
import org.apache.http.client.cache.HttpCacheContext;
8+
import org.apache.http.client.config.RequestConfig;
9+
import org.apache.http.client.methods.CloseableHttpResponse;
10+
import org.apache.http.client.methods.HttpGet;
11+
import org.apache.http.impl.client.CloseableHttpClient;
12+
import org.apache.http.impl.client.cache.CacheConfig;
13+
import org.apache.http.impl.client.cache.CachingHttpClients;
14+
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
15+
import org.apache.http.util.EntityUtils;
16+
import org.slf4j.Logger;
17+
import org.slf4j.LoggerFactory;
18+
19+
import java.io.IOException;
20+
import java.lang.reflect.Type;
21+
22+
/**
23+
* Created by jkodumal on 11/2/15.
24+
*/
25+
class FeatureRequestor {
26+
27+
private final String apiKey;
28+
private final LDConfig config;
29+
private final CloseableHttpClient client;
30+
private static final Logger logger = LoggerFactory.getLogger(FeatureRequestor.class);
31+
32+
FeatureRequestor(String apiKey, LDConfig config) {
33+
this.apiKey = apiKey;
34+
this.config = config;
35+
this.client = createClient();
36+
}
37+
38+
protected CloseableHttpClient createClient() {
39+
CloseableHttpClient client;
40+
PoolingHttpClientConnectionManager manager = new PoolingHttpClientConnectionManager();
41+
manager.setMaxTotal(100);
42+
manager.setDefaultMaxPerRoute(20);
43+
44+
CacheConfig cacheConfig = CacheConfig.custom()
45+
.setMaxCacheEntries(1000)
46+
.setMaxObjectSize(8192)
47+
.setSharedCache(false)
48+
.build();
49+
50+
RequestConfig requestConfig = RequestConfig.custom()
51+
.setConnectTimeout(config.connectTimeout)
52+
.setSocketTimeout(config.socketTimeout)
53+
.setProxy(config.proxyHost)
54+
.build();
55+
client = CachingHttpClients.custom()
56+
.setCacheConfig(cacheConfig)
57+
.setConnectionManager(manager)
58+
.setDefaultRequestConfig(requestConfig)
59+
.build();
60+
return client;
61+
}
62+
63+
<T> FeatureRep<T> makeRequest(String featureKey, boolean latest) throws IOException {
64+
Gson gson = new Gson();
65+
HttpCacheContext context = HttpCacheContext.create();
66+
67+
String resource = latest ? "/api/eval/latest-features/" : "/api/eval/features/";
68+
69+
HttpGet request = config.getRequest(apiKey,resource + featureKey);
70+
71+
CloseableHttpResponse response = null;
72+
try {
73+
response = client.execute(request, context);
74+
75+
CacheResponseStatus responseStatus = context.getCacheResponseStatus();
76+
77+
switch (responseStatus) {
78+
case CACHE_HIT:
79+
logger.debug("A response was generated from the cache with " +
80+
"no requests sent upstream");
81+
break;
82+
case CACHE_MODULE_RESPONSE:
83+
logger.debug("The response was generated directly by the " +
84+
"caching module");
85+
break;
86+
case CACHE_MISS:
87+
logger.debug("The response came from an upstream server");
88+
break;
89+
case VALIDATED:
90+
logger.debug("The response was generated from the cache " +
91+
"after validating the entry with the origin server");
92+
break;
93+
}
94+
95+
int status = response.getStatusLine().getStatusCode();
96+
97+
if (status != HttpStatus.SC_OK) {
98+
if (status == HttpStatus.SC_UNAUTHORIZED) {
99+
logger.error("Invalid API key");
100+
} else if (status == HttpStatus.SC_NOT_FOUND) {
101+
logger.error("Unknown feature key: " + featureKey);
102+
} else {
103+
logger.error("Unexpected status code: " + status);
104+
}
105+
throw new IOException("Failed to fetch flag");
106+
}
107+
108+
Type boolType = new TypeToken<FeatureRep<T>>() {}.getType();
109+
110+
FeatureRep<T> result = gson.fromJson(EntityUtils.toString(response.getEntity()), boolType);
111+
return result;
112+
}
113+
finally {
114+
try {
115+
if (response != null) response.close();
116+
} catch (IOException e) {
117+
}
118+
}
119+
}
120+
}

src/main/java/com/launchdarkly/client/LDClient.java

Lines changed: 11 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,17 @@
11
package com.launchdarkly.client;
22

33

4-
import com.google.gson.Gson;
54
import com.google.gson.JsonElement;
6-
import com.google.gson.reflect.TypeToken;
7-
import org.apache.http.HttpStatus;
85
import org.apache.http.annotation.ThreadSafe;
9-
import org.apache.http.client.cache.CacheResponseStatus;
10-
import org.apache.http.client.cache.HttpCacheContext;
11-
import org.apache.http.client.config.RequestConfig;
12-
import org.apache.http.client.methods.CloseableHttpResponse;
13-
import org.apache.http.client.methods.HttpGet;
14-
import org.apache.http.impl.client.CloseableHttpClient;
15-
import org.apache.http.impl.client.cache.CacheConfig;
16-
import org.apache.http.impl.client.cache.CachingHttpClients;
17-
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
18-
import org.apache.http.util.EntityUtils;
6+
import org.slf4j.Logger;
7+
import org.slf4j.LoggerFactory;
198

209
import java.io.Closeable;
2110
import java.io.IOException;
22-
import java.lang.reflect.Type;
2311
import java.net.URL;
2412
import java.util.jar.Attributes;
2513
import java.util.jar.Manifest;
2614

27-
import org.slf4j.Logger;
28-
import org.slf4j.LoggerFactory;
29-
3015
/**
3116
*
3217
* A client for the LaunchDarkly API. Client instances are thread-safe. Applications should instantiate
@@ -37,10 +22,9 @@
3722
public class LDClient implements Closeable {
3823
private static final Logger logger = LoggerFactory.getLogger(LDClient.class);
3924
private final LDConfig config;
40-
private final CloseableHttpClient client;
25+
private final FeatureRequestor requestor;
4126
private final EventProcessor eventProcessor;
4227
private final StreamProcessor streamProcessor;
43-
private final String apiKey;
4428
protected static final String CLIENT_VERSION = getClientVersion();
4529
private volatile boolean offline = false;
4630

@@ -63,9 +47,8 @@ public LDClient(String apiKey) {
6347
* @param config a client configuration object
6448
*/
6549
public LDClient(String apiKey, LDConfig config) {
66-
this.apiKey = apiKey;
6750
this.config = config;
68-
this.client = createClient();
51+
this.requestor = createFeatureRequestor(apiKey, config);
6952
this.eventProcessor = createEventProcessor(apiKey, config);
7053

7154
if (config.stream) {
@@ -78,6 +61,10 @@ public LDClient(String apiKey, LDConfig config) {
7861
}
7962
}
8063

64+
protected FeatureRequestor createFeatureRequestor(String apiKey, LDConfig config) {
65+
return new FeatureRequestor(apiKey, config);
66+
}
67+
8168
protected EventProcessor createEventProcessor(String apiKey, LDConfig config) {
8269
return new EventProcessor(apiKey, config);
8370
}
@@ -86,30 +73,6 @@ protected StreamProcessor createStreamProcessor(String apiKey, LDConfig config)
8673
return new StreamProcessor(apiKey, config);
8774
}
8875

89-
protected CloseableHttpClient createClient() {
90-
CloseableHttpClient client;
91-
PoolingHttpClientConnectionManager manager = new PoolingHttpClientConnectionManager();
92-
manager.setMaxTotal(100);
93-
manager.setDefaultMaxPerRoute(20);
94-
95-
CacheConfig cacheConfig = CacheConfig.custom()
96-
.setMaxCacheEntries(1000)
97-
.setMaxObjectSize(8192)
98-
.setSharedCache(false)
99-
.build();
100-
101-
RequestConfig requestConfig = RequestConfig.custom()
102-
.setConnectTimeout(config.connectTimeout)
103-
.setSocketTimeout(config.socketTimeout)
104-
.setProxy(config.proxyHost)
105-
.build();
106-
client = CachingHttpClients.custom()
107-
.setCacheConfig(cacheConfig)
108-
.setConnectionManager(manager)
109-
.setDefaultRequestConfig(requestConfig)
110-
.build();
111-
return client;
112-
}
11376

11477
/**
11578
* Tracks that a user performed an event.
@@ -191,13 +154,14 @@ public boolean toggle(String featureKey, LDUser user, boolean defaultValue) {
191154
logger.debug("Using feature flag stored from streaming API");
192155
result = (FeatureRep<Boolean>) this.streamProcessor.getFeature(featureKey);
193156
if (config.debugStreaming) {
194-
FeatureRep<Boolean> pollingResult = fetchFeature(featureKey);
157+
FeatureRep<Boolean> pollingResult = requestor.makeRequest(featureKey, true);
195158
if (!result.equals(pollingResult)) {
196159
logger.warn("Mismatch between streaming and polling feature! Streaming: {} Polling: {}", result, pollingResult);
197160
}
198161
}
199162
} else {
200-
result = fetchFeature(featureKey);
163+
// If streaming is enabled, always get the latest version of the feature while polling
164+
result = requestor.makeRequest(featureKey, this.config.stream);
201165
}
202166
if (result == null) {
203167
logger.warn("Unknown feature flag " + featureKey + "; returning default value");
@@ -221,60 +185,7 @@ public boolean toggle(String featureKey, LDUser user, boolean defaultValue) {
221185
}
222186
}
223187

224-
private FeatureRep<Boolean> fetchFeature(String featureKey) throws IOException {
225-
Gson gson = new Gson();
226-
HttpCacheContext context = HttpCacheContext.create();
227-
HttpGet request = config.getRequest(apiKey, "/api/eval/features/" + featureKey);
228-
229-
CloseableHttpResponse response = null;
230-
try {
231-
response = client.execute(request, context);
232-
233-
CacheResponseStatus responseStatus = context.getCacheResponseStatus();
234-
235-
switch (responseStatus) {
236-
case CACHE_HIT:
237-
logger.debug("A response was generated from the cache with " +
238-
"no requests sent upstream");
239-
break;
240-
case CACHE_MODULE_RESPONSE:
241-
logger.debug("The response was generated directly by the " +
242-
"caching module");
243-
break;
244-
case CACHE_MISS:
245-
logger.debug("The response came from an upstream server");
246-
break;
247-
case VALIDATED:
248-
logger.debug("The response was generated from the cache " +
249-
"after validating the entry with the origin server");
250-
break;
251-
}
252-
253-
int status = response.getStatusLine().getStatusCode();
254-
255-
if (status != HttpStatus.SC_OK) {
256-
if (status == HttpStatus.SC_UNAUTHORIZED) {
257-
logger.error("Invalid API key");
258-
} else if (status == HttpStatus.SC_NOT_FOUND) {
259-
logger.error("Unknown feature key: " + featureKey);
260-
} else {
261-
logger.error("Unexpected status code: " + status);
262-
}
263-
throw new IOException("Failed to fetch flag");
264-
}
265-
266-
Type boolType = new TypeToken<FeatureRep<Boolean>>() {}.getType();
267188

268-
FeatureRep<Boolean> result = gson.fromJson(EntityUtils.toString(response.getEntity()), boolType);
269-
return result;
270-
}
271-
finally {
272-
try {
273-
if (response != null) response.close();
274-
} catch (IOException e) {
275-
}
276-
}
277-
}
278189

279190
/**
280191
* Closes the LaunchDarkly client event processing thread and flushes all pending events. This should only

0 commit comments

Comments
 (0)