This is a fork of the Rack Throttle middleware
that provides logic for rate-limiting incoming HTTP requests to Rack applications using
Redis as storage system. You can use Rack::RedisThrottle
with any Ruby web framework based
on Rack, including Ruby on Rails 3.0 and Sinatra. This gem was designed to experiment rate
limit with Rails 3.x and Doorkeeper.
Redis Throttle Middleware come to life thanks to the work I've made in Lelylan, an open source microservices architecture for the Internet of Things. If this project helped you in any way, think about giving us a star on Github.
- Works only with Redis.
- Automatically deploy by setting
ENV['REDIS_RATE_LIMIT_URL']
. - When the Redis connection is not available redis throttle skips the rate limit check (it does not blow up).
- Automatically adds
X-RateLimit-Limit
andX-RateLimit-Remaining
headers. - Set MockRedis while running your tests
Redis Throttle is tested against MRI 1.9.3, 2.0, and 2.1.x.
Update your gem file and run bundle
gem 'redis-throttle', git: 'git://github.com/lelylan/redis-throttle.git'
# At the top of config/application.rb
require 'rack/redis_throttle'
# Inside the class of config/application.rb
class Application < Rails::Application
# Limit the daily number of requests to 2500
config.middleware.use Rack::RedisThrottle::Daily, max: 2500
end
#!/usr/bin/env ruby -rubygems
require 'sinatra'
require 'rack/throttle'
use Rack::Throttle::Daily, max: 2500
#!/usr/bin/env rackup
require 'rack/throttle'
use Rack::Throttle::Daily max: 2500
run lambda { |env| [200, {'Content-Type' => 'text/plain'}, "Hello, world!\n"] }
You can fully customize the implementation details of any of these strategies by simply subclassing one of the default implementations.
In our example we want to reach these goals:
- We want to use Doorkeper as authorization system (OAuth2)
- The number of daily requests are based on the user id and not the IP
address (default in
Rack::RedisThrottle
) - The number of daily requests is dynamically set per user by the
user#rate_limit
field.
Now subclass Rack::RedisThrottle::Daily
, create your own rules and use it in your Rails app
# /lib/middlewares/daily_rate_limit
require 'rack/redis_throttle'
class DailyRateLimit < Rack::RedisThrottle::Daily
def call(env)
@user_rate_limit = user_rate_limit(env)
super
end
def client_identifier(request)
@user_rate_limit.respond_to?(:_id) ? @user_rate_limit._id : 'user-unknown'
end
def max_per_window(request)
@user_rate_limit.respond_to?(:rate_limit) ? @user_rate_limit.rate_limit : 1000
end
# Rate limit only requests sending the access token
def need_protection?(request)
request.env.has_key?('HTTP_AUTHORIZATION')
end
private
def user_rate_limit(env)
request = Rack::Request.new(env)
token = request.env['HTTP_AUTHORIZATION'].split(' ')[-1]
access_token = Doorkeeper::AccessToken.where(token: token).first
access_token ? User.find(access_token.resource_owner_id) : nil
end
end
Now you can use it in your Rails App.
# config/application.rb
module App
class Application < Rails::Application
# Puts your rate limit middleware as high as you can in your middleware stack
config.middleware.insert_after Rack::Lock, 'DailyRateLimit'
Rack::RedisThrottle
automatically sets two rate limits headers to let the
client know the max number of requests and the one availables.
HTTP/1.1 200 OK
X-RateLimit-Limit: 5000
X-RateLimit-Remaining: 4999
When you exceed the API calls limit your request is forbidden.
HTTP/1.1 403 Forbidden
X-RateLimit-Limit: 5000
X-RateLimit-Remaining: 0
While testing your Rack app Mock the redis connection by requiring this file
# Rate limit fake redis connection
require 'rack/redis_throttle/testing/connection'
The rate-limiting counters stored and maintained by Rack::RedisThrottle
are
keyed to unique HTTP clients. By default, HTTP clients are uniquely identified
by their IP address as returned by Rack::Request#ip
. If you wish to instead
use a more granular, application-specific identifier such as a session key or
a user account name, you need only subclass a throttling strategy implementation
and override the #client_identifier
method.
When a client exceeds their rate limit, Rack::RedisThrottle
by default returns
a "403 Forbidden" response with an associated "Rate Limit Exceeded" message
in the response body. If you need personalize it, for example with a
JSON message.
def http_error(request, code, message = nil, headers = {})
[ code, { 'Content-Type' => 'application/json' }.merge(headers), [body(request).to_json] ]
end
def body(request)
{
status: 403,
method: request.env['REQUEST_METHOD'],
request: "#{request.env['rack.url_scheme']}://#{request.env['HTTP_HOST']}#{request.env['PATH_INFO']}",
description: 'Rate limit exceeded',
daily_rate_limit: max_per_window(request)
}
end
Only Rack::RedisThrottle::Daily
has a test suite. We will cover all
the gem whenever I'll find more time and I'll see it being used widely.
Fork the repo on github and send a pull requests with topic branches. Do not forget to provide specs to your contribution.
- Fork and clone the repository.
- Run
gem install bundler
to get the latest for the gemset. - Run
bundle install
for dependencies. - Run
bundle exec guard
and press enter to execute all specs.
Follow betterspecs.org guidelines.
Follow github guidelines.
Use the issue tracker for bugs. Mail or Tweet us for any idea that can improve the project.
- GIT Repository
- Initial inspiration from Martinciu's dev blog
Andrea Reginato Thanks to Lelylan for letting me share the code.
Special thanks to the following people for submitting patches.
See CHANGELOG
Redis Throttle is free and unencumbered public domain software. See LICENCE