diff --git a/src/main/java/com/dynatrace/openkit/core/configuration/HTTPClientConfiguration.java b/src/main/java/com/dynatrace/openkit/core/configuration/HTTPClientConfiguration.java index 04a31fbc..2d838d3c 100644 --- a/src/main/java/com/dynatrace/openkit/core/configuration/HTTPClientConfiguration.java +++ b/src/main/java/com/dynatrace/openkit/core/configuration/HTTPClientConfiguration.java @@ -31,6 +31,7 @@ public class HTTPClientConfiguration { private final SSLTrustManager sslTrustManager; private final HttpRequestInterceptor httpRequestInterceptor; private final HttpResponseInterceptor httpResponseInterceptor; + private final long deviceID; private HTTPClientConfiguration(Builder builder) { this.baseURL = builder.baseURL; @@ -39,6 +40,7 @@ private HTTPClientConfiguration(Builder builder) { this.sslTrustManager = builder.sslTrustManager; this.httpRequestInterceptor = builder.httpRequestInterceptor; this.httpResponseInterceptor = builder.httpResponseInterceptor; + this.deviceID = builder.deviceID; } /** @@ -65,7 +67,8 @@ public static Builder modifyWith(OpenKitConfiguration openKitConfig) { .withSSLTrustManager(openKitConfig.getSSLTrustManager()) .withServerID(openKitConfig.getDefaultServerID()) .withHttpRequestInterceptor(openKitConfig.getHttpRequestInterceptor()) - .withHttpResponseInterceptor(openKitConfig.getHttpResponseInterceptor()); + .withHttpResponseInterceptor(openKitConfig.getHttpResponseInterceptor()) + .withDeviceID(openKitConfig.getDeviceID()); } /** @@ -81,7 +84,8 @@ public static Builder modifyWith(HTTPClientConfiguration httpClientConfig) { .withSSLTrustManager(httpClientConfig.getSSLTrustManager()) .withServerID(httpClientConfig.getServerID()) .withHttpRequestInterceptor(httpClientConfig.getHttpRequestInterceptor()) - .withHttpResponseInterceptor(httpClientConfig.getHttpResponseInterceptor()); + .withHttpResponseInterceptor(httpClientConfig.getHttpResponseInterceptor()) + .withDeviceID(httpClientConfig.getDeviceID()); } /** @@ -132,6 +136,11 @@ public HttpResponseInterceptor getHttpResponseInterceptor() { return httpResponseInterceptor; } + /** + * Returns the unique device identifier + */ + public long getDeviceID() { return deviceID; } + /** * Builder class for building {@link HTTPClientConfiguration}. */ @@ -143,6 +152,7 @@ public static final class Builder { private SSLTrustManager sslTrustManager = null; private HttpRequestInterceptor httpRequestInterceptor = null; private HttpResponseInterceptor httpResponseInterceptor = null; + private long deviceID; public Builder withBaseURL(String baseURL) { @@ -175,6 +185,11 @@ public Builder withHttpResponseInterceptor(HttpResponseInterceptor httpResponseI return this; } + public Builder withDeviceID(long deviceID) { + this.deviceID = deviceID; + return this; + } + public HTTPClientConfiguration build() { return new HTTPClientConfiguration(this); } diff --git a/src/main/java/com/dynatrace/openkit/protocol/Beacon.java b/src/main/java/com/dynatrace/openkit/protocol/Beacon.java index 82f425cb..a6841acd 100644 --- a/src/main/java/com/dynatrace/openkit/protocol/Beacon.java +++ b/src/main/java/com/dynatrace/openkit/protocol/Beacon.java @@ -839,7 +839,7 @@ public StatusResponse send(HTTPClientProvider provider, AdditionalQueryParameter } // send the request - response = httpClient.sendBeaconRequest(clientIPAddress, encodedBeacon, additionalParameters); + response = httpClient.sendBeaconRequest(clientIPAddress, encodedBeacon, additionalParameters, getSessionNumber()); if (response == null || response.isErroneousResponse()) { // error happened - but don't know what exactly // reset the previously retrieved chunk (restore it in internal cache) & retry another time diff --git a/src/main/java/com/dynatrace/openkit/protocol/HTTPClient.java b/src/main/java/com/dynatrace/openkit/protocol/HTTPClient.java index 192e867f..2789fa29 100644 --- a/src/main/java/com/dynatrace/openkit/protocol/HTTPClient.java +++ b/src/main/java/com/dynatrace/openkit/protocol/HTTPClient.java @@ -85,6 +85,7 @@ public String getRequestName() { private static final String QUERY_KEY_RESPONSE_TYPE = "resp"; private static final String QUERY_KEY_CONFIG_TIMESTAMP = "cts"; private static final String QUERY_KEY_NEW_SESSION = "ns"; + private static final String QUERY_KEY_SESSION_IDENTIFIER = "si"; // additional reserved characters for URL encoding private static final char[] QUERY_RESERVED_CHARACTERS = {'_'}; @@ -100,6 +101,7 @@ public String getRequestName() { private final String newSessionURL; private final int serverID; + private final long deviceID; private final SSLTrustManager sslTrustManager; @@ -118,6 +120,7 @@ public HTTPClient(Logger logger, HTTPClientConfiguration configuration) { sslTrustManager = configuration.getSSLTrustManager(); httpRequestInterceptor = configuration.getHttpRequestInterceptor(); httpResponseInterceptor = configuration.getHttpResponseInterceptor(); + deviceID = configuration.getDeviceID(); } // *** public methods *** @@ -143,8 +146,11 @@ public StatusResponse sendNewSessionRequest(AdditionalQueryParameters additional public StatusResponse sendBeaconRequest( String clientIPAddress, byte[] data, - AdditionalQueryParameters additionalParameters) { + AdditionalQueryParameters additionalParameters, + int sessionNumber) { String url = appendAdditionalQueryParameters(monitorURL, additionalParameters); + url = appendSessionIdentifierParameter(url, String.valueOf(sessionNumber), String.valueOf(deviceID)); + StatusResponse response = sendRequest(RequestType.BEACON, url, clientIPAddress, data, "POST"); return response == null ? StatusResponse.createErrorResponse(logger, Integer.MAX_VALUE) @@ -371,6 +377,13 @@ private static String appendAdditionalQueryParameters(String baseUrl, Additional return builder.toString(); } + private static String appendSessionIdentifierParameter(String baseUrl, String sessionNumber, String deviceID) { + StringBuilder builder = new StringBuilder(baseUrl); + appendQueryParam(builder, QUERY_KEY_SESSION_IDENTIFIER, deviceID + "_" + sessionNumber); + + return builder.toString(); + } + // helper method for appending query parameters private static void appendQueryParam(StringBuilder urlBuilder, String key, String value) { urlBuilder.append('&'); diff --git a/src/test/java/com/dynatrace/openkit/core/communication/BeaconSendingContextTest.java b/src/test/java/com/dynatrace/openkit/core/communication/BeaconSendingContextTest.java index f48ce4dc..e64a5020 100644 --- a/src/test/java/com/dynatrace/openkit/core/communication/BeaconSendingContextTest.java +++ b/src/test/java/com/dynatrace/openkit/core/communication/BeaconSendingContextTest.java @@ -46,8 +46,7 @@ import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.sameInstance; import static org.junit.Assert.assertThat; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.isA; +import static org.mockito.Matchers.*; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; @@ -80,7 +79,7 @@ public void setUp() { 200, Collections.>emptyMap() ); - when(httpClient.sendBeaconRequest(isA(String.class), any(byte[].class), any(AdditionalQueryParameters.class))) + when(httpClient.sendBeaconRequest(isA(String.class), any(byte[].class), any(AdditionalQueryParameters.class), anyInt())) .thenReturn(statusResponse); httpClientProvider = mock(HTTPClientProvider.class); diff --git a/src/test/java/com/dynatrace/openkit/core/configuration/HttpClientConfigurationTest.java b/src/test/java/com/dynatrace/openkit/core/configuration/HttpClientConfigurationTest.java index 3cdd9924..7c90b99d 100644 --- a/src/test/java/com/dynatrace/openkit/core/configuration/HttpClientConfigurationTest.java +++ b/src/test/java/com/dynatrace/openkit/core/configuration/HttpClientConfigurationTest.java @@ -134,6 +134,22 @@ public void instanceFromOpenKitConfigTakesOverHttpResponseInterceptor() { assertThat(obtained, is(equalTo(httpResponseInterceptor))); } + @Test + public void instanceFromOpenKitConfigTakesOverDeviceId() { + // given + OpenKitConfiguration openKitConfig = mock(OpenKitConfiguration.class); + when(openKitConfig.getDeviceID()).thenReturn(42l); + + HTTPClientConfiguration target = HTTPClientConfiguration.from(openKitConfig); + + // when + long obtained = target.getDeviceID(); + + // then + verify(openKitConfig, times(1)).getDeviceID(); + assertThat(obtained, is(equalTo(42l))); + } + @Test public void builderFromHttpClientConfigTakesOverBaseUrl() { // given @@ -224,6 +240,23 @@ public void builderFromHttpClientConfigTakesHttpResponseInterceptor() { assertThat(target.getHttpResponseInterceptor(), is(sameInstance(httpResponseInterceptor))); } + @Test + public void builderFromHttpClientConfigTakesDeviceId() { + // given + HTTPClientConfiguration httpConfig = mock(HTTPClientConfiguration.class); + when(httpConfig.getDeviceID()).thenReturn(42l); + + // when + HTTPClientConfiguration target = HTTPClientConfiguration.modifyWith(httpConfig).build(); + + // when + long obtained = target.getDeviceID(); + + // then + verify(httpConfig, times(1)).getDeviceID(); + assertThat(obtained, is(equalTo(42l))); + } + @Test public void emptyBuilderCreatesEmptyInstance() { // given @@ -237,6 +270,7 @@ public void emptyBuilderCreatesEmptyInstance() { assertThat(obtained.getApplicationID(), is(nullValue())); assertThat(obtained.getSSLTrustManager(), is(nullValue())); assertThat(obtained.getServerID(), is(equalTo(-1))); + assertThat(obtained.getDeviceID(), is(equalTo(0l))); } @Test @@ -318,4 +352,17 @@ public void builderWithHttpResponseInterceptorPropagatesToInstance() { // then assertThat(obtained.getHttpResponseInterceptor(), is(sameInstance(httpResponseInterceptor))); } + + @Test + public void builderWithDeviceIdPropagatesToInstance() { + // given + HTTPClientConfiguration.Builder target = new HTTPClientConfiguration.Builder() + .withDeviceID(42l); + + // when + HTTPClientConfiguration obtained = target.build(); + + // then + assertThat(obtained.getDeviceID(), is(equalTo(42l))); + } } diff --git a/src/test/java/com/dynatrace/openkit/protocol/BeaconTest.java b/src/test/java/com/dynatrace/openkit/protocol/BeaconTest.java index ed34a6c4..9ed1f6a3 100644 --- a/src/test/java/com/dynatrace/openkit/protocol/BeaconTest.java +++ b/src/test/java/com/dynatrace/openkit/protocol/BeaconTest.java @@ -217,7 +217,7 @@ public void testCreateInstanceWithInvalidIpAddress() { // then ArgumentCaptor ipCaptor = ArgumentCaptor.forClass(String.class); verify(httpClient, times(1)) - .sendBeaconRequest(ipCaptor.capture(), any(byte[].class), eq(mockAdditionalParameters)); + .sendBeaconRequest(ipCaptor.capture(), any(byte[].class), eq(mockAdditionalParameters), anyInt()); String capturedIp = ipCaptor.getValue(); assertThat(capturedIp, is(nullValue())); @@ -248,7 +248,7 @@ public void testCreateInstanceWithNullIpAddress() { // then ArgumentCaptor ipCaptor = ArgumentCaptor.forClass(String.class); verify(httpClient, times(1)) - .sendBeaconRequest(ipCaptor.capture(), any(byte[].class), eq(mockAdditionalParameters)); + .sendBeaconRequest(ipCaptor.capture(), any(byte[].class), eq(mockAdditionalParameters), anyInt()); String capturedIp = ipCaptor.getValue(); assertThat(capturedIp, is(nullValue())); @@ -3726,7 +3726,7 @@ public void sendValidData() { responseCode, Collections.>emptyMap() ); - when(httpClient.sendBeaconRequest(any(String.class), any(byte[].class), any(AdditionalQueryParameters.class))) + when(httpClient.sendBeaconRequest(any(String.class), any(byte[].class), any(AdditionalQueryParameters.class), anyInt())) .thenReturn(successResponse); when(httpClientProvider.createClient(any(HTTPClientConfiguration.class))).thenReturn(httpClient); @@ -3737,7 +3737,7 @@ public void sendValidData() { // then assertThat(response, notNullValue()); assertThat(response.getResponseCode(), is(responseCode)); - verify(httpClient, times(1)).sendBeaconRequest(eq(ipAddress), any(byte[].class), eq(mockAdditionalParameters)); + verify(httpClient, times(1)).sendBeaconRequest(eq(ipAddress), any(byte[].class), eq(mockAdditionalParameters), anyInt()); } @Test @@ -3753,7 +3753,7 @@ public void sendDataAndFakeErrorResponse() { HTTPClient httpClient = mock(HTTPClient.class); int responseCode = 418; StatusResponse errorResponse = StatusResponse.createErrorResponse(mockLogger, responseCode); - when(httpClient.sendBeaconRequest(any(String.class), any(byte[].class), any(AdditionalQueryParameters.class))) + when(httpClient.sendBeaconRequest(any(String.class), any(byte[].class), any(AdditionalQueryParameters.class), anyInt())) .thenReturn(errorResponse); when(httpClientProvider.createClient(any(HTTPClientConfiguration.class))).thenReturn(httpClient); @@ -3764,7 +3764,7 @@ public void sendDataAndFakeErrorResponse() { // then assertThat(response, notNullValue()); assertThat(response.getResponseCode(), is(responseCode)); - verify(httpClient, times(1)).sendBeaconRequest(eq(ipAddress), any(byte[].class), eq(mockAdditionalParameters)); + verify(httpClient, times(1)).sendBeaconRequest(eq(ipAddress), any(byte[].class), eq(mockAdditionalParameters), anyInt()); } @Test @@ -3831,7 +3831,7 @@ public void sendCanHandleMultipleChunks() { responseCode, Collections.>emptyMap() ); - when(httpClient.sendBeaconRequest(any(String.class), any(byte[].class), any(AdditionalQueryParameters.class))) + when(httpClient.sendBeaconRequest(any(String.class), any(byte[].class), any(AdditionalQueryParameters.class), anyInt())) .thenReturn(firstResponse, secondResponse); when(httpClientProvider.createClient(any(HTTPClientConfiguration.class))).thenReturn(httpClient); @@ -3844,7 +3844,7 @@ public void sendCanHandleMultipleChunks() { assertThat(response, is(notNullValue())); assertThat(response, is(sameInstance(secondResponse))); - verify(httpClient, times(2)).sendBeaconRequest(any(String.class), any(byte[].class), eq(mockAdditionalParameters)); + verify(httpClient, times(2)).sendBeaconRequest(any(String.class), any(byte[].class), eq(mockAdditionalParameters), anyInt()); verify(mockBeaconCache, times(1)).prepareDataForSending(any(BeaconKey.class)); verify(mockBeaconCache, times(3)).hasDataForSending(any(BeaconKey.class)); diff --git a/src/test/java/com/dynatrace/openkit/protocol/HTTPClientTest.java b/src/test/java/com/dynatrace/openkit/protocol/HTTPClientTest.java index 170d15b9..5fd9f86c 100644 --- a/src/test/java/com/dynatrace/openkit/protocol/HTTPClientTest.java +++ b/src/test/java/com/dynatrace/openkit/protocol/HTTPClientTest.java @@ -631,7 +631,7 @@ public void sendBeaconRequestDoesNotReturnNull() throws UnsupportedEncodingExcep doReturn(null).when(target).sendRequest(Mockito.any(RequestType.class), anyString(), anyString(), Mockito.any(byte[].class), anyString()); // when - StatusResponse obtained = target.sendBeaconRequest("127.0.0.1", "".getBytes(CHARSET), mockAdditionalParameters); + StatusResponse obtained = target.sendBeaconRequest("127.0.0.1", "".getBytes(CHARSET), mockAdditionalParameters, 1); // then assertThat(obtained, is(notNullValue())); @@ -656,6 +656,29 @@ public void sendStatusRequestDoesNotAppendIfAdditionalQueryParametersAreNull() { assertThat(urlCaptor.getValue(), is(expectedUrl.toString())); } + @Test + public void sendStatusRequestDoesNotAppendSessionIdentifierIfNull() { + // given + long timestamp = 1234; + AdditionalQueryParameters additionalQueryParameters = mock(AdditionalQueryParameters.class); + when(additionalQueryParameters.getConfigurationTimestamp()).thenReturn(timestamp); + + ArgumentCaptor urlCaptor = ArgumentCaptor.forClass(String.class); + + HTTPClient target = spy(new HTTPClient(logger, configuration)); + doReturn(null).when(target).sendRequest(any(RequestType.class), anyString(), anyString(), any(byte[].class), anyString()); + + // when + target.sendStatusRequest(additionalQueryParameters); + + // then + verify(target, times(1)).sendRequest(any(RequestType.class), urlCaptor.capture(), anyString(), any(byte[].class), anyString()); + + StringBuilder expectedUrl = initializeBaseUrl(); + appendUrlParameter(expectedUrl, "cts", String.valueOf(timestamp)); + assertThat(urlCaptor.getValue(), is(expectedUrl.toString())); + } + @Test public void sendStatusRequestAppendsAdditionalQueryParameters() { // given @@ -731,12 +754,13 @@ public void sendBeaconRequestDoesNotAppendIfAdditionalQueryParametersAreNull() { doReturn(null).when(target).sendRequest(any(RequestType.class), anyString(), anyString(), any(byte[].class), anyString()); // when - target.sendBeaconRequest(null, null, null); + target.sendBeaconRequest(null, null, null, 1); // then verify(target, times(1)).sendRequest(any(RequestType.class), urlCaptor.capture(), anyString(), any(byte[].class), anyString()); StringBuilder expectedUrl = initializeBaseUrl(); + appendUrlParameter(expectedUrl, "si", "0%5F1"); assertThat(urlCaptor.getValue(), is(expectedUrl.toString())); } @@ -753,13 +777,14 @@ public void sendBeaconRequestAppendsAdditionalQueryParameters() { doReturn(null).when(target).sendRequest(any(RequestType.class), anyString(), anyString(), any(byte[].class), anyString()); // when - target.sendBeaconRequest(null, null, additionalQueryParameters); + target.sendBeaconRequest(null, null, additionalQueryParameters, 1); // then verify(target, times(1)).sendRequest(any(RequestType.class), urlCaptor.capture(), anyString(), any(byte[].class), anyString()); StringBuilder expectedUrl = initializeBaseUrl(); appendUrlParameter(expectedUrl, "cts", String.valueOf(timestamp)); + appendUrlParameter(expectedUrl, "si", "0%5F1"); assertThat(urlCaptor.getValue(), is(expectedUrl.toString())); }