Add rate limiting, CORS support, custom HTTP method annotations, and HTTP server enhancements

- 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.
This commit is contained in:
CodingPhoenix
2026-05-08 12:00:09 +02:00
parent 392658d54e
commit 05c6ad3dd4
15 changed files with 733 additions and 7 deletions
@@ -0,0 +1,67 @@
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);
}
}
}