Does your app have API?

Yes? That’s awesome! But are you sure that it is safe? And I don’t mean if you secured access with api_key, Basic HTTP or anything like this. I mean if it is safe for your severs? At zubibu we are going to add API for updating items in customer shops. It is pretty simple and obvious method. But if some API client use it in bad way it can harm us. For example, if an online store with 400 thousands items decided to decrease all prices by 2% for each item, and if they implemented zubibu API call after each item update - it could harm us. 400k requests in a very short period of time could be dangerous for many apps.

How to protect your API?

You can educate your clients :), you can implement queue for handling API calls on your side. But in most cases (or at least in case described above) queue should be done on the client side. To force implementing queue on the client side you could use API throttling. It should as light as possible and invoked as soon as possible in request processing. So the Rack app sound perfect for this task. You can write your own api throttling app or you can use rack-throttle gem. It is simple but has almost all required features and is pretty easy to extend. We at zubibu needed some custom features, so we decided to extend Rack::Throttle::Limiter. This is our implementation of API throttle. I home comments in code are enough to understand it.

# lib/api_defender.rb
require 'rack/throttle'
# we limit daily API usage
class ApiDefender < Rack::Throttle::Daily

  def initialize(app)
    host, port, db = YAML.load_file(File.dirname(__FILE__) + '/../config/redis.yml')[Rails.env].split(':')
    options = {
      # we already use Redis in our app, so we reuse it's 
      # config file here
      :cache => Redis.new(:host => host, :port => port, :thread_safe => true, :db => db),
      # if you use staging environment on the same redis server
      # it is good to have separete key prefix for this
      :key_prefix => "zubibu:#{Rails.env}:api_defender",
      # only 5000 request per day
      :max => 5000
    }
    @app, @options = app, options
  end

  # this method checks if request needs throttling. 
  # If so, it increases usage counter and compare it with maximum 
  # allowed API calls. Returns true if a request can be handled.
  def allowed?(request)
    need_defense?(request) ? cache_incr(request) <= max_per_window : true
  end

  def call(env)
    status, heders, body = super
    request = Rack::Request.new(env)
    # just to be nice for our clients we inform them how many
    # requests remaining does they have
    if need_defense?(request)
      heders['X-RateLimit-Limit']     = max_per_window.to_s
      heders['X-RateLimit-Remaining'] = ([0, max_per_window - (cache_get(cache_key(request)).to_i rescue 1)].max).to_s
    end
    [status, heders, body]
  end

  # rack-throttle can use many backends for storing request counter.
  # We use Redis only so we can use it's features. In this case 
  # key increase and key expiration
  def cache_incr(request)
    key = cache_key(request)
    count = cache.incr(key)
    cache.expire(key, 1.day) if count == 1
    count
  end

  protected
    # only API calls should be throttled
    def need_defense?(request)
      request.host == API_HOST
    end

end

To add this to your application you to add rack-throttle to your Gemfile

# Gemfile
#...
gem 'rack-throttle'

and run bundle install

after that add, ApiDefender middleware to your app’s rack stack. It should go as high in the stock as possible:

# config/applicaiton.rb
require 'lib/api_defender'
module SomeAwesomeApp
  class Application < Rails::Application
    # ...
    config.middleware.insert_after Rack::Lock, ApiDefender
  end
end

And thats it! Now, when you access your API you should get response like this:

$ curl -I http://api.someawesomeapp.com/whatever 
HTTP/1.1 200 OK
X-RateLimit-Limit: 5000
X-RateLimit-Remaining: 4999

but if you exceed 5000 API calls you will get this response:

HTTP/1.1 403 Forbidden
X-RateLimit-Limit: 5000
X-RateLimit-Remaining: 0

And voilà, your API is more safe now!

More modifications

I suggest you reading rack-throttle’s README file as well as it’s source code to find out what else you could easily modify for your needs. For example your ApiDefence middleware could extend Rack::Throttle::Hourly instead of Rack::Throttle::Daily. Your could use different counter store, or identify your clients differently by overriding client_identifier method.