Skip to content

Commit 553883d

Browse files
authored
feat: Enable gzip option for sending events (#66)
I created a local maven package and imported it into the server SDK and verified the contract tests pass with and without gzip enabled.
1 parent 658d75a commit 553883d

File tree

2 files changed

+75
-4
lines changed

2 files changed

+75
-4
lines changed

lib/shared/internal/src/main/java/com/launchdarkly/sdk/internal/events/DefaultEventSender.java

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import com.launchdarkly.sdk.internal.http.HttpHelpers;
66
import com.launchdarkly.sdk.internal.http.HttpProperties;
77

8+
import java.io.ByteArrayOutputStream;
89
import java.io.IOException;
910
import java.net.URI;
1011
import java.nio.charset.Charset;
@@ -13,6 +14,7 @@
1314
import java.util.Date;
1415
import java.util.Locale;
1516
import java.util.UUID;
17+
import java.util.zip.GZIPOutputStream;
1618

1719
import static com.launchdarkly.sdk.internal.http.HttpErrors.checkIfErrorIsRecoverableAndLog;
1820
import static com.launchdarkly.sdk.internal.http.HttpErrors.httpErrorDescription;
@@ -55,13 +57,24 @@ public final class DefaultEventSender implements EventSender {
5557
Locale.US); // server dates as defined by RFC-822/RFC-1123 use English day/month names
5658
private static final Object HTTP_DATE_FORMAT_LOCK = new Object(); // synchronize on this because DateFormat isn't thread-safe
5759

60+
private static class CompressionResult<T> {
61+
final T data;
62+
final boolean wasCompressed;
63+
64+
CompressionResult(T data, boolean wasCompressed) {
65+
this.data = data;
66+
this.wasCompressed = wasCompressed;
67+
}
68+
}
69+
5870
private final OkHttpClient httpClient;
5971
private final boolean shouldCloseHttpClient;
6072
private final Headers baseHeaders;
6173
private final String analyticsRequestPath;
6274
private final String diagnosticRequestPath;
6375
final long retryDelayMillis; // visible for testing
6476
private final LDLogger logger;
77+
private final boolean enableGzipCompression;
6578

6679
/**
6780
* Creates an instance.
@@ -70,13 +83,15 @@ public final class DefaultEventSender implements EventSender {
7083
* @param analyticsRequestPath the request path for posting analytics events
7184
* @param diagnosticRequestPath the request path for posting diagnostic events
7285
* @param retryDelayMillis retry delay, or zero to use the default
86+
* @param enableGzipCompression whether to enable gzip compression
7387
* @param logger the logger
7488
*/
7589
public DefaultEventSender(
7690
HttpProperties httpProperties,
7791
String analyticsRequestPath,
7892
String diagnosticRequestPath,
7993
long retryDelayMillis,
94+
boolean enableGzipCompression,
8095
LDLogger logger
8196
) {
8297
if (httpProperties.getSharedHttpClient() == null) {
@@ -87,6 +102,7 @@ public DefaultEventSender(
87102
shouldCloseHttpClient = false;
88103
}
89104
this.logger = logger;
105+
this.enableGzipCompression = enableGzipCompression;
90106

91107
this.baseHeaders = httpProperties.toHeadersBuilder()
92108
.add("Content-Type", "application/json")
@@ -115,6 +131,24 @@ public Result sendDiagnosticEvent(byte[] data, URI eventsBaseUri) {
115131
return sendEventData(true, data, 1, eventsBaseUri);
116132
}
117133

134+
private CompressionResult<byte[]> compressData(byte[] data) {
135+
if (!enableGzipCompression) {
136+
return new CompressionResult<>(data, false);
137+
}
138+
139+
try {
140+
ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
141+
try (GZIPOutputStream gzipStream = new GZIPOutputStream(byteStream)) {
142+
gzipStream.write(data);
143+
}
144+
byte[] compressedData = byteStream.toByteArray();
145+
return new CompressionResult<>(compressedData, true);
146+
} catch (IOException e) {
147+
logger.warn("Failed to compress event data, falling back to uncompressed: {}", e.toString());
148+
return new CompressionResult<>(data, false);
149+
}
150+
}
151+
118152
private Result sendEventData(boolean isDiagnostic, byte[] data, int eventCount, URI eventsBaseUri) {
119153
if (data == null || data.length == 0) {
120154
// DefaultEventProcessor won't normally pass us an empty payload, but if it does, don't bother sending
@@ -137,10 +171,15 @@ private Result sendEventData(boolean isDiagnostic, byte[] data, int eventCount,
137171
}
138172

139173
URI uri = HttpHelpers.concatenateUriPath(eventsBaseUri, path);
140-
Headers headers = headersBuilder.build();
141-
RequestBody body = RequestBody.create(data, JSON_CONTENT_TYPE);
174+
CompressionResult<byte[]> compressionResult = compressData(data);
175+
RequestBody body = RequestBody.create(compressionResult.data, JSON_CONTENT_TYPE);
142176
boolean mustShutDown = false;
143177

178+
if (compressionResult.wasCompressed) {
179+
headersBuilder.add("Content-Encoding", "gzip");
180+
}
181+
182+
Headers headers = headersBuilder.build();
144183
logger.debug("Posting {} to {} with payload: {}", description, uri,
145184
LogValues.defer(new LazilyPrintedUtf8Data(data)));
146185

lib/shared/internal/src/test/java/com/launchdarkly/sdk/internal/events/DefaultEventSenderTest.java

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ private EventSender makeEventSender() {
5151
}
5252

5353
private EventSender makeEventSender(HttpProperties httpProperties) {
54-
return new DefaultEventSender(httpProperties, null, null, BRIEF_RETRY_DELAY_MILLIS, testLogger);
54+
return new DefaultEventSender(httpProperties, null, null, BRIEF_RETRY_DELAY_MILLIS, false, testLogger);
5555
}
5656

5757
@Test
@@ -92,7 +92,7 @@ public void diagnosticDataIsDelivered() throws Exception {
9292
public void customRequestPaths() throws Exception {
9393
try (HttpServer server = HttpServer.start(eventsSuccessResponse())) {
9494
try (EventSender es = new DefaultEventSender(HttpProperties.defaults(),
95-
"/custom/path/a", "/custom/path/d", BRIEF_RETRY_DELAY_MILLIS, testLogger)) {
95+
"/custom/path/a", "/custom/path/d", BRIEF_RETRY_DELAY_MILLIS, false, testLogger)) {
9696
EventSender.Result result = es.sendAnalyticsEvents(FAKE_DATA_BYTES, 1, server.getUri());
9797
assertTrue(result.isSuccess());
9898
result = es.sendDiagnosticEvent(FAKE_DATA_BYTES, server.getUri());
@@ -356,6 +356,38 @@ public void nothingIsSentForEmptyData() throws Exception {
356356
}
357357
}
358358

359+
@Test
360+
public void gzipCompressionIsEnabledWhenConfigured() throws Exception {
361+
try (HttpServer server = HttpServer.start(eventsSuccessResponse())) {
362+
try (EventSender es = new DefaultEventSender(HttpProperties.defaults(), null, null, BRIEF_RETRY_DELAY_MILLIS, true, testLogger)) {
363+
EventSender.Result result = es.sendAnalyticsEvents(FAKE_DATA_BYTES, 1, server.getUri());
364+
365+
assertTrue(result.isSuccess());
366+
assertFalse(result.isMustShutDown());
367+
}
368+
369+
RequestInfo req = server.getRecorder().requireRequest();
370+
assertThat(req.getHeader("content-type"), equalToIgnoringCase("application/json; charset=utf-8"));
371+
assertThat(req.getHeader("content-encoding"), equalToIgnoringCase("gzip"));
372+
}
373+
}
374+
375+
@Test
376+
public void gzipCompressionIsDisabledByDefault() throws Exception {
377+
try (HttpServer server = HttpServer.start(eventsSuccessResponse())) {
378+
try (EventSender es = makeEventSender()) {
379+
EventSender.Result result = es.sendAnalyticsEvents(FAKE_DATA_BYTES, 1, server.getUri());
380+
381+
assertTrue(result.isSuccess());
382+
assertFalse(result.isMustShutDown());
383+
}
384+
385+
RequestInfo req = server.getRecorder().requireRequest();
386+
assertThat(req.getHeader("content-type"), equalToIgnoringCase("application/json; charset=utf-8"));
387+
assertThat(req.getHeader("content-encoding"), not(equalToIgnoringCase("gzip")));
388+
}
389+
}
390+
359391
private void testUnrecoverableHttpError(int status) throws Exception {
360392
Handler errorResponse = Handlers.status(status);
361393

0 commit comments

Comments
 (0)