05c6ad3dd4
- Introduced rate limiting functionality with multiple algorithms (Token Bucket, Fixed Window, Leaky Bucket, Sliding Window) via `RateLimiter` interface. - Added CORS handling with `CorsConfig` and `CorsHandler` for flexible origin, headers, and method configuration. - Implemented support for custom HTTP methods via `PATCH` and `CUSTOM` annotations in `AnnotationScanner`. - Enhanced `HttpServer` to support builder pattern and optional integrations for CORS and rate limiting. - Updated `HttpRequestHandler` to incorporate CORS and rate limiting logic.
67 lines
2.3 KiB
Java
67 lines
2.3 KiB
Java
package dev.coph.nextusweb.server.ratelimit;
|
|
|
|
import java.util.concurrent.ConcurrentHashMap;
|
|
import java.util.concurrent.atomic.AtomicLong;
|
|
|
|
public final class SlidingWindowLimiter implements RateLimiter {
|
|
|
|
private final long limit;
|
|
private final long windowNanos;
|
|
private final ConcurrentHashMap<String, SlidingWindow> windows = new ConcurrentHashMap<>();
|
|
|
|
public SlidingWindowLimiter(long limit, long windowMillis) {
|
|
this.limit = limit;
|
|
this.windowNanos = windowMillis * 1_000_000L;
|
|
}
|
|
|
|
@Override
|
|
public Result tryAcquire(String key, long nowNanos) {
|
|
SlidingWindow w = windows.computeIfAbsent(key, k -> new SlidingWindow(nowNanos));
|
|
return w.tryAcquire(nowNanos, limit, windowNanos);
|
|
}
|
|
|
|
public void cleanup(long olderThanNanos) {
|
|
long now = System.nanoTime();
|
|
windows.entrySet().removeIf(e -> now - e.getValue().windowStart.get() > olderThanNanos);
|
|
}
|
|
|
|
private static final class SlidingWindow {
|
|
final AtomicLong windowStart;
|
|
final AtomicLong currentCount;
|
|
final AtomicLong previousCount;
|
|
|
|
SlidingWindow(long now) {
|
|
this.windowStart = new AtomicLong(now);
|
|
this.currentCount = new AtomicLong(0);
|
|
this.previousCount = new AtomicLong(0);
|
|
}
|
|
|
|
synchronized Result tryAcquire(long now, long limit, long windowNanos) {
|
|
long start = windowStart.get();
|
|
long elapsed = now - start;
|
|
|
|
if (elapsed >= 2 * windowNanos) {
|
|
windowStart.set(now);
|
|
previousCount.set(0);
|
|
currentCount.set(0);
|
|
elapsed = 0;
|
|
} else if (elapsed >= windowNanos) {
|
|
windowStart.set(start + windowNanos);
|
|
previousCount.set(currentCount.get());
|
|
currentCount.set(0);
|
|
elapsed -= windowNanos;
|
|
}
|
|
|
|
double prevWeight = 1.0 - ((double) elapsed / windowNanos);
|
|
long weightedCount = (long) (previousCount.get() * prevWeight) + currentCount.get();
|
|
|
|
if (weightedCount >= limit) {
|
|
long retryMs = (windowNanos - elapsed) / 1_000_000L;
|
|
return Result.deny(limit, Math.max(1, retryMs));
|
|
}
|
|
|
|
currentCount.incrementAndGet();
|
|
return Result.allow(limit - weightedCount - 1, limit);
|
|
}
|
|
}
|
|
} |