Files
Nextus-Web/src/main/java/dev/coph/nextusweb/server/ratelimit/RateLimiter.java
T
CodingPhoenix 893bb0b7bd
CI - Test, Publish and Release / run-tests (push) Failing after 15s
CI - Test, Publish and Release / create-release (push) Has been skipped
CI - Test, Publish and Release / check-and-publish (push) Has been skipped
Reformat code comments for consistency and clarity across all classes
2026-06-15 07:27:07 +02:00

84 lines
3.6 KiB
Java

package dev.coph.nextusweb.server.ratelimit;
/**
* Strategy interface for rate limiting. An implementation decides, per logical key, whether a
* single request may proceed right now.
*
* <p>Concrete strategies in this package include {@link TokenBucketLimiter},
* {@link LeakyBucketLimiter}, {@link FixedWindowLimiter} and {@link SlidingWindowLimiter}.
* Implementations are expected to be thread-safe, since the same limiter is shared across all
* request-handling threads.</p>
*
* <p>The interface remains effectively functional ({@link #tryAcquire} is its single abstract
* method), so simple stateless limiters can still be written as a lambda; stateful limiters that
* keep one entry per key should additionally override {@link #cleanup(long)}.</p>
*/
public interface RateLimiter {
/**
* Attempts to consume one unit of quota for the given key at the given timestamp.
*
* @param key the logical bucket key (for example a client IP or user identifier)
* @param nowNanos the current time in nanoseconds, typically {@link System#nanoTime()}
* @return a {@link Result} describing whether the request was allowed and the remaining
* quota
*/
Result tryAcquire(String key, long nowNanos);
/**
* Evicts per-key state that has not been accessed within the given age, bounding the memory
* a limiter consumes when it has seen many distinct keys.
*
* <p>Implementations keep one entry per key seen ({@code clientIp}, API key, ...). Without
* periodic eviction those maps grow without bound, which is both a memory leak and a denial
* of service vector (an attacker that varies the key on every request can exhaust the heap).
* {@link RateLimitGate} calls this periodically for every configured limiter.</p>
*
* <p>The default implementation does nothing, which is correct for stateless limiters; any
* limiter that retains per-key state <strong>must</strong> override it to evict stale
* entries.</p>
*
* @param olderThanNanos maximum idle age in nanoseconds before an entry is removed
*/
default void cleanup(long olderThanNanos) {
}
/**
* Immutable outcome of a {@link #tryAcquire(String, long)} call.
*
* @param allowed whether the request may proceed
* @param remaining the remaining quota in the current window/bucket
* @param limit the configured limit, surfaced as {@code X-RateLimit-Limit}
* @param retryAfterMillis when denied, how long the caller should wait before retrying, in
* milliseconds (0 when allowed)
*/
record Result(
boolean allowed,
long remaining,
long limit,
long retryAfterMillis
) {
/**
* Creates a result representing an allowed request.
*
* @param remaining the remaining quota after this request
* @param limit the configured limit
* @return an "allowed" result with no retry delay
*/
public static Result allow(long remaining, long limit) {
return new Result(true, remaining, limit, 0);
}
/**
* Creates a result representing a denied (rate-limited) request.
*
* @param limit the configured limit
* @param retryAfterMillis how long to wait before retrying, in milliseconds
* @return a "denied" result with zero remaining quota
*/
public static Result deny(long limit, long retryAfterMillis) {
return new Result(false, 0, limit, retryAfterMillis);
}
}
}