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) {