diff --git a/Java/src/main/java/net/hypixel/api/HypixelAPI.java b/Java/src/main/java/net/hypixel/api/HypixelAPI.java
index bb5835a9..faf0bbdf 100644
--- a/Java/src/main/java/net/hypixel/api/HypixelAPI.java
+++ b/Java/src/main/java/net/hypixel/api/HypixelAPI.java
@@ -12,6 +12,7 @@
import net.hypixel.api.reply.*;
import net.hypixel.api.reply.skyblock.*;
import net.hypixel.api.util.GameType;
+import net.hypixel.api.util.RateLimiter;
import net.hypixel.api.util.ResourceType;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
@@ -25,6 +26,7 @@
import java.util.concurrent.Executors;
public class HypixelAPI {
+ private static final int DEFAULT_MAX_REQUESTS_PER_MINUTE = 120;
private static final Gson GSON = new GsonBuilder()
.registerTypeAdapter(UUID.class, new UUIDTypeAdapter())
@@ -40,19 +42,33 @@ public class HypixelAPI {
private final ExecutorService executorService;
private final HttpClient httpClient;
+ private final RateLimiter rateLimiter;
public HypixelAPI(UUID apiKey) {
this.apiKey = apiKey;
this.executorService = Executors.newCachedThreadPool();
this.httpClient = HttpClientBuilder.create().build();
+ this.rateLimiter = new RateLimiter(DEFAULT_MAX_REQUESTS_PER_MINUTE);
}
/**
- * Shuts down the internal executor service
+ * Set how many requests this API instance is allowed to make in one minute.
+ *
If more requests attempt to pass past this limit in one minute,
+ * they will need to wait for the next minute. Default is 120.
+ *
+ * @param limitPerMinute The new limit.
+ */
+ public void setRateLimit(int limitPerMinute) {
+ rateLimiter.setRate(limitPerMinute);
+ }
+
+ /**
+ * Shuts down the internal executor service and rate limiter service.
*/
public void shutdown() {
executorService.shutdown();
+ rateLimiter.shutdown();
}
/**
@@ -246,6 +262,8 @@ private CompletableFuture get(Class clazz, Strin
executorService.submit(() -> {
try {
+ rateLimiter.beforeAction();
+
R response = httpClient.execute(new HttpGet(url.toString()), obj -> {
String content = EntityUtils.toString(obj.getEntity(), "UTF-8");
if (clazz == ResourceReply.class) {
diff --git a/Java/src/main/java/net/hypixel/api/util/RateLimiter.java b/Java/src/main/java/net/hypixel/api/util/RateLimiter.java
new file mode 100644
index 00000000..088d8e3a
--- /dev/null
+++ b/Java/src/main/java/net/hypixel/api/util/RateLimiter.java
@@ -0,0 +1,62 @@
+package net.hypixel.api.util;
+
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+
+import static java.util.concurrent.TimeUnit.MINUTES;
+
+public class RateLimiter {
+ private final Object lock = new Object();
+
+ private final ScheduledExecutorService resetter = Executors.newSingleThreadScheduledExecutor();
+ private volatile int limitPerMinute;
+ private int actionsThisMinute;
+
+ /**
+ * @param limitPerMinute Maximum amount of times {@link RateLimiter#beforeAction()} can be called in the same minute
+ * before it begins blocking threads until the next minute.
+ */
+ public RateLimiter(int limitPerMinute) {
+ this.limitPerMinute = limitPerMinute;
+ resetter.scheduleAtFixedRate(this::reset, 1, 1, MINUTES);
+ }
+
+ private void reset() {
+ synchronized ( lock ) {
+ actionsThisMinute = 0;
+ lock.notifyAll();
+ }
+ }
+
+ /**
+ * Set the action queue limit to be used by {@link RateLimiter#beforeAction()}.
+ *
+ * @param limitPerMinute The new limit.
+ */
+ public void setRate(int limitPerMinute) {
+ this.limitPerMinute = limitPerMinute;
+ }
+
+ /**
+ * Blocks the current thread in the event that the action queue has been filled for this minute.
+ * Unblocks when the queue refreshes.
+ *
+ * @throws InterruptedException If interrupted while waiting.
+ */
+ public void beforeAction() throws InterruptedException {
+ synchronized ( lock ) {
+ while (actionsThisMinute >= limitPerMinute) {
+ lock.wait();
+ }
+
+ actionsThisMinute++;
+ }
+ }
+
+ /**
+ * Shut down the internal executor service.
+ */
+ public void shutdown() {
+ resetter.shutdown();
+ }
+}