From 5a9bf8698012d2c1d90307c64bf23a23a7b57640 Mon Sep 17 00:00:00 2001 From: Ashish Pratap Singh Date: Tue, 16 Jul 2024 19:53:11 -0700 Subject: [PATCH] Add rate limiting code --- .../rate_limiting/FixedWindowCounter.java | 33 +++++++++++++++ .../java/rate_limiting/LeakyBucket.java | 42 +++++++++++++++++++ .../rate_limiting/SlidingWindowCounter.java | 42 +++++++++++++++++++ .../java/rate_limiting/SlidingWindowLog.java | 33 +++++++++++++++ .../java/rate_limiting/TokenBucket.java | 36 ++++++++++++++++ .../rate_limiting/fixed_window_counter.py | 33 +++++++++++++++ .../python/rate_limiting/leaky_bucket.py | 36 ++++++++++++++++ .../rate_limiting/sliding_window_counter.py | 39 +++++++++++++++++ .../rate_limiting/sliding_window_log.py | 31 ++++++++++++++ .../python/rate_limiting/token_bucket.py | 31 ++++++++++++++ 10 files changed, 356 insertions(+) create mode 100644 implementations/java/rate_limiting/FixedWindowCounter.java create mode 100644 implementations/java/rate_limiting/LeakyBucket.java create mode 100644 implementations/java/rate_limiting/SlidingWindowCounter.java create mode 100644 implementations/java/rate_limiting/SlidingWindowLog.java create mode 100644 implementations/java/rate_limiting/TokenBucket.java create mode 100644 implementations/python/rate_limiting/fixed_window_counter.py create mode 100644 implementations/python/rate_limiting/leaky_bucket.py create mode 100644 implementations/python/rate_limiting/sliding_window_counter.py create mode 100644 implementations/python/rate_limiting/sliding_window_log.py create mode 100644 implementations/python/rate_limiting/token_bucket.py diff --git a/implementations/java/rate_limiting/FixedWindowCounter.java b/implementations/java/rate_limiting/FixedWindowCounter.java new file mode 100644 index 0000000..55d4e21 --- /dev/null +++ b/implementations/java/rate_limiting/FixedWindowCounter.java @@ -0,0 +1,33 @@ +package implementations.java.rate_limiting; + +import java.time.Instant; + +public class FixedWindowCounter { + private final long windowSizeInSeconds; // Size of each window in seconds + private final long maxRequestsPerWindow; // Maximum number of requests allowed per window + private long currentWindowStart; // Start time of the current window + private long requestCount; // Number of requests in the current window + + public FixedWindowCounter(long windowSizeInSeconds, long maxRequestsPerWindow) { + this.windowSizeInSeconds = windowSizeInSeconds; + this.maxRequestsPerWindow = maxRequestsPerWindow; + this.currentWindowStart = Instant.now().getEpochSecond(); + this.requestCount = 0; + } + + public synchronized boolean allowRequest() { + long now = Instant.now().getEpochSecond(); + + // Check if we've moved to a new window + if (now - currentWindowStart >= windowSizeInSeconds) { + currentWindowStart = now; // Start a new window + requestCount = 0; // Reset the count for the new window + } + + if (requestCount < maxRequestsPerWindow) { + requestCount++; // Increment the count for this window + return true; // Allow the request + } + return false; // We've exceeded the limit for this window, deny the request + } +} diff --git a/implementations/java/rate_limiting/LeakyBucket.java b/implementations/java/rate_limiting/LeakyBucket.java new file mode 100644 index 0000000..2946170 --- /dev/null +++ b/implementations/java/rate_limiting/LeakyBucket.java @@ -0,0 +1,42 @@ +package implementations.java.rate_limiting; + +import java.time.Instant; +import java.util.LinkedList; +import java.util.Queue; + +public class LeakyBucket { + private final long capacity; // Maximum number of requests the bucket can hold + private final double leakRate; // Rate at which requests leak out of the bucket (requests per second) + private final Queue bucket; // Queue to hold timestamps of requests + private Instant lastLeakTimestamp; // Last time we leaked from the bucket + + public LeakyBucket(long capacity, double leakRate) { + this.capacity = capacity; + this.leakRate = leakRate; + this.bucket = new LinkedList<>(); + this.lastLeakTimestamp = Instant.now(); + } + + public synchronized boolean allowRequest() { + leak(); // First, leak out any requests based on elapsed time + + if (bucket.size() < capacity) { + bucket.offer(Instant.now()); // Add the new request to the bucket + return true; // Allow the request + } + return false; // Bucket is full, deny the request + } + + private void leak() { + Instant now = Instant.now(); + long elapsedMillis = now.toEpochMilli() - lastLeakTimestamp.toEpochMilli(); + int leakedItems = (int) (elapsedMillis * leakRate / 1000.0); // Calculate how many items should have leaked + + // Remove the leaked items from the bucket + for (int i = 0; i < leakedItems && !bucket.isEmpty(); i++) { + bucket.poll(); + } + + lastLeakTimestamp = now; + } +} diff --git a/implementations/java/rate_limiting/SlidingWindowCounter.java b/implementations/java/rate_limiting/SlidingWindowCounter.java new file mode 100644 index 0000000..67a4532 --- /dev/null +++ b/implementations/java/rate_limiting/SlidingWindowCounter.java @@ -0,0 +1,42 @@ +package implementations.java.rate_limiting; + +import java.time.Instant; + +public class SlidingWindowCounter { + private final long windowSizeInSeconds; // Size of the sliding window in seconds + private final long maxRequestsPerWindow; // Maximum number of requests allowed in the window + private long currentWindowStart; // Start time of the current window + private long previousWindowCount; // Number of requests in the previous window + private long currentWindowCount; // Number of requests in the current window + + public SlidingWindowCounter(long windowSizeInSeconds, long maxRequestsPerWindow) { + this.windowSizeInSeconds = windowSizeInSeconds; + this.maxRequestsPerWindow = maxRequestsPerWindow; + this.currentWindowStart = Instant.now().getEpochSecond(); + this.previousWindowCount = 0; + this.currentWindowCount = 0; + } + + public synchronized boolean allowRequest() { + long now = Instant.now().getEpochSecond(); + long timePassedInWindow = now - currentWindowStart; + + // Check if we've moved to a new window + if (timePassedInWindow >= windowSizeInSeconds) { + previousWindowCount = currentWindowCount; + currentWindowCount = 0; + currentWindowStart = now; + timePassedInWindow = 0; + } + + // Calculate the weighted count of requests + double weightedCount = previousWindowCount * ((windowSizeInSeconds - timePassedInWindow) / (double) windowSizeInSeconds) + + currentWindowCount; + + if (weightedCount < maxRequestsPerWindow) { + currentWindowCount++; // Increment the count for this window + return true; // Allow the request + } + return false; // We've exceeded the limit, deny the request + } +} diff --git a/implementations/java/rate_limiting/SlidingWindowLog.java b/implementations/java/rate_limiting/SlidingWindowLog.java new file mode 100644 index 0000000..6f0858b --- /dev/null +++ b/implementations/java/rate_limiting/SlidingWindowLog.java @@ -0,0 +1,33 @@ +package implementations.java.rate_limiting; + +import java.time.Instant; +import java.util.LinkedList; +import java.util.Queue; + +public class SlidingWindowLog { + private final long windowSizeInSeconds; // Size of the sliding window in seconds + private final long maxRequestsPerWindow; // Maximum number of requests allowed in the window + private final Queue requestLog; // Log of request timestamps + + public SlidingWindowLog(long windowSizeInSeconds, long maxRequestsPerWindow) { + this.windowSizeInSeconds = windowSizeInSeconds; + this.maxRequestsPerWindow = maxRequestsPerWindow; + this.requestLog = new LinkedList<>(); + } + + public synchronized boolean allowRequest() { + long now = Instant.now().getEpochSecond(); + long windowStart = now - windowSizeInSeconds; + + // Remove timestamps that are outside of the current window + while (!requestLog.isEmpty() && requestLog.peek() <= windowStart) { + requestLog.poll(); + } + + if (requestLog.size() < maxRequestsPerWindow) { + requestLog.offer(now); // Log this request + return true; // Allow the request + } + return false; // We've exceeded the limit for this window, deny the request + } +} diff --git a/implementations/java/rate_limiting/TokenBucket.java b/implementations/java/rate_limiting/TokenBucket.java new file mode 100644 index 0000000..5868586 --- /dev/null +++ b/implementations/java/rate_limiting/TokenBucket.java @@ -0,0 +1,36 @@ +package implementations.java.rate_limiting; + +import java.time.Instant; + +public class TokenBucket { + private final long capacity; // Maximum number of tokens the bucket can hold + private final double fillRate; // Rate at which tokens are added to the bucket (tokens per second) + private double tokens; // Current number of tokens in the bucket + private Instant lastRefillTimestamp; // Last time we refilled the bucket + + public TokenBucket(long capacity, double fillRate) { + this.capacity = capacity; + this.fillRate = fillRate; + this.tokens = capacity; // Start with a full bucket + this.lastRefillTimestamp = Instant.now(); + } + + public synchronized boolean allowRequest(int tokens) { + refill(); // First, add any new tokens based on elapsed time + + if (this.tokens < tokens) { + return false; // Not enough tokens, deny the request + } + + this.tokens -= tokens; // Consume the tokens + return true; // Allow the request + } + + private void refill() { + Instant now = Instant.now(); + // Calculate how many tokens to add based on the time elapsed + double tokensToAdd = (now.toEpochMilli() - lastRefillTimestamp.toEpochMilli()) * fillRate / 1000.0; + this.tokens = Math.min(capacity, this.tokens + tokensToAdd); // Add tokens, but don't exceed capacity + this.lastRefillTimestamp = now; + } +} \ No newline at end of file diff --git a/implementations/python/rate_limiting/fixed_window_counter.py b/implementations/python/rate_limiting/fixed_window_counter.py new file mode 100644 index 0000000..eb5868b --- /dev/null +++ b/implementations/python/rate_limiting/fixed_window_counter.py @@ -0,0 +1,33 @@ +import time + +class FixedWindowCounter: + def __init__(self, window_size, max_requests): + self.window_size = window_size # Size of the window in seconds + self.max_requests = max_requests # Maximum number of requests per window + self.current_window = time.time() // window_size + self.request_count = 0 + + def allow_request(self): + current_time = time.time() + window = current_time // self.window_size + + # If we've moved to a new window, reset the counter + if window != self.current_window: + self.current_window = window + self.request_count = 0 + + # Check if we're still within the limit for this window + if self.request_count < self.max_requests: + self.request_count += 1 + return True + return False + +# Usage example +limiter = FixedWindowCounter(window_size=60, max_requests=5) # 5 requests per minute + +for _ in range(10): + print(limiter.allow_request()) # Will print True for the first 5 requests, then False + time.sleep(0.1) # Wait a bit between requests + +time.sleep(60) # Wait for the window to reset +print(limiter.allow_request()) # True \ No newline at end of file diff --git a/implementations/python/rate_limiting/leaky_bucket.py b/implementations/python/rate_limiting/leaky_bucket.py new file mode 100644 index 0000000..ddb2492 --- /dev/null +++ b/implementations/python/rate_limiting/leaky_bucket.py @@ -0,0 +1,36 @@ +from collections import deque +import time + +class LeakyBucket: + def __init__(self, capacity, leak_rate): + self.capacity = capacity # Maximum number of requests in the bucket + self.leak_rate = leak_rate # Rate at which requests leak (requests/second) + self.bucket = deque() # Queue to hold request timestamps + self.last_leak = time.time() # Last time we leaked from the bucket + + def allow_request(self): + now = time.time() + # Simulate leaking from the bucket + leak_time = now - self.last_leak + leaked = int(leak_time * self.leak_rate) + if leaked > 0: + # Remove the leaked requests from the bucket + for _ in range(min(leaked, len(self.bucket))): + self.bucket.popleft() + self.last_leak = now + + # Check if there's capacity and add the new request + if len(self.bucket) < self.capacity: + self.bucket.append(now) + return True + return False + +# Usage example +limiter = LeakyBucket(capacity=5, leak_rate=1) # 5 requests, leak 1 per second + +for _ in range(10): + print(limiter.allow_request()) # Will print True for the first 5 requests, then False + time.sleep(0.1) # Wait a bit between requests + +time.sleep(1) # Wait for bucket to leak +print(limiter.allow_request()) # True \ No newline at end of file diff --git a/implementations/python/rate_limiting/sliding_window_counter.py b/implementations/python/rate_limiting/sliding_window_counter.py new file mode 100644 index 0000000..54c00f2 --- /dev/null +++ b/implementations/python/rate_limiting/sliding_window_counter.py @@ -0,0 +1,39 @@ +import time + +class SlidingWindowCounter: + def __init__(self, window_size, max_requests): + self.window_size = window_size # Size of the sliding window in seconds + self.max_requests = max_requests # Maximum number of requests per window + self.current_window = time.time() // window_size + self.request_count = 0 + self.previous_count = 0 + + def allow_request(self): + now = time.time() + window = now // self.window_size + + # If we've moved to a new window, update the counts + if window != self.current_window: + self.previous_count = self.request_count + self.request_count = 0 + self.current_window = window + + # Calculate the weighted request count + window_elapsed = (now % self.window_size) / self.window_size + threshold = self.previous_count * (1 - window_elapsed) + self.request_count + + # Check if we're within the limit + if threshold < self.max_requests: + self.request_count += 1 + return True + return False + +# Usage example +limiter = SlidingWindowCounter(window_size=60, max_requests=5) # 5 requests per minute + +for _ in range(10): + print(limiter.allow_request()) # Will print True for the first 5 requests, then gradually become False + time.sleep(0.1) # Wait a bit between requests + +time.sleep(30) # Wait for half the window to pass +print(limiter.allow_request()) # Might be True or False depending on the exact timing \ No newline at end of file diff --git a/implementations/python/rate_limiting/sliding_window_log.py b/implementations/python/rate_limiting/sliding_window_log.py new file mode 100644 index 0000000..88a06b1 --- /dev/null +++ b/implementations/python/rate_limiting/sliding_window_log.py @@ -0,0 +1,31 @@ +import time +from collections import deque + +class SlidingWindowLog: + def __init__(self, window_size, max_requests): + self.window_size = window_size # Size of the sliding window in seconds + self.max_requests = max_requests # Maximum number of requests per window + self.request_log = deque() # Log to keep track of request timestamps + + def allow_request(self): + now = time.time() + + # Remove timestamps that are outside the current window + while self.request_log and now - self.request_log[0] >= self.window_size: + self.request_log.popleft() + + # Check if we're still within the limit + if len(self.request_log) < self.max_requests: + self.request_log.append(now) + return True + return False + +# Usage example +limiter = SlidingWindowLog(window_size=60, max_requests=5) # 5 requests per minute + +for _ in range(10): + print(limiter.allow_request()) # Will print True for the first 5 requests, then False + time.sleep(0.1) # Wait a bit between requests + +time.sleep(60) # Wait for the window to slide +print(limiter.allow_request()) # True \ No newline at end of file diff --git a/implementations/python/rate_limiting/token_bucket.py b/implementations/python/rate_limiting/token_bucket.py new file mode 100644 index 0000000..5ab1c61 --- /dev/null +++ b/implementations/python/rate_limiting/token_bucket.py @@ -0,0 +1,31 @@ +import time + +class TokenBucket: + def __init__(self, capacity, fill_rate): + self.capacity = capacity # Maximum number of tokens the bucket can hold + self.fill_rate = fill_rate # Rate at which tokens are added (tokens/second) + self.tokens = capacity # Current token count, start with a full bucket + self.last_time = time.time() # Last time we checked the token count + + def allow_request(self, tokens=1): + now = time.time() + # Calculate how many tokens have been added since the last check + time_passed = now - self.last_time + self.tokens = min(self.capacity, self.tokens + time_passed * self.fill_rate) + self.last_time = now + + # Check if we have enough tokens for this request + if self.tokens >= tokens: + self.tokens -= tokens + return True + return False + +# Usage example +limiter = TokenBucket(capacity=10, fill_rate=1) # 10 tokens, refill 1 token per second + +for _ in range(15): + print(limiter.allow_request()) # Will print True for the first 10 requests, then False + time.sleep(0.1) # Wait a bit between requests + +time.sleep(5) # Wait for bucket to refill +print(limiter.allow_request()) # True \ No newline at end of file