[refs #374] Add http headers for rate limit, restrict rate limit only for api calls.
This commit is contained in:
parent
de21c6142b
commit
7756f6200d
|
@ -2,8 +2,7 @@
|
||||||
require File.expand_path('../boot', __FILE__)
|
require File.expand_path('../boot', __FILE__)
|
||||||
|
|
||||||
require 'rails/all'
|
require 'rails/all'
|
||||||
require 'rack/throttle'
|
require './lib/api_defender'
|
||||||
require 'redis'
|
|
||||||
|
|
||||||
# If you have a Gemfile, require the gems listed there, including any gems
|
# If you have a Gemfile, require the gems listed there, including any gems
|
||||||
# you've limited to :test, :development, or :production.
|
# you've limited to :test, :development, or :production.
|
||||||
|
@ -17,9 +16,7 @@ end
|
||||||
module Rosa
|
module Rosa
|
||||||
class Application < Rails::Application
|
class Application < Rails::Application
|
||||||
# Rate limit
|
# Rate limit
|
||||||
config.middleware.use Rack::Throttle::Interval, :cache => Redis.new, :key_prefix => :throttle, :min => 0.1
|
config.middleware.insert_after Rack::Lock, ApiDefender
|
||||||
config.middleware.use Rack::Throttle::Hourly, :max => 500
|
|
||||||
#config.middleware.use Rack::Throttle::Daily, :max => 500
|
|
||||||
|
|
||||||
config.action_view.javascript_expansions[:defaults] = %w(jquery rails)
|
config.action_view.javascript_expansions[:defaults] = %w(jquery rails)
|
||||||
config.autoload_paths += %W(#{config.root}/lib)
|
config.autoload_paths += %W(#{config.root}/lib)
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
require 'rack/throttle'
|
||||||
|
require 'redis'
|
||||||
|
|
||||||
|
# Limit hourly API usage
|
||||||
|
# http://martinciu.com/2011/08/how-to-add-api-throttle-to-your-rails-app.html
|
||||||
|
class ApiDefender < Rack::Throttle::Hourly
|
||||||
|
|
||||||
|
def initialize(app)
|
||||||
|
options = {
|
||||||
|
:cache => Redis.new(:thread_safe => true),
|
||||||
|
:key_prefix => :throttle,
|
||||||
|
|
||||||
|
# only 500 request per hour
|
||||||
|
:max => 500
|
||||||
|
}
|
||||||
|
@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
|
||||||
|
|
||||||
|
# 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.env['PATH_INFO'] =~ /^\/api\/v1\//
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
Loading…
Reference in New Issue