From 30637316732e9bdafb20353ba3a74b02f44dff03 Mon Sep 17 00:00:00 2001 From: Gavin Whelan Date: Tue, 14 Jan 2020 23:35:50 +0000 Subject: [PATCH] Add X-LaunchDarkly-Payload-ID header on event post. (#169) --- .../client/DefaultEventProcessor.java | 6 ++- .../client/DefaultEventProcessorTest.java | 46 +++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/launchdarkly/client/DefaultEventProcessor.java b/src/main/java/com/launchdarkly/client/DefaultEventProcessor.java index 5aee5efa0..56fbd1b16 100644 --- a/src/main/java/com/launchdarkly/client/DefaultEventProcessor.java +++ b/src/main/java/com/launchdarkly/client/DefaultEventProcessor.java @@ -15,6 +15,7 @@ import java.util.Date; import java.util.List; import java.util.Random; +import java.util.UUID; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.Executors; @@ -42,6 +43,7 @@ final class DefaultEventProcessor implements EventProcessor { private static final Logger logger = LoggerFactory.getLogger(DefaultEventProcessor.class); private static final String EVENT_SCHEMA_HEADER = "X-LaunchDarkly-Event-Schema"; private static final String EVENT_SCHEMA_VERSION = "3"; + private static final String EVENT_PAYLOAD_ID_HEADER = "X-LaunchDarkly-Payload-ID"; private final BlockingQueue inbox; private final ScheduledExecutorService scheduler; @@ -542,7 +544,8 @@ void stop() { private void postEvents(String json, int outputEventCount) { String uriStr = config.eventsURI.toString() + "/bulk"; - + String eventPayloadId = UUID.randomUUID().toString(); + logger.debug("Posting {} event(s) to {} with payload: {}", outputEventCount, uriStr, json); @@ -558,6 +561,7 @@ private void postEvents(String json, int outputEventCount) { .post(RequestBody.create(MediaType.parse("application/json; charset=utf-8"), json)) .addHeader("Content-Type", "application/json") .addHeader(EVENT_SCHEMA_HEADER, EVENT_SCHEMA_VERSION) + .addHeader(EVENT_PAYLOAD_ID_HEADER, eventPayloadId) .build(); long startTime = System.currentTimeMillis(); diff --git a/src/test/java/com/launchdarkly/client/DefaultEventProcessorTest.java b/src/test/java/com/launchdarkly/client/DefaultEventProcessorTest.java index ce05c62fe..05e117819 100644 --- a/src/test/java/com/launchdarkly/client/DefaultEventProcessorTest.java +++ b/src/test/java/com/launchdarkly/client/DefaultEventProcessorTest.java @@ -11,6 +11,7 @@ import java.text.SimpleDateFormat; import java.util.Date; +import java.util.UUID; import java.util.concurrent.TimeUnit; import static com.launchdarkly.client.TestHttpUtil.httpsServerWithSelfSignedCert; @@ -22,6 +23,7 @@ import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.nullValue; import static org.junit.Assert.assertEquals; @@ -485,6 +487,50 @@ public void eventSchemaIsSent() throws Exception { } } + @Test + public void eventPayloadIdIsSent() throws Exception { + Event e = EventFactory.DEFAULT.newIdentifyEvent(user); + + try (MockWebServer server = makeStartedServer(eventsSuccessResponse())) { + try (DefaultEventProcessor ep = new DefaultEventProcessor(SDK_KEY, baseConfig(server).build())) { + ep.sendEvent(e); + } + + RecordedRequest req = server.takeRequest(); + String payloadHeaderValue = req.getHeader("X-LaunchDarkly-Payload-ID"); + assertThat(payloadHeaderValue, notNullValue(String.class)); + assertThat(UUID.fromString(payloadHeaderValue), notNullValue(UUID.class)); + } + } + + @Test + public void eventPayloadIdReusedOnRetry() throws Exception { + MockResponse errorResponse = new MockResponse().setResponseCode(429); + Event e = EventFactory.DEFAULT.newIdentifyEvent(user); + + try (MockWebServer server = makeStartedServer(errorResponse, eventsSuccessResponse(), eventsSuccessResponse())) { + try (DefaultEventProcessor ep = new DefaultEventProcessor(SDK_KEY, baseConfig(server).build())) { + ep.sendEvent(e); + ep.flush(); + // Necessary to ensure the retry occurs before the second request for test assertion ordering + ep.waitUntilInactive(); + ep.sendEvent(e); + } + + // Failed response request + RecordedRequest req = server.takeRequest(0, TimeUnit.SECONDS); + String payloadId = req.getHeader("X-LaunchDarkly-Payload-ID"); + // Retry request has same payload ID as failed request + req = server.takeRequest(0, TimeUnit.SECONDS); + String retryId = req.getHeader("X-LaunchDarkly-Payload-ID"); + assertThat(retryId, equalTo(payloadId)); + // Second request has different payload ID from first request + req = server.takeRequest(0, TimeUnit.SECONDS); + payloadId = req.getHeader("X-LaunchDarkly-Payload-ID"); + assertThat(retryId, not(equalTo(payloadId))); + } + } + @Test public void http400ErrorIsRecoverable() throws Exception { testRecoverableHttpError(400);