Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Added the possibility to add a custom startupExceptionHandler #227

Merged
merged 1 commit into from
Nov 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 14 additions & 4 deletions src/main/java/io/getunleash/repository/FeatureRepository.java
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,15 @@ private void initCollections(UnleashScheduledExecutor executor) {
}

if (unleashConfig.isSynchronousFetchOnInitialisation()) {
updateFeatures(e -> {
throw e;
}).run();
if (this.unleashConfig.getStartupExceptionHandler() != null) {
updateFeatures(this.unleashConfig.getStartupExceptionHandler()).run();
} else {
updateFeatures(
e -> {
throw e;
})
.run();
}
}

if (!unleashConfig.isDisablePolling()) {
Expand Down Expand Up @@ -128,7 +134,11 @@ private Runnable updateFeatures(final Consumer<UnleashException> handler) {
featureBackupHandler.write(featureCollection);
} else if (response.getStatus() == ClientFeaturesResponse.Status.UNAVAILABLE) {
if (!ready && unleashConfig.isSynchronousFetchOnInitialisation()) {
throw new UnleashException(String.format("Could not initialize Unleash, got response code %d", response.getHttpStatusCode()), null);
throw new UnleashException(
String.format(
"Could not initialize Unleash, got response code %d",
response.getHttpStatusCode()),
null);
}
if (ready) {
throttler.handleHttpErrorCodes(response.getHttpStatusCode());
Expand Down
43 changes: 32 additions & 11 deletions src/main/java/io/getunleash/util/UnleashConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import io.getunleash.CustomHttpHeadersProvider;
import io.getunleash.DefaultCustomHttpHeadersProviderImpl;
import io.getunleash.UnleashContextProvider;
import io.getunleash.UnleashException;
import io.getunleash.event.NoOpSubscriber;
import io.getunleash.event.UnleashSubscriber;
import io.getunleash.lang.Nullable;
Expand All @@ -14,19 +15,15 @@
import io.getunleash.strategy.Strategy;
import java.io.File;
import java.math.BigInteger;
import java.net.Authenticator;
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.PasswordAuthentication;
import java.net.Proxy;
import java.net.URI;
import java.net.UnknownHostException;
import java.net.*;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.time.Duration;
import java.util.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.function.Consumer;

public class UnleashConfig {

Expand Down Expand Up @@ -71,6 +68,7 @@ public class UnleashConfig {
@Nullable private final Strategy fallbackStrategy;
@Nullable private final ToggleBootstrapProvider toggleBootstrapProvider;
@Nullable private final Proxy proxy;
@Nullable private final Consumer<UnleashException> startupExceptionHandler;

private UnleashConfig(
@Nullable URI unleashAPI,
Expand Down Expand Up @@ -101,7 +99,8 @@ private UnleashConfig(
@Nullable Strategy fallbackStrategy,
@Nullable ToggleBootstrapProvider unleashBootstrapProvider,
@Nullable Proxy proxy,
@Nullable Authenticator proxyAuthenticator) {
@Nullable Authenticator proxyAuthenticator,
@Nullable Consumer<UnleashException> startupExceptionHandler) {

if (appName == null) {
throw new IllegalStateException("You are required to specify the unleash appName");
Expand Down Expand Up @@ -165,6 +164,7 @@ private UnleashConfig(
this.metricSenderFactory = metricSenderFactory;
this.clientSpecificationVersion =
UnleashProperties.getProperty("client.specification.version");
this.startupExceptionHandler = startupExceptionHandler;
}

public static Builder builder() {
Expand Down Expand Up @@ -334,6 +334,11 @@ public UnleashFeatureFetcherFactory getUnleashFeatureFetcherFactory() {
return this.unleashFeatureFetcherFactory;
}

@Nullable
public Consumer<UnleashException> getStartupExceptionHandler() {
return startupExceptionHandler;
}

static class SystemProxyAuthenticator extends Authenticator {
@Override
protected @Nullable PasswordAuthentication getPasswordAuthentication() {
Expand Down Expand Up @@ -427,6 +432,8 @@ public static class Builder {
private @Nullable Proxy proxy;
private @Nullable Authenticator proxyAuthenticator;

private @Nullable Consumer<UnleashException> startupExceptionHandler;

private static String getHostname() {
String hostName = System.getProperty("hostname");
if (hostName == null || hostName.isEmpty()) {
Expand Down Expand Up @@ -657,6 +664,19 @@ public Builder apiKey(String apiKey) {
return this;
}

/**
* Used to handle exceptions when starting up synchronously. Allows user the option to
* choose how errors should be handled.
*
* @param startupExceptionHandler - a lambda taking the Exception and doing what it wants to
* the system.
*/
public Builder startupExceptionHandler(
@Nullable Consumer<UnleashException> startupExceptionHandler) {
this.startupExceptionHandler = startupExceptionHandler;
return this;
}

public UnleashConfig build() {
return new UnleashConfig(
unleashAPI,
Expand Down Expand Up @@ -688,7 +708,8 @@ public UnleashConfig build() {
fallbackStrategy,
toggleBootstrapProvider,
proxy,
proxyAuthenticator);
proxyAuthenticator,
startupExceptionHandler);
}

public String getDefaultSdkVersion() {
Expand Down
63 changes: 32 additions & 31 deletions src/test/java/io/getunleash/DefaultUnleashTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,11 @@
import io.getunleash.event.EventDispatcher;
import io.getunleash.event.UnleashReady;
import io.getunleash.event.UnleashSubscriber;
import io.getunleash.integration.TestDefinition;
import io.getunleash.metric.UnleashMetricService;
import io.getunleash.repository.*;
import io.getunleash.strategy.DefaultStrategy;
import io.getunleash.strategy.Strategy;
import io.getunleash.util.UnleashConfig;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.*;
Expand All @@ -40,10 +38,10 @@ class DefaultUnleashTest {

@RegisterExtension
static WireMockExtension serverMock =
WireMockExtension.newInstance()
.configureStaticDsl(true)
.options(wireMockConfig().dynamicPort().dynamicHttpsPort())
.build();
WireMockExtension.newInstance()
.configureStaticDsl(true)
.options(wireMockConfig().dynamicPort().dynamicHttpsPort())
.build();

@BeforeEach
public void setup() {
Expand Down Expand Up @@ -258,49 +256,51 @@ public void synchronous_fetch_on_initialisation_fails_on_initialization() {

@ParameterizedTest
@ValueSource(ints = {401, 403, 404, 500})
public void synchronous_fetch_on_initialisation_fails_on_non_200_response(int code) throws URISyntaxException {
public void synchronous_fetch_on_initialisation_fails_on_non_200_response(int code)
throws URISyntaxException {
mockUnleashAPI(code);
IsReadyTestSubscriber readySubscriber = new IsReadyTestSubscriber();
UnleashConfig config =
UnleashConfig.builder()
.unleashAPI(new URI("http://localhost:" + serverMock.getPort() + "/api/"))
.appName("wrong_upstream")
.apiKey("default:development:1234567890123456")
.instanceId("non-200")
.synchronousFetchOnInitialisation(true)
.subscriber(readySubscriber)
.build();
UnleashConfig.builder()
.unleashAPI(new URI("http://localhost:" + serverMock.getPort() + "/api/"))
.appName("wrong_upstream")
.apiKey("default:development:1234567890123456")
.instanceId("non-200")
.synchronousFetchOnInitialisation(true)
.subscriber(readySubscriber)
.build();

assertThatThrownBy(() -> new DefaultUnleash(config)).isInstanceOf(UnleashException.class);
assertThat(readySubscriber.ready).isFalse();
}

@Test
public void synchronous_fetch_on_initialisation_switches_to_ready_on_200() throws URISyntaxException {
public void synchronous_fetch_on_initialisation_switches_to_ready_on_200()
throws URISyntaxException {
mockUnleashAPI(200);
IsReadyTestSubscriber readySubscriber = new IsReadyTestSubscriber();
UnleashConfig config =
UnleashConfig.builder()
.unleashAPI(new URI("http://localhost:" + serverMock.getPort() + "/api/"))
.appName("wrong_upstream")
.apiKey("default:development:1234567890123456")
.instanceId("with-success-response")
.synchronousFetchOnInitialisation(true)
.subscriber(readySubscriber)
.build();
UnleashConfig.builder()
.unleashAPI(new URI("http://localhost:" + serverMock.getPort() + "/api/"))
.appName("wrong_upstream")
.apiKey("default:development:1234567890123456")
.instanceId("with-success-response")
.synchronousFetchOnInitialisation(true)
.subscriber(readySubscriber)
.build();
new DefaultUnleash(config);
assertThat(readySubscriber.ready).isTrue();
}

private void mockUnleashAPI(int featuresStatusCode) {
stubFor(
get(urlEqualTo("/api/client/features"))
.withHeader("Accept", equalTo("application/json"))
.willReturn(
aResponse()
.withStatus(featuresStatusCode)
.withHeader("Content-Type", "application/json")
.withBody("{\"features\": []}")));
get(urlEqualTo("/api/client/features"))
.withHeader("Accept", equalTo("application/json"))
.willReturn(
aResponse()
.withStatus(featuresStatusCode)
.withHeader("Content-Type", "application/json")
.withBody("{\"features\": []}")));
stubFor(post(urlEqualTo("/api/client/register")).willReturn(aResponse().withStatus(200)));
}

Expand Down Expand Up @@ -344,6 +344,7 @@ public void client_identifier_handles_api_key_being_null() {

private static class IsReadyTestSubscriber implements UnleashSubscriber {
public boolean ready = false;

public void onReady(UnleashReady unleashReady) {
this.ready = true;
}
Expand Down
Loading
Loading