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.
incr
method 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