Throttle api calls in Rails

Kavita Jadhav
2 min readJan 24, 2021

--

Sometimes your application gets overwhelmed with requests. These requests can block other users from accessing the application. To prevent this you can set a threshold on the number of requests a user can make for any endpoint in a given span of time.

I am using rack-attack gem to achieve it.

Add rack-attack gem. Add it to your Gemfile and run bundle install command.

Add rack_attack.rb file in initializers with code snippet given below. Here we will be adding an API rate limit for login API. I am allowing 10 requests per minute and 50 requests per day. You can add more API’s according to your requirements.

require "ipaddr"


Rack
::Attack.cache.store = ActiveSupport::Cache::RedisStore.new(REDIS_URL)

class Rack::Attack
class Request
< ::Rack::Request
def
remote_ip
@remote_ip ||= (env["HTTP_REFERRER"] || env["action_dispatch.remote_ip"] || ip).to_s
end

def
body_params
unless @body_params
@body_params
= JSON.parse(body.read)
body.rewind
end
@body_params
end

def
username
body_params["username"]
end

def
login?
self.path == "/sign_in" && self.post?
end
end

throttle("username:limit-per-minute", limit: 10, period: 1.minute) do |req|
if req.login?
req.username
end
end

throttle("username:limit-daily", limit: 50, period: 1.day) do |req|
if req.login?
req.username
end
end
end

It will maintain a count of requests made using a given username and start throwing an error when the specified request limit is crossed. Users will be able to access the app again in the next minute or day.

You can return a custom response or error message when the API request limit is reached. Add code snippet below after Request class.

Rack::Attack.throttled_callback = lambda do |request|
[429, {}, [{error: 'Your request cannot be processed at this time. Please try later.'}]]
end

You can use the HTTP response code from the 500 series if you don't want to reveal user that you have a request limit in place.

Next, you can put your domain logic in the throttled_callback block. Here I am notifying the admin when the user reaches the limit.

Rack::Attack.throttled_callback = lambda do |request|
ApplicationMailer.notify_admin(to: SYSTEM_ADMIN_EMAIL, subject: 'Api call limit reached', body: "Username: #{request.body_params["username"]}").deliver

[429, {}, [{error: 'Your request cannot be processed at this time. Please try later.'}]]
end

You don’t necessarily have a user identifier in each request like contact_us, or about_us. You can limit requests for an IP address in such a scenario.

Add the code snippet below for that:

throttle("ip:login-limit-per-minute", limit: 10, period: 1.minute) do |req|
if req.contact_us?
req.remote_ip
end
end

Add contact_us? method in the Request class:

def contact_us?
self.path == "/contact_us" && self.get?
end

Hope this helps you to get started.

Happy coding!!!

--

--

Kavita Jadhav

Application Developer @ ThoughtWorks Technologies