In my previous blog post I have discussed API designing and versioning. Now I am going to build a simple algorithm to restrict API access based on requests per minute using redis
Very often, as an API provider we need to control request traffic based on certain criteria, like account subscription, time interval or requests per day or per month. Redis provides a key expiry functionality based on TTL (time to live) and using this we can implement the requests per minute feature.
# ruby redis client expire method.
redis_client.expire(key, time_to_expire_in_secs)
# i.e
redis_client.expire("1", 60)
Here is the implementation of API request counting store.
incrmethod increments the key and sets the expiry for the key if it’s set for the first time.threshold?method checks the value after incrementing it.
class ApiRpmStore
TIME_TO_EXPIRE = 60 # 1 min
class << self
attr_accessor :redis_client
def init(config = {})
self.redis_client = Redis.new(:url => "redis://#{config['host']}:#{config['port']}/#{config['database']}")
end
def incr(key)
val = redis_client.incr(key)
redis_client.expire(key, TIME_TO_EXPIRE) if val == 1
val
end
def threshold?(key, threshold_value = 0)
self.incr(key) < threshold_value
end
end
end
Test this using the console.
irb> ApiRpmStore.init({'host' => 'localhost', port: 6379, database: 0})
# threshold value is 1 for key 'user-1'
irb> ApiRpmStore.threshold?('user-1', 1) # return true
irb> ApiRpmStore.threshold?('user-1', 1) # return false
Now implement the before action methods in the controller in which we are going to validate for requests per minute. If the requests per minute validation fails, then we return a response ‘422: too many requests’ with some helper url, like the subscription or license page.
class Api::ApiController < ActionController::Base
private
def authenticate
authenticate_or_request_with_http_token do |token, options|
@user = User.where(api_key: token).first
end
end
def validate_rpm
if ApiRpmStore.threshold?(@user.id.to_s, @user.request_per_min) # request_per_min is threshold for
render json: {help: 'http://mysite.com/plans'}, status: :too_many_requests
return false
end
end
end
This is the events controller on which we are going to throttle the API requests. If you want to apply rate limit to all api then add before_action :validate_rpm to your base api controller (in my case it is the ‘Api::ApiController’ ).
class Api::V1::EventsController < Api::ApiController
before_action :authenticate
before_action :validate_rpm
respond_to :json
def index
@events = Event.all
respond_with @events
end
end
You can find Rails application code sample on github
