Skip to content

Commit ba01220

Browse files
author
Mike Davis
authored
Add package level README.md for core-httpclient-impl (#303)
1 parent 2f1c564 commit ba01220

File tree

3 files changed

+228
-0
lines changed

3 files changed

+228
-0
lines changed

core-httpclient-impl/README.md

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
# Java SDK Async Http Client
2+
3+
This package provides default implementations of an Optimizely `EventHandler` and `ProjectConfigManager`. Also included
4+
in this package is a factory class, `OptimizelyFactory`, which can be used to reliably instantiate the Optimizely SDK
5+
with the default configuration of the `AsyncEventHandler` and `HttpProjectConfigManager`.
6+
7+
## Installation
8+
9+
### Gradle
10+
11+
```groovy
12+
compile 'com.optimizely.ab:core-httpclient-impl:{VERSION}'
13+
```
14+
15+
### Maven
16+
```xml
17+
<dependency>
18+
<groupId>com.optimizely.ab</groupId>
19+
<artifactId>core-httpclient-impl</artifactId>
20+
<version>{VERSION}</version>
21+
</dependency>
22+
23+
```
24+
25+
### Basic usage
26+
```java
27+
import com.optimizely.ab.Optimizely;
28+
import com.optimizely.ab.OptimizelyFactory;
29+
30+
public class App {
31+
32+
public static void main(String[] args) {
33+
String sdkKey = args[0];
34+
Optimizely optimizely = OptimizelyFactory.newDefaultInstance(sdkKey);
35+
}
36+
}
37+
38+
```
39+
40+
### Advanced usage
41+
```java
42+
import com.optimizely.ab.Optimizely;
43+
import com.optimizely.ab.config.HttpProjectConfigManager;
44+
import com.optimizely.ab.event.AsyncEventHandler;
45+
46+
import java.util.concurrent.TimeUnit;
47+
48+
public class App {
49+
50+
public static void main(String[] args) {
51+
String sdkKey = args[0];
52+
53+
EventHandler eventHandler = AsyncEventHandler.builder()
54+
.withQueueCapacity(20000)
55+
.withNumWorkers(5)
56+
.build();
57+
58+
ProjectConfigManager projectConfigManager = HttpProjectConfigManager.builder()
59+
.withSdkKey(sdkKey)
60+
.withPollingInterval(1, TimeUnit.MINUTES)
61+
.build();
62+
63+
Optimizely optimizely = Optimizely.builder()
64+
.withConfig(projectConfigManager)
65+
.withEventHandler(eventHandler)
66+
.build();
67+
}
68+
}
69+
```
70+
71+
## AsyncEventHandler
72+
73+
The [`AsyncEventHandler`](https://github.com/optimizely/java-sdk/blob/master/core-httpclient-impl/src/main/java/com/optimizely/ab/event/AsyncEventHandler.java)
74+
provides an implementation of the the [`EventHandler`](https://github.com/optimizely/java-sdk/blob/master/core-api/src/main/java/com/optimizely/ab/event/EventHandler.java)
75+
backed by a `ThreadPoolExecutor`. When events are
76+
triggered from the Optimizely SDK, they are immediately queued as discrete tasks to the executor and processed in the
77+
order they were submitted. Each worker is responsible for making outbound http requests to the
78+
Optimizely log endpoint for metric tracking. The default queue size and the number of workers are configurable via
79+
global properties and can be overridden via the `AsyncEventHandler.Builder`.
80+
81+
### Usage
82+
83+
To use the AsyncEventHandler, an instance must be built via the `AsyncEventHandler.Builder` then passed to the `Optimizely.Builder`
84+
85+
```java
86+
EventHandler eventHandler = AsyncEventHandler.builder()
87+
.withQueueCapacity(20000)
88+
.withNumWorkers(5)
89+
.build();
90+
```
91+
92+
#### Queue capacity
93+
94+
The queue capacity can be set to initialize the backing queue for the executor service. If the queue fills up, then
95+
events will be dropped and exception will be logged. Setting a higher queue value will prevent event loss, but will
96+
use up more memory in the event the workers can not keep up if the production rate.
97+
98+
#### Number of workers
99+
100+
The number of workers determines the number of threads used by the thread pool.
101+
102+
#### Advanced configurations
103+
104+
|Property Name|Default Value|Description|
105+
|---|---|---|
106+
|async.event.handler.queue.capacity|10000|Queue size for pending LogEvents|
107+
|async.event.handler.num.workers|2|Number of worker threads|
108+
|async.event.handler.max.connections|200|Max number of connections|
109+
|async.event.handler.event.max.per.route|20|Max number of connections per route|
110+
|async.event.handler.validate.after|5000|Time in milliseconds to maintain idol connections|
111+
112+
113+
## HttpProjectConfigManager
114+
115+
The [`HttpProjectConfigManager`](https://github.com/optimizely/java-sdk/blob/master/core-httpclient-impl/src/main/java/com/optimizely/ab/config/HttpProjectConfigManager.java)
116+
is an implementation of the abstract [`PollingProjectConfigManager`](https://github.com/optimizely/java-sdk/blob/master/core-api/src/main/java/com/optimizely/ab/config/PollingProjectConfigManager.java).
117+
The `poll` method is extended and makes an http GET request to the configured url to asynchronously download the project data file
118+
and initialize an instance of the ProjectConfig. By default, the `HttpProjectConfigManager` will block until the
119+
first successful retrieval of the datafile, up to a configurable timeout. The frequency of the polling method and the
120+
blocking timeout can be set via the `HttpProjectConfigManager.Builder` with the default values being pulled from global
121+
properties.
122+
123+
### Usage
124+
125+
```java
126+
ProjectConfigManager projectConfigManager = HttpProjectConfigManager.builder()
127+
.withSdkKey(sdkKey)
128+
.withPollingInterval(1, TimeUnit.MINUTES)
129+
.build();
130+
```
131+
132+
#### SDK Key
133+
134+
The SDK key is used to compose the outbound http request to the default datafile location hosted on the Optimizely CDN.
135+
136+
#### Polling interval
137+
138+
The polling interval is used to determine a fixed delay between consecutive http requests for the datafile.
139+
140+
#### Initial Datafile
141+
142+
An initial datafile can be provided via the builder to bootstrap the the `ProjectConfigManager` so that it can be used
143+
immediately without blocking execution.
144+
145+
#### Advanced configurations
146+
147+
|Property Name|Default Value|Description|
148+
|---|---|---|
149+
|http.project.config.manager.polling.duration|5|Fixed delay between fetches for the datafile|
150+
|http.project.config.manager.polling.unit|MINUTES|Time unit corresponding to polling interval|
151+
|http.project.config.manager.blocking.duration|10|Max duration spent waiting for initial bootstrapping|
152+
|http.project.config.manager.blocking.unit|SECONDS|Time unit corresponding to blocking duration|
153+
|http.project.config.manager.sdk.key|null|Optimizely project SDK key|
154+
155+
156+
## Optimizely properties file
157+
158+
An Optimizely properties file, `optimizely.properties`, that is available within the runtime classpath can be used to configure
159+
the default values of a given Optimizely resource. Refer to the resource implementation for available configuration
160+
parameters.
161+
162+
#### Example:
163+
```properties
164+
http.project.config.manager.polling.duration = 1
165+
http.project.config.manager.polling.unit = MINUTES
166+
167+
async.event.handler.queue.capacity = 20000
168+
async.event.handler.num.workers = 5
169+
```
170+
171+
## OptimizelyFactory
172+
173+
The [`OptimizelyFactory`](https://github.com/optimizely/java-sdk/blob/master/core-httpclient-impl/src/main/java/com/optimizely/ab/OptimizelyFactory.java)
174+
included in this package provides basic utility to instantiate the Optimizely SDK
175+
with a minimal number of provided configuration options. Configuration properties are sourced from Java system properties,
176+
environment variables or from an `optimizely.properties` file, in that order. Not all configuration and initialization
177+
are captured via the `OptimizelyFactory`, for those use cases the resources can be built via their respective builder
178+
classes.
179+
180+
### Usage
181+
The SDK key is required to be provided at runtime either directly via the factory method:
182+
```Java
183+
Optimizely optimizely = OptimizelyFactory.newDefaultInstance(<<SDK_KEY>>);
184+
```
185+
186+
If the SDK is provided via a global property then the empty signature can be used:
187+
```Java
188+
Optimizely optimizely = OptimizelyFactory.newDefaultInstance();
189+
```

core-httpclient-impl/src/main/java/com/optimizely/ab/config/HttpProjectConfigManager.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,11 +241,15 @@ public HttpProjectConfigManager build(boolean defer) {
241241
if (period <= 0) {
242242
logger.warn("Invalid polling interval {}, {}. Defaulting to {}, {}",
243243
period, timeUnit, DEFAULT_POLLING_DURATION, DEFAULT_POLLING_UNIT);
244+
period = DEFAULT_POLLING_DURATION;
245+
timeUnit = DEFAULT_POLLING_UNIT;
244246
}
245247

246248
if (blockingTimeoutPeriod <= 0) {
247249
logger.warn("Invalid polling interval {}, {}. Defaulting to {}, {}",
248250
blockingTimeoutPeriod, blockingTimeoutUnit, DEFAULT_BLOCKING_DURATION, DEFAULT_BLOCKING_UNIT);
251+
blockingTimeoutPeriod = DEFAULT_BLOCKING_DURATION;
252+
blockingTimeoutUnit = DEFAULT_BLOCKING_UNIT;
249253
}
250254

251255
if (httpClient == null) {

core-httpclient-impl/src/test/java/com/optimizely/ab/config/HttpProjectConfigManagerTest.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,11 @@ public void tearDown() {
8282
}
8383

8484
projectConfigManager.close();
85+
86+
System.clearProperty("optimizely." + HttpProjectConfigManager.CONFIG_BLOCKING_UNIT);
87+
System.clearProperty("optimizely." + HttpProjectConfigManager.CONFIG_BLOCKING_DURATION);
88+
System.clearProperty("optimizely." + HttpProjectConfigManager.CONFIG_POLLING_UNIT);
89+
System.clearProperty("optimizely." + HttpProjectConfigManager.CONFIG_POLLING_DURATION);
8590
}
8691

8792
@Test
@@ -236,6 +241,7 @@ public void testGetDatafileHttpResponse5XX() throws Exception {
236241
projectConfigManager.getDatafileFromResponse(getResponse);
237242
}
238243

244+
@Test
239245
public void testInvalidPayload() throws Exception {
240246
reset(mockHttpClient);
241247
CloseableHttpResponse invalidPayloadResponse = mock(CloseableHttpResponse.class);
@@ -257,6 +263,35 @@ public void testInvalidPayload() throws Exception {
257263
assertNull(projectConfigManager.getConfig());
258264
}
259265

266+
@Test
267+
public void testInvalidPollingIntervalFromSystemProperties() throws Exception {
268+
System.setProperty("optimizely." + HttpProjectConfigManager.CONFIG_POLLING_DURATION, "-1");
269+
projectConfigManager = builder()
270+
.withOptimizelyHttpClient(mockHttpClient)
271+
.withSdkKey("sdk-key")
272+
.build();
273+
}
274+
275+
@Test
276+
public void testInvalidBlockingIntervalFromSystemProperties() throws Exception {
277+
reset(mockHttpClient);
278+
CloseableHttpResponse invalidPayloadResponse = mock(CloseableHttpResponse.class);
279+
StatusLine statusLine = mock(StatusLine.class);
280+
281+
when(statusLine.getStatusCode()).thenReturn(200);
282+
when(invalidPayloadResponse.getStatusLine()).thenReturn(statusLine);
283+
when(invalidPayloadResponse.getEntity()).thenReturn(new StringEntity("I am an invalid response!"));
284+
285+
when(mockHttpClient.execute(any(HttpGet.class)))
286+
.thenReturn(invalidPayloadResponse);
287+
288+
System.setProperty("optimizely." + HttpProjectConfigManager.CONFIG_BLOCKING_DURATION, "-1");
289+
projectConfigManager = builder()
290+
.withOptimizelyHttpClient(mockHttpClient)
291+
.withSdkKey("sdk-key")
292+
.build();
293+
}
294+
260295
@Test
261296
@Ignore
262297
public void testBasicFetch() throws Exception {

0 commit comments

Comments
 (0)