diff --git a/README.md b/README.md index d84e927dd..bcae8eed8 100644 --- a/README.md +++ b/README.md @@ -464,7 +464,7 @@ The `UnleashConfig$Builder` class (created via `UnleashConfig.builder()`) expose | `enableProxyAuthenticationByJvmProperties` | Enable support for [using JVM properties for HTTP proxy authentication](#http-proxy-with-authentication). | No | `false` | | `environment` | The value to set for the Unleash context's `environment` property. **Not** the same as [Unleash's environments](https://docs.getunleash.io/reference/environments).| No | `null` | | `fallbackStrategy` | A strategy implementation that the client can use if it doesn't recognize the strategy type returned from the server. | No | `null` | -| `fetchTogglesInterval` | How often (in seconds) the client should check for toggle updates. Set to `0` if you want to only check once. | No | `10` | +| `fetchTogglesInterval` | How often (in seconds) the client should check for toggle updates. Set to `0` if you want to only check once. | No | `15` | | `instanceId` | A unique(-ish) identifier for your instance. Typically a hostname, pod id or something similar. Unleash uses this to separate metrics from the client SDKs with the same `appName`. | Yes | `null` | | `namePrefix` | If provided, the client will only fetch toggles whose name starts with the provided value. | No | `null` | | `projectName` | If provided, the client will only fetch toggles from the specified project. (This can also be achieved with an API token). | No | `null` | diff --git a/examples/cli-example/build.gradle.kts b/examples/cli-example/build.gradle.kts index 8b56b34da..48de7314e 100644 --- a/examples/cli-example/build.gradle.kts +++ b/examples/cli-example/build.gradle.kts @@ -13,6 +13,6 @@ repositories { } dependencies { - implementation("io.getunleash:unleash-client-java:7.1.0") - implementation("ch.qos.logback:logback-classic:1.4.6") + implementation("io.getunleash:unleash-client-java:10.0.2") + implementation("ch.qos.logback:logback-classic:1.4.12") } diff --git a/examples/cli-example/gradle/wrapper/gradle-wrapper.properties b/examples/cli-example/gradle/wrapper/gradle-wrapper.properties index 00e33edef..d6e308a63 100644 --- a/examples/cli-example/gradle/wrapper/gradle-wrapper.properties +++ b/examples/cli-example/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/examples/cli-example/src/main/java/io/getunleash/example/AdvancedConstraints.java b/examples/cli-example/src/main/java/io/getunleash/example/AdvancedConstraints.java index 0faa50f83..4f5163cd2 100644 --- a/examples/cli-example/src/main/java/io/getunleash/example/AdvancedConstraints.java +++ b/examples/cli-example/src/main/java/io/getunleash/example/AdvancedConstraints.java @@ -3,8 +3,6 @@ import io.getunleash.DefaultUnleash; import io.getunleash.Unleash; import io.getunleash.UnleashContext; -import io.getunleash.event.UnleashSubscriber; -import io.getunleash.repository.FeatureToggleResponse; import io.getunleash.util.UnleashConfig; public class AdvancedConstraints { @@ -15,38 +13,22 @@ public static void main(String[] args) throws InterruptedException { .customHttpHeader( "Authorization", getOrElse("UNLEASH_API_TOKEN", - "default:default.a45fede67f99b17f67312c93e00f448340e7af4ace2b0de2650f5a99")) - .unleashAPI(getOrElse("UNLEASH_API_URL", "http://localhost:3063/api")) + "*:development.25a06b75248528f8ca93ce179dcdd141aedfb632231e0d21fd8ff349")) + .unleashAPI(getOrElse("UNLEASH_API_URL", "https://app.unleash-hosted.com/demo/api")) .instanceId("java-example") .synchronousFetchOnInitialisation(true) - .sendMetricsInterval(30) - .subscriber( - new UnleashSubscriber() { - @Override - public void togglesFetched( - FeatureToggleResponse toggleResponse) { - System.out.println(toggleResponse); - System.out.println( - toggleResponse - .getToggleCollection() - .getFeatures() - .size()); - } - }) - .build(); + .sendMetricsInterval(30).build(); + Unleash unleash = new DefaultUnleash(config); + UnleashContext context = UnleashContext.builder() + .addProperty("semver", "1.5.2") + .build(); + UnleashContext smallerSemver = UnleashContext.builder() + .addProperty("semver", "1.1.0") + .build(); while (true) { - Thread.sleep(2000); - UnleashContext context = UnleashContext.builder() - .addProperty("semver", "1.5.2") - .build(); - System.out.println( - unleash.isEnabled("advanced.constraints", context)); // expect this to be true - UnleashContext smallerSemver = UnleashContext.builder() - .addProperty("semver", "1.1.0") - .build(); - System.out.println( - unleash.isEnabled("advanced.constraints", smallerSemver)); // expect this to be false + unleash.isEnabled("advanced.constraints", context); // expect this to be true + unleash.isEnabled("advanced.constraints", smallerSemver); // expect this to be false } } diff --git a/pom.xml b/pom.xml index e6ad8e57f..73e47892c 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 io.getunleash unleash-client-java - 10.0.2 + 10.1.0 2.0.13 diff --git a/src/main/java/io/getunleash/metric/ClientMetrics.java b/src/main/java/io/getunleash/metric/ClientMetrics.java index 8b6bfdf85..cbc371f5c 100644 --- a/src/main/java/io/getunleash/metric/ClientMetrics.java +++ b/src/main/java/io/getunleash/metric/ClientMetrics.java @@ -11,6 +11,7 @@ public class ClientMetrics implements UnleashEvent { private final String appName; private final String instanceId; + private final String connectionId; private final MetricsBucket bucket; private final String environment; private final String specVersion; @@ -22,6 +23,7 @@ public class ClientMetrics implements UnleashEvent { this.environment = config.getEnvironment(); this.appName = config.getAppName(); this.instanceId = config.getInstanceId(); + this.connectionId = config.getConnectionId(); this.bucket = bucket; this.specVersion = config.getClientSpecificationVersion(); this.platformName = System.getProperty("java.vm.name"); @@ -37,6 +39,10 @@ public String getInstanceId() { return instanceId; } + public String getConnectionId() { + return connectionId; + } + public MetricsBucket getBucket() { return bucket; } @@ -71,6 +77,12 @@ public void publishTo(UnleashSubscriber unleashSubscriber) { @Override public String toString() { - return "metrics:" + " appName=" + appName + " instanceId=" + instanceId; + return "metrics:" + + " appName=" + + appName + + " instanceId=" + + instanceId + + " connectionId=" + + connectionId; } } diff --git a/src/main/java/io/getunleash/metric/ClientRegistration.java b/src/main/java/io/getunleash/metric/ClientRegistration.java index 8af6f0781..accddd5a7 100644 --- a/src/main/java/io/getunleash/metric/ClientRegistration.java +++ b/src/main/java/io/getunleash/metric/ClientRegistration.java @@ -11,6 +11,7 @@ public class ClientRegistration implements UnleashEvent { private final String appName; private final String instanceId; + private final String connectionId; private final String sdkVersion; private final Set strategies; private final LocalDateTime started; @@ -26,6 +27,7 @@ public class ClientRegistration implements UnleashEvent { this.appName = config.getAppName(); this.instanceId = config.getInstanceId(); this.sdkVersion = config.getSdkVersion(); + this.connectionId = config.getConnectionId(); this.started = started; this.strategies = strategies; this.interval = config.getSendMetricsInterval(); @@ -43,6 +45,10 @@ public String getInstanceId() { return instanceId; } + public String getConnectionId() { + return connectionId; + } + public String getSdkVersion() { return sdkVersion; } diff --git a/src/main/java/io/getunleash/metric/DefaultHttpMetricsSender.java b/src/main/java/io/getunleash/metric/DefaultHttpMetricsSender.java index 4a2ace288..19ce5a045 100644 --- a/src/main/java/io/getunleash/metric/DefaultHttpMetricsSender.java +++ b/src/main/java/io/getunleash/metric/DefaultHttpMetricsSender.java @@ -15,6 +15,7 @@ import java.time.Instant; import java.time.LocalDateTime; import java.util.concurrent.atomic.AtomicLong; +import static io.getunleash.util.UnleashConfig.UNLEASH_INTERVAL; public class DefaultHttpMetricsSender implements MetricSender { @@ -82,6 +83,7 @@ private int post(URL url, Object o) throws UnleashException { connection.setRequestMethod("POST"); connection.setRequestProperty("Accept", "application/json"); connection.setRequestProperty("Content-Type", "application/json"); + connection.setRequestProperty(UNLEASH_INTERVAL, this.unleashConfig.getSendMetricsIntervalMillis()); UnleashConfig.setRequestProperties(connection, this.unleashConfig); connection.setUseCaches(false); connection.setDoInput(true); diff --git a/src/main/java/io/getunleash/metric/OkHttpMetricsSender.java b/src/main/java/io/getunleash/metric/OkHttpMetricsSender.java index ba4bd3399..1a1cbb414 100644 --- a/src/main/java/io/getunleash/metric/OkHttpMetricsSender.java +++ b/src/main/java/io/getunleash/metric/OkHttpMetricsSender.java @@ -19,6 +19,8 @@ import okhttp3.RequestBody; import okhttp3.Response; +import static io.getunleash.util.UnleashConfig.UNLEASH_INTERVAL; + public class OkHttpMetricsSender implements MetricSender { private final UnleashConfig config; private final MediaType JSON = @@ -88,7 +90,9 @@ public int sendMetrics(ClientMetrics metrics) { private int post(HttpUrl url, Object o) { RequestBody body = RequestBody.create(gson.toJson(o), JSON); - Request request = new Request.Builder().url(url).post(body).build(); + Request request = new Request.Builder().url(url).post(body) + .addHeader(UNLEASH_INTERVAL, config.getSendMetricsIntervalMillis()) + .build(); try (Response response = this.client.newCall(request).execute()) { return response.code(); } catch (IOException ioEx) { diff --git a/src/main/java/io/getunleash/repository/HttpFeatureFetcher.java b/src/main/java/io/getunleash/repository/HttpFeatureFetcher.java index ea3bd61ac..ab3a0fbc1 100644 --- a/src/main/java/io/getunleash/repository/HttpFeatureFetcher.java +++ b/src/main/java/io/getunleash/repository/HttpFeatureFetcher.java @@ -15,6 +15,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import static io.getunleash.util.UnleashConfig.UNLEASH_INTERVAL; + public class HttpFeatureFetcher implements FeatureFetcher { private static final Logger LOG = LoggerFactory.getLogger(HttpFeatureFetcher.class); private Optional etag = Optional.empty(); @@ -35,6 +37,7 @@ public ClientFeaturesResponse fetchFeatures() throws UnleashException { HttpURLConnection connection = null; try { connection = openConnection(this.toggleUrl); + connection.setRequestProperty(UNLEASH_INTERVAL, this.config.getFetchTogglesIntervalMillis()); connection.connect(); return getFeatureResponse(connection, true); diff --git a/src/main/java/io/getunleash/repository/OkHttpFeatureFetcher.java b/src/main/java/io/getunleash/repository/OkHttpFeatureFetcher.java index 281fd7bbe..d4f0d73e8 100644 --- a/src/main/java/io/getunleash/repository/OkHttpFeatureFetcher.java +++ b/src/main/java/io/getunleash/repository/OkHttpFeatureFetcher.java @@ -16,11 +16,15 @@ import okhttp3.Request; import okhttp3.Response; +import static io.getunleash.util.UnleashConfig.UNLEASH_INTERVAL; + public class OkHttpFeatureFetcher implements FeatureFetcher { private final HttpUrl toggleUrl; private final OkHttpClient client; + private final String interval; public OkHttpFeatureFetcher(UnleashConfig unleashConfig) { + this.interval = unleashConfig.getFetchTogglesIntervalMillis(); File tempDir = null; try { tempDir = Files.createTempDirectory("http_cache").toFile(); @@ -50,6 +54,7 @@ public OkHttpFeatureFetcher(UnleashConfig unleashConfig) { } public OkHttpFeatureFetcher(UnleashConfig unleashConfig, OkHttpClient client) { + this.interval = unleashConfig.getFetchTogglesIntervalMillis(); this.client = OkHttpClientConfigurer.configureInterceptor(unleashConfig, client); this.toggleUrl = Objects.requireNonNull( @@ -63,7 +68,9 @@ public OkHttpFeatureFetcher(UnleashConfig unleashConfig, OkHttpClient client) { @Override public ClientFeaturesResponse fetchFeatures() throws UnleashException { - Request request = new Request.Builder().url(toggleUrl).get().build(); + Request request = new Request.Builder().url(toggleUrl).get() + .addHeader(UNLEASH_INTERVAL, interval) + .build(); int code = 200; try (Response response = client.newCall(request).execute()) { if (response.isSuccessful()) { diff --git a/src/main/java/io/getunleash/util/UnleashConfig.java b/src/main/java/io/getunleash/util/UnleashConfig.java index 83e4d162c..030a5de72 100644 --- a/src/main/java/io/getunleash/util/UnleashConfig.java +++ b/src/main/java/io/getunleash/util/UnleashConfig.java @@ -28,6 +28,7 @@ public class UnleashConfig { public static final String UNLEASH_INSTANCE_ID_HEADER = "UNLEASH-INSTANCEID"; public static final String UNLEASH_CONNECTION_ID_HEADER = "UNLEASH-CONNECTION-ID"; + public static final String UNLEASH_INTERVAL = "UNLEASH-INTERVAL"; public static final String UNLEASH_APP_NAME_HEADER = "UNLEASH-APPNAME"; public static final String UNLEASH_SDK_HEADER = "UNLEASH-SDK"; @@ -216,7 +217,7 @@ public String getInstanceId() { return instanceId; } - String getConnectionId() { + public String getConnectionId() { return connectionId; } @@ -236,6 +237,14 @@ public long getFetchTogglesInterval() { return fetchTogglesInterval; } + public String getFetchTogglesIntervalMillis() { + return String.valueOf(fetchTogglesInterval * 1000); + } + public String getSendMetricsIntervalMillis() { + return String.valueOf(sendMetricsInterval * 1000); + } + + public Duration getFetchTogglesConnectTimeout() { return fetchTogglesConnectTimeout; } @@ -416,7 +425,7 @@ public static class Builder { private @Nullable String backupFile; private @Nullable String projectName; private @Nullable String namePrefix; - private long fetchTogglesInterval = 10; + private long fetchTogglesInterval = 15; private Duration fetchTogglesConnectTimeout = Duration.ofSeconds(10); diff --git a/src/test/java/io/getunleash/metric/DefaultHttpMetricsSenderTest.java b/src/test/java/io/getunleash/metric/DefaultHttpMetricsSenderTest.java index 7f8be65e8..2f65c71ed 100644 --- a/src/test/java/io/getunleash/metric/DefaultHttpMetricsSenderTest.java +++ b/src/test/java/io/getunleash/metric/DefaultHttpMetricsSenderTest.java @@ -9,6 +9,8 @@ import static com.github.tomakehurst.wiremock.client.WireMock.urlMatching; import static com.github.tomakehurst.wiremock.client.WireMock.verify; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; +import static io.getunleash.util.UnleashConfig.UNLEASH_CONNECTION_ID_HEADER; +import static io.getunleash.util.UnleashConfig.UNLEASH_INTERVAL; import com.github.tomakehurst.wiremock.junit5.WireMockExtension; import io.getunleash.engine.MetricsBucket; @@ -41,13 +43,14 @@ public void should_send_client_registration() throws URISyntaxException { UnleashConfig config = UnleashConfig.builder().appName("test-app").unleashAPI(uri).build(); DefaultHttpMetricsSender sender = new DefaultHttpMetricsSender(config); - sender.registerClient( - new ClientRegistration(config, LocalDateTime.now(), new HashSet())); + sender.registerClient(new ClientRegistration(config, LocalDateTime.now(), new HashSet<>())); verify( postRequestedFor(urlMatching("/client/register")) .withRequestBody(matching(".*appName.*")) .withRequestBody(matching(".*strategies.*")) + .withHeader( + UNLEASH_CONNECTION_ID_HEADER, matching(config.getConnectionId())) .withHeader("UNLEASH-APPNAME", matching("test-app"))); } @@ -70,6 +73,10 @@ public void should_send_client_metrics() throws URISyntaxException { postRequestedFor(urlMatching("/client/metrics")) .withRequestBody(matching(".*appName.*")) .withRequestBody(matching(".*bucket.*")) + .withHeader( + UNLEASH_INTERVAL, matching(config.getSendMetricsIntervalMillis())) + .withHeader( + UNLEASH_CONNECTION_ID_HEADER, matching(config.getConnectionId())) .withHeader("UNLEASH-APPNAME", matching("test-app"))); } @@ -81,7 +88,13 @@ public void should_handle_service_failure_when_sending_metrics() throws URISynta .willReturn(aResponse().withStatus(500))); URI uri = new URI("http://localhost:" + serverMock.getPort()); - UnleashConfig config = UnleashConfig.builder().appName("test-app").unleashAPI(uri).build(); + long metricsInterval = 0; + UnleashConfig config = + UnleashConfig.builder() + .appName("test-app") + .unleashAPI(uri) + .sendMetricsInterval(metricsInterval) + .build(); DefaultHttpMetricsSender sender = new DefaultHttpMetricsSender(config); MetricsBucket bucket = new MetricsBucket(Instant.now(), Instant.now(), null); @@ -92,6 +105,7 @@ public void should_handle_service_failure_when_sending_metrics() throws URISynta postRequestedFor(urlMatching("/client/metrics")) .withRequestBody(matching(".*appName.*")) .withRequestBody(matching(".*bucket.*")) + .withHeader(UNLEASH_INTERVAL, matching(String.valueOf(metricsInterval))) .withHeader("UNLEASH-APPNAME", matching("test-app"))); } } diff --git a/src/test/java/io/getunleash/metric/UnleashMetricServiceImplTest.java b/src/test/java/io/getunleash/metric/UnleashMetricServiceImplTest.java index a5e73a9c3..7f89da264 100644 --- a/src/test/java/io/getunleash/metric/UnleashMetricServiceImplTest.java +++ b/src/test/java/io/getunleash/metric/UnleashMetricServiceImplTest.java @@ -150,6 +150,7 @@ public void should_record_and_send_metrics() throws YggdrasilError { assertThat(clientMetrics.getAppName()).isEqualTo(config.getAppName()); assertThat(clientMetrics.getEnvironment()).isEqualTo(config.getEnvironment()); assertThat(clientMetrics.getInstanceId()).isEqualTo(config.getInstanceId()); + assertThat(clientMetrics.getConnectionId()).isEqualTo(config.getConnectionId()); assertThat(bucket.getStart()).isNotNull(); assertThat(bucket.getStop()).isNotNull(); assertThat(bucket.getToggles()).hasSize(2); @@ -192,6 +193,7 @@ public void should_record_and_send_variant_metrics() throws YggdrasilError { assertThat(clientMetrics.getAppName()).isEqualTo(config.getAppName()); assertThat(clientMetrics.getInstanceId()).isEqualTo(config.getInstanceId()); + assertThat(clientMetrics.getConnectionId()).isEqualTo(config.getConnectionId()); assertThat(bucket.getStart()).isNotNull(); assertThat(bucket.getStop()).isNotNull(); assertThat(bucket.getToggles()).hasSize(1); diff --git a/src/test/java/io/getunleash/repository/FeatureRepositoryTest.java b/src/test/java/io/getunleash/repository/FeatureRepositoryTest.java index 828628bd4..52ffcc757 100644 --- a/src/test/java/io/getunleash/repository/FeatureRepositoryTest.java +++ b/src/test/java/io/getunleash/repository/FeatureRepositoryTest.java @@ -248,8 +248,8 @@ public void should_increase_to_max_interval_when_code(int code) runner.assertThatFetchesAndReceives(ClientFeaturesResponse.Status.UNAVAILABLE, code); assertThat(featureRepository.getFailures()).isEqualTo(1); - assertThat(featureRepository.getSkips()).isEqualTo(30); - for (int i = 0; i < 30; i++) { + assertThat(featureRepository.getSkips()).isEqualTo(20); + for (int i = 0; i < 20; i++) { runner.assertThatSkipsNextRun(); } assertThat(featureRepository.getFailures()).isEqualTo(1); diff --git a/src/test/java/io/getunleash/util/UnleashConfigTest.java b/src/test/java/io/getunleash/util/UnleashConfigTest.java index e0d08e551..d0aa38417 100644 --- a/src/test/java/io/getunleash/util/UnleashConfigTest.java +++ b/src/test/java/io/getunleash/util/UnleashConfigTest.java @@ -148,7 +148,6 @@ public void should_add_client_identification_headers_to_connection() throws IOEx .appName(appName) .instanceId(instanceId) .unleashAPI(unleashAPI) - .customHttpHeader(UNLEASH_CONNECTION_ID_HEADER, "ignore") .build(); URL someUrl = new URL(unleashAPI + "/some/arbitrary/path"); @@ -157,7 +156,8 @@ public void should_add_client_identification_headers_to_connection() throws IOEx UnleashConfig.setRequestProperties(connection, unleashConfig); assertThat(connection.getRequestProperty(UNLEASH_APP_NAME_HEADER)).isEqualTo(appName); assertThat(connection.getRequestProperty(UNLEASH_INSTANCE_ID_HEADER)).isEqualTo(instanceId); - assertThat(connection.getRequestProperty(UNLEASH_CONNECTION_ID_HEADER)).hasSize(36); + assertThat(connection.getRequestProperty(UNLEASH_CONNECTION_ID_HEADER)) + .isEqualTo(unleashConfig.getConnectionId()); assertThat(connection.getRequestProperty(UNLEASH_SDK_HEADER)) .isEqualTo("unleash-client-java:development"); assertThat(connection.getRequestProperty("User-Agent")).isEqualTo(appName);