CurbIt
CurbIt makes it easy to add application level rate limiting to your Rails app by using a controller macro.
CurbIt is NOT a replacement for properly configured rate limiting at fronting services. Properly defending your app against DoS attacks or other malicious client behavior should always include configuring rate limiting and throttling at the services that sit in front of your app. This includes firewalls, load balancers, and reverse proxies. But sometimes that just isn’t enough. Sometimes you want to rate limit requests from users based on application logic that is not practical to get to or replicate in those services.
Usage
Minimal configuration
Quick setup inside your Rails controller. ActionController::Base is already extended to include Curbit when installed as a plugin. This will add a rate_limit “macro” to your controllers.
class InvitesController < ApplicationController
def invite
# invite logic...
end
rate_limit :invite, :max_calls => 2, :time_limit => 30.seconds, :wait_time => 1.minute
end
If a user calls the invite service from the same remote address more than 2 times within 30 seconds, CurbIt will render a ‘503 Service Unavailable’ response and the invite method is never called. The user will then need to wait 1 minute before being allowed to make the request again. Default response messages for html, xml, and json formats are rendered as required.
Custom client identifier
If you don’t want to use the remote address to identify the client, you can specify a method that CurbIt will call to get a key from.
class InvitesController < ApplicationController
def invite
# invite logic...
end
rate_limit :invite, :key => :userid, :max_calls => 2,
:time_limit => 30.seconds, :wait_time => 1.minute
def userid
session[:user_id]
end
end
CurbIt will call the :userid method and use the returned value to create a unique identifier. This identifier is used to index cached information about the request.
You can alternatively pass a Proc that will take the controller instance as an argument.
rate_limit :invite, :key => proc {|c| c.session[:user_id]},
:max_calls => 2,
:time_limit => 30.seconds, :wait_time => 1.minute
(If you’re wondering why CurbIt passes the controller into the proc, it’s because the Proc is not bound to the controller instance when it’s defined. This way, you can at least have access to stuff you might need.)
Custom message
You might like to customize the messages returned by CurbIt.
class InvitesController < ApplicationController
def invite
# invite logic...
end
rate_limit :invite, :max_calls => 2, :time_limit => 30.seconds, :wait_time => 1.minute,
:message => "Hey! Slow down there cow polk.",
:status => 200
end
After reaching the maximum threshold of requests, CurbIt will render the message “Hey! Slow down there cow polk.” with a response status of 200. If :status is not defined, CurbIt will set a 503 status on the response. CurbIt will also embed this message into some default json or xml containers based on the request format.
Custom message rendering
CurbIt does it’s best to render a response based on the requested format, but you might have an obscure mime-type you’re using or you might like to customize the response rendering.
class InvitesController < ApplicationController
def invite
# invite logic...
end
rate_limit :invite, :max_calls => 2, :time_limit => 30.seconds, :wait_time => 1.minute,
:message => :limit_response
def limit_response(wait_time)
respond_to {|fmt|
fmt.csv {
render :text => "Plese wait #{wait_time} seconds before trying again",
:status => 200
}
}
end
end
Here, CurbIt will relinquish all control for response rendering to your method. This will ignore any :status argument set in the cofig options.
Default response bodies
-
xml: <error>message</error>
-
json: “error”:{“message”}
-
html: message
-
text: message
Rails Installation
As a Gem
Gem install
$ gem install curbit --source http://gemcutter.org
Rails dependency
Specify the gem dependency in your config/environment.rb file:
Rails::Initializer.run do |config|
#...
config.gem "curbit", :source => "http://gemcutter.org
#...
end
Then:
$ rake gems:install
$ rake gems:unpack
Then include in your controllers:
class MyController < ApplicationController
include Curbit::Controller
#...
end
As a Plugin
$ script/plugin install git://github.com/ssayles/curbit.git
Curbit::Controller will automatically be added to your controllers so you can call the rate_limit macro whenever you want.
Dependencies
CurbIt utilizes Rails.cache to store information about requests. Be mindful that if you are running a cluster, you’ll want to make sure you’re using a shared cache like memcached.
That’s it!
Credits
CurbIt is written and maintained by Scott Sayles.
Copyright
Copyright © 2009 Scott Sayles. See LICENSE for details.