All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.cryptomorin.xseries.profiles.mojang.RateLimiter Maven / Gradle / Ivy

There is a newer version: 12.1.0
Show newest version
package com.cryptomorin.xseries.profiles.mojang;

import com.google.errorprone.annotations.CanIgnoreReturnValue;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Unmodifiable;

import java.time.Duration;
import java.util.Iterator;
import java.util.concurrent.ConcurrentLinkedQueue;

/**
 * Used for {@link MojangAPI} requests.
 * The rate limits make things difficult specially in BungeeCord servers where
 * a single dedicated server is used for multiple subservers.
 * This will specially not work well at all if in shared hosting
 * that can make requests almost impossible with all the servers
 * in a single node.
 * Another issue is that XSeries is mostly intended to be shaded by individual plugins
 * so while we are directly helping the internal Mojang cache, there are some
 * requests that Mojang doesn't have a cache for, so this creates another big
 * problem of having to use separate caches that are not shared.
 * 

* However, these are all untested speculation. But one can't imagine how else * they'd identify a request other than the sender's IP because the default * client used by Mojang doesn't even use a {@code User-Agent}. * According to a comment by a Mojang web service admin in this JIRA issue * shared VPS are indeed an issue due to shared IPs. *

* According to Mojang API the rate limit * is around 600 requests per 10 (i.e. 1 request per second) for most endpoints. * However UUID to Profile and Skin/Cape * is around 200 requests per minute. */ @ApiStatus.Internal public final class RateLimiter { private final ConcurrentLinkedQueue requests = new ConcurrentLinkedQueue<>(); private final int maxRequests; private final long per; RateLimiter(int maxRequests, Duration per) { this.maxRequests = maxRequests; this.per = per.toMillis(); } @CanIgnoreReturnValue @Unmodifiable private ConcurrentLinkedQueue getRequests() { if (requests.isEmpty()) return requests; // Implementing a cleanup delay is practically not any different // from just letting this loop to happen in terms of performance. long now = System.currentTimeMillis(); Iterator iter = requests.iterator(); while (iter.hasNext()) { long requestedAt = iter.next(); long diff = now - requestedAt; if (diff > per) iter.remove(); else break; // Requests are ordered, so if this fails, the others will fail too. } return requests; } public int getRemainingRequests() { return Math.max(0, maxRequests - getRequests().size()); } public int getEffectiveRequestsCount() { return getRequests().size(); } public void instantRateLimit() { long now = System.currentTimeMillis(); for (int i = 0; i < getRemainingRequests(); i++) { requests.add(now); } } public boolean acquire() { if (getRemainingRequests() <= 0) { return false; } else { requests.add(System.currentTimeMillis()); return true; } } public Duration timeUntilNextFreeRequest() { if (getRemainingRequests() == 0) { long now = System.currentTimeMillis(); long oldestRequestedAt = requests.peek(); long diff = now - oldestRequestedAt; return Duration.ofMillis(per - diff); } return Duration.ZERO; } public synchronized void acquireOrWait() { long sleepUntil = timeUntilNextFreeRequest().toMillis(); if (sleepUntil == 0) return; try { Thread.sleep(sleepUntil); } catch (InterruptedException e) { throw new RuntimeException(e); } } @Override public String toString() { return getClass().getSimpleName() + "[total=" + getRequests().size() + ", remaining=" + getRemainingRequests() + ", maxRequests=" + maxRequests + ", per=" + per + ']'; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy