Have you ever been told to slow down when you're talking too fast? API rate limiters work similarly for computer programs.
An API rate limiter controls how often a user can make requests to a service. It's like a traffic cop for data, making sure no one hogs all the resources.
class InMemoryRateLimiter
MAX_REQUESTS = 100
WINDOW_SIZE = 3600 # 1 hour in seconds
def initialize
@requests = {}
end
def allow_request?(user_id)
current_time = Time.now.to_i
@requests[user_id] ||= []
@requests[user_id].reject! { |timestamp| timestamp < current_time - WINDOW_SIZE }
if @requests[user_id].size < MAX_REQUESTS
@requests[user_id] << current_time
true
else
false
end
end
end
# In your controller:
def index
limiter = InMemoryRateLimiter.new
if limiter.allow_request?(current_user.id)
# Process the request
else
render json: { error: "Too many requests" }, status: :too_many_requests
end
end
Pros: Extremely fast, simple to implement
Cons: Not persistent, doesn't scale across multiple servers
There are several other approaches to implement rate limiting without using In-memory storage. Here are a few alternatives:
Each of these approaches has its pros and cons:
Redis excels in high-performance scenarios and distributed systems, making it a popular choice for rate limiting in production environments, despite the added complexity of managing a Redis server.
class RedisRateLimiter
MAX_REQUESTS = 100
WINDOW_SIZE = 3600 # 1 hour in seconds
def initialize(user_id)
@user_id = user_id
@redis = Redis.new
end
def allow_request?
current_time = Time.now.to_i
key = "rate_limit:#{@user_id}"
# Removes all elements in the sorted set with a score between min and max (inclusive).
# We use it to remove all entries older than our time window (1 hour ago).
# This effectively "slides" our window, keeping only recent entries.
@redis.zremrangebyscore(key, 0, current_time - WINDOW_SIZE)
# Returns the number of elements in the sorted set.
# We use this to count how many requests have been made in our current time window.
current_count = @redis.zcard(key)
if current_count < MAX_REQUESTS
# Adds one or more members to a sorted set, or updates the score if it already exists.
# Here, we're adding an entry with the current timestamp as the score.
# The value is a unique string (timestamp plus random hex) to avoid collisions.
@redis.zadd(key, current_time, "#{current_time}.#{SecureRandom.hex(6)}")
@redis.expire(key, WINDOW_SIZE)
true
else
false
end
end
end
The best choice depends on your specific needs, such as performance requirements, persistence needs, and the scale of your application.
The rate limiting concepts and strategies we've discussed can be applied across various programming languages and frameworks. They're not limited to Ruby or Rails, but can be adapted to fit any technology stack you're working with.
For Ruby developers, there are several gems available that implement these rate limiting concepts, saving you the effort of writing everything from scratch. Some popular gems include:
To use one of these gems, you typically add it to your Gemfile and configure it according to your needs.
Remember, while these gems make implementation easier in Ruby, the underlying principles remain the same across languages. Whether you're using Python, JavaScript, Java, or any other language, you can apply these rate limiting strategies by either using language-specific libraries or implementing the logic yourself.
Lock Down Your APIs: Simple Security Measures (Whitelisting, JSON Web Tokens, API Keys, OAuth 2.0, Basic Authentication, Hash-based Message Authentication Code) https://vulehuan.com/en/blog/2024/7/lock-down-your-apis-simple-security-measures-whitelisting-json-web-tokens-api-keys-oauth-2-0-basic-authentication-hashbased-message-authentication-code-668a3c76f0915ca45912b911.html