Skip to content

Instantly share code, notes, and snippets.

@mdub
Created May 21, 2009 20:31
Show Gist options
  • Save mdub/115717 to your computer and use it in GitHub Desktop.
Save mdub/115717 to your computer and use it in GitHub Desktop.
# This Rack middleware prevents API consumers from exceeding their allocated requests-per-minute limit.
#
# Per-minute usage allocations divided by 6 to give a 10-second allocation. If a consumer goes over-limit
# for a particular period, they are temporarily denied access.
class PreventDenialOfService
class << self
def call(env)
new(env).call
end
def request_allocations
@request_allocations ||= PeriodicallyClearedHash.new(10) do |consumer_key|
initial_request_allocation_for(consumer_key)
end
end
private
def initial_request_allocation_for(consumer_key)
maximum_requests_per_minute = ApiConsumer[consumer_key].maximum_requests_per_minute
if maximum_requests_per_minute
maximum_requests_per_minute / 6
end
end
end
def initialize(env)
if env["HTTP_AUTHORIZATION"] =~ /^OAuth.* oauth_consumer_key="([^"]+)"/
@oauth_consumer_key = $1
end
end
attr_reader :oauth_consumer_key
def call
catch :response do
limit_access if oauth_consumer_key
proceed
end
end
private
def request_allocations
PreventDenialOfService.request_allocations
end
def limit_access
begin
request_allocation = request_allocations[oauth_consumer_key]
return false if request_allocation.nil?
throw :response, over_limit if request_allocation <= 0
request_allocations[oauth_consumer_key] = request_allocation - 1
rescue AuthorisationError => e
throw :response, unauthorized(e.message)
end
end
def unauthorized
end
def proceed
[404, {"Content-Type" => "text/plain"}, ["Not Found"]]
end
def unauthorized(message)
[401, {"Content-Type" => "text/plain"}, [message]]
end
def over_limit
[503, {"Content-Type" => "text/plain", "Retry-After" => "10"}, ["Over limit"]]
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment