diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 5bbc02c22..b05128453 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -41,7 +41,7 @@ protected def api_check if request.format.to_s =~ /\/json|\/xml/ || params[:controller] == "iqdb" - if ApiLimiter.throttled?(request.remote_ip) + if ApiLimiter.throttled?(request.remote_ip, request.request_method) render :text => "421 User Throttled\n", :layout => false, :status => 421 return false end diff --git a/app/logical/api_limiter.rb b/app/logical/api_limiter.rb index 881ac1da1..8889ce3b2 100644 --- a/app/logical/api_limiter.rb +++ b/app/logical/api_limiter.rb @@ -1,15 +1,26 @@ module ApiLimiter - def throttled?(ip_addr) - key = "#{ip_addr}:#{Time.now.hour}" - MEMCACHE.fetch(key, 1.hour, :raw => true) {0} - MEMCACHE.incr(key).to_i > CurrentUser.user.api_hourly_limit + def idempotent?(method) + case method + when "POST", "PUT", "DELETE" + false + + else + true + end end - def remaining_hourly_limit(ip_addr) - key = "#{ip_addr}:#{Time.now.hour}" + def throttled?(ip_addr, http_method = "GET") + idempotent = idempotent?(http_method) + key = "api/#{ip_addr}/#{Time.now.hour}/#{idempotent}" + MEMCACHE.fetch(key, 1.hour, :raw => true) {0} + MEMCACHE.incr(key).to_i > CurrentUser.user.api_hourly_limit(idempotent) + end + + def remaining_hourly_limit(ip_addr, idempotent = true) + key = "api/#{ip_addr}/#{Time.now.hour}/#{idempotent}" requests = MEMCACHE.fetch(key, 1.hour, :raw => true) {0}.to_i CurrentUser.user.api_hourly_limit - requests end - module_function :throttled?, :remaining_hourly_limit + module_function :throttled?, :idempotent?, :remaining_hourly_limit end diff --git a/app/models/user.rb b/app/models/user.rb index aff970590..a90600ed9 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -611,20 +611,34 @@ class User < ActiveRecord::Base end end - def api_hourly_limit - if is_platinum? && api_key.present? - 20_000 + def api_hourly_limit(idempotent = true) + base = if is_platinum? && api_key.present? + 2000 elsif is_gold? && api_key.present? - 10_000 + 1000 else - 3_000 + 300 + end + + if idempotent + base * 10 + else + base end end def remaining_api_hourly_limit - ApiLimiter.remaining_hourly_limit(CurrentUser.ip_addr) + ApiLimiter.remaining_hourly_limit(CurrentUser.ip_addr, true) end - + + def remaining_api_hourly_limit_read + ApiLimiter.remaining_hourly_limit(CurrentUser.ip_addr, true) + end + + def remaining_api_hourly_limit_write + ApiLimiter.remaining_hourly_limit(CurrentUser.ip_addr, false) + end + def statement_timeout if is_platinum? 9_000 @@ -644,7 +658,7 @@ class User < ActiveRecord::Base def method_attributes list = [:is_banned, :can_approve_posts, :can_upload_free, :is_super_voter, :level_string] if id == CurrentUser.user.id - list += [:remaining_api_hourly_limit] + list += [:remaining_api_hourly_limit, :remaining_api_hourly_limit_read, :remaining_api_hourly_limit_write] end list end