diff --git a/pom.xml b/pom.xml index ebdc559..a2fcb16 100644 --- a/pom.xml +++ b/pom.xml @@ -79,6 +79,11 @@ vertx-redis-client 3.3.0 + + io.vertx + vertx-circuit-breaker + 3.3.0 + diff --git a/src/main/java/net/frozenorb/apiv3/util/MaxMindUtils.java b/src/main/java/net/frozenorb/apiv3/util/MaxMindUtils.java index c08750d..0f73216 100644 --- a/src/main/java/net/frozenorb/apiv3/util/MaxMindUtils.java +++ b/src/main/java/net/frozenorb/apiv3/util/MaxMindUtils.java @@ -2,6 +2,8 @@ package net.frozenorb.apiv3.util; import com.google.common.base.Charsets; import com.mongodb.async.SingleResultCallback; +import io.vertx.circuitbreaker.CircuitBreaker; +import io.vertx.circuitbreaker.CircuitBreakerOptions; import io.vertx.core.http.HttpClient; import io.vertx.core.http.HttpClientOptions; import io.vertx.core.json.JsonObject; @@ -17,32 +19,48 @@ public class MaxMindUtils { private static final String maxMindUserId = APIv3.getConfig().getProperty("maxMind.userId"); private static final String maxMindLicenseKey = APIv3.getConfig().getProperty("maxMind.maxMindLicenseKey"); private static final HttpClient httpsClient = APIv3.getVertxInstance().createHttpClient(new HttpClientOptions().setSsl(true).setTrustAll(true)); + private static final CircuitBreaker breaker; + + // MaxMind likes to randomly not respond, so we take advantage of the circuit breaker pattern to only + // check MaxMind periodically (while it's in a non-responsive state) to keep our average response times + // nice and low. + static { + breaker = CircuitBreaker.create("maxmind-circuit-breaker", APIv3.getVertxInstance(), + new CircuitBreakerOptions() + .setMaxFailures(5) + .setTimeout(1000) // 1 second + .setFallbackOnFailure(true) + .setResetTimeout(120_000) // 2 minutes + ); + } public static void getInsights(String ip, SingleResultCallback callback) { - if (1 == 1) { - callback.onResult(null, null); - return; - } + breaker.executeWithFallback((future) -> { + String authHeader = "Basic " + Base64.getEncoder().encodeToString((maxMindUserId + ":" + maxMindLicenseKey).getBytes(Charsets.UTF_8)); - String authHeader = "Basic " + Base64.getEncoder().encodeToString((maxMindUserId + ":" + maxMindLicenseKey).getBytes(Charsets.UTF_8)); + httpsClient.get(443, "geoip.maxmind.com", "/geoip/v2.1/insights/" + ip, (response) -> { + response.bodyHandler((body) -> { + JsonObject bodyJson = new JsonObject(body.toString()); - httpsClient.get(443, "geoip.maxmind.com", "/geoip/v2.1/insights/" + ip, (response) -> { - response.bodyHandler((body) -> { - JsonObject bodyJson = new JsonObject(body.toString()); + try { + MaxMindResult maxMindResult = new MaxMindResult(bodyJson); + future.complete(maxMindResult); + } catch (Exception ignored) { + future.complete(null); + } + }); - try { - MaxMindResult maxMindResult = new MaxMindResult(bodyJson); - callback.onResult(maxMindResult, null); - } catch (Exception ignored) { - callback.onResult(null, null); - } - }); - - response.exceptionHandler((error) -> callback.onResult(null, error)); - }) - .putHeader("Authorization", authHeader) - .setTimeout(1000) - .end(); + response.exceptionHandler(future::fail); + }) + .putHeader("Authorization", authHeader) + .end(); + }, (ignored) -> null).setHandler((result) -> { // The (ignored) -> null section is our fallback, which just returns null (when the breaker is open) + if (result.failed()) { + callback.onResult(null, result.cause()); + } else { + callback.onResult((MaxMindResult) result.result(), null); + } + }); } public static String getEnglishName(JsonObject source) {