mirror of
https://github.com/internetee/registry.git
synced 2025-07-26 20:48:22 +02:00
Move throttling feature from gem back to the app
This commit is contained in:
parent
83413213d9
commit
f17ef17d16
9 changed files with 230 additions and 2 deletions
27
app/lib/shunter.rb
Normal file
27
app/lib/shunter.rb
Normal file
|
@ -0,0 +1,27 @@
|
|||
module Shunter
|
||||
module_function
|
||||
|
||||
class ThrottleError < StandardError; end
|
||||
|
||||
BASE_LOGGER = ::Logger.new($stdout)
|
||||
ONE_MINUTE = 60
|
||||
ONE_HUNDRED_REQUESTS = 100
|
||||
|
||||
BASE_CONNECTION = ENV['shunter_redis_connection'] || { host: 'redis', port: 6379 }
|
||||
|
||||
def default_timespan
|
||||
ENV['shunter_default_timespan'] || ONE_MINUTE
|
||||
end
|
||||
|
||||
def default_threshold
|
||||
ENV['shunter_default_threshold'] || ONE_HUNDRED_REQUESTS
|
||||
end
|
||||
|
||||
def default_adapter
|
||||
ENV['shunter_default_adapter'] || 'Shunter::Adapters::Redis'
|
||||
end
|
||||
|
||||
def feature_enabled?
|
||||
ActiveModel::Type::Boolean.new.cast(ENV['shunter_enabled'] || 'false')
|
||||
end
|
||||
end
|
31
app/lib/shunter/adapters/memory.rb
Normal file
31
app/lib/shunter/adapters/memory.rb
Normal file
|
@ -0,0 +1,31 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Shunter
|
||||
module Adapters
|
||||
class Memory
|
||||
attr_reader :store
|
||||
|
||||
def initialize(_options = {})
|
||||
@@store ||= {}
|
||||
end
|
||||
|
||||
def find_counter(key)
|
||||
@@store[key]
|
||||
end
|
||||
|
||||
def write_counter(key)
|
||||
@@store[key] = 1
|
||||
end
|
||||
|
||||
def increment_counter(key)
|
||||
@@store[key] += 1
|
||||
end
|
||||
|
||||
def clear!
|
||||
@@store = {}
|
||||
end
|
||||
|
||||
def expire_counter(_key, _timespan); end
|
||||
end
|
||||
end
|
||||
end
|
29
app/lib/shunter/adapters/redis.rb
Normal file
29
app/lib/shunter/adapters/redis.rb
Normal file
|
@ -0,0 +1,29 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Shunter
|
||||
module Adapters
|
||||
class Redis
|
||||
attr_reader :redis
|
||||
|
||||
def initialize(options)
|
||||
@redis = ::Redis.new(options)
|
||||
end
|
||||
|
||||
def find_counter(key)
|
||||
@redis.get(key)
|
||||
end
|
||||
|
||||
def write_counter(key)
|
||||
@redis.set(key, 1)
|
||||
end
|
||||
|
||||
def increment_counter(key)
|
||||
@redis.incr(key)
|
||||
end
|
||||
|
||||
def expire_counter(key, timespan)
|
||||
@redis.expire(key, timespan)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
63
app/lib/shunter/base.rb
Normal file
63
app/lib/shunter/base.rb
Normal file
|
@ -0,0 +1,63 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Shunter
|
||||
class Base
|
||||
attr_accessor :user_id, :adapter
|
||||
|
||||
def initialize(options = {})
|
||||
@user_id = options[:user_id]
|
||||
adapter_klass = Shunter.default_adapter.constantize
|
||||
@adapter = adapter_klass.new(options[:conn_options])
|
||||
end
|
||||
|
||||
def user_key
|
||||
"counting_#{@user_id}"
|
||||
end
|
||||
|
||||
def blocked_user_key
|
||||
"blocked_#{@user_id}"
|
||||
end
|
||||
|
||||
def throttle
|
||||
return false if blocked?
|
||||
|
||||
valid_counter?
|
||||
end
|
||||
|
||||
def blocked?
|
||||
adapter.find_counter(blocked_user_key).present?
|
||||
end
|
||||
|
||||
def valid_counter?
|
||||
if adapter.find_counter(user_key)
|
||||
number_of_requests = adapter.increment_counter(user_key)
|
||||
if number_of_requests > allowed_requests.to_i
|
||||
init_counter(blocked_user_key)
|
||||
return false
|
||||
end
|
||||
else
|
||||
init_counter(user_key)
|
||||
end
|
||||
true
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def init_counter(key)
|
||||
adapter.write_counter(key)
|
||||
adapter.expire_counter(key, timespan)
|
||||
end
|
||||
|
||||
def allowed_requests
|
||||
Shunter.default_threshold
|
||||
end
|
||||
|
||||
def timespan
|
||||
Shunter.default_timespan
|
||||
end
|
||||
|
||||
def logger
|
||||
Shunter::BASE_LOGGER
|
||||
end
|
||||
end
|
||||
end
|
44
app/lib/shunter/integration/throttle.rb
Normal file
44
app/lib/shunter/integration/throttle.rb
Normal file
|
@ -0,0 +1,44 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'active_support/concern'
|
||||
|
||||
module Shunter
|
||||
module Integration
|
||||
module Throttle
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do |base|
|
||||
actions = base.const_defined?('THROTTLED_ACTIONS') && base.const_get('THROTTLED_ACTIONS')
|
||||
return if actions.blank?
|
||||
|
||||
around_action :throttle, only: actions
|
||||
|
||||
def throttle
|
||||
unless throttled_user.present? && Shunter.feature_enabled?
|
||||
yield if block_given?
|
||||
return
|
||||
end
|
||||
|
||||
user_id = throttled_user.id
|
||||
|
||||
shunter = Shunter::Base.new(conn_options: connection_options, user_id: user_id)
|
||||
if shunter.throttle
|
||||
logger.info "Request from #{throttled_user.class}/#{throttled_user.id} is coming through throttling"
|
||||
yield if block_given?
|
||||
else
|
||||
logger.info "Too many requests from #{throttled_user.class}/#{throttled_user.id}."
|
||||
raise Shunter::ThrottleError
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def connection_options
|
||||
Shunter::BASE_CONNECTION
|
||||
end
|
||||
|
||||
def logger
|
||||
Shunter::BASE_LOGGER
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -41,4 +41,6 @@ Rails.application.configure do
|
|||
|
||||
# If set to :null_store, Setting.x returns nil after first spec runs (database is emptied)
|
||||
config.cache_store = :memory_store
|
||||
|
||||
config.log_level = :fatal
|
||||
end
|
||||
|
|
|
@ -193,7 +193,7 @@ class EppDomainInfoBaseTest < EppTestCase
|
|||
<epp xmlns="#{Xsd::Schema.filename(for_prefix: 'epp-ee')}">
|
||||
<command>
|
||||
<info>
|
||||
<domain:info xmlns:domain="#{Xsd::Schema.filename(for_prefix: 'domain-ee')}">
|
||||
<domain:info xmlns:domain="#{Xsd::Schema.filename(for_prefix: 'domain-ee', for_version: '1.2')}">
|
||||
<domain:name>#{domain.name}</domain:name>
|
||||
</domain:info>
|
||||
</info>
|
||||
|
|
33
test/lib/shunter/base_test.rb
Normal file
33
test/lib/shunter/base_test.rb
Normal file
|
@ -0,0 +1,33 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "test_helper"
|
||||
require "action_controller"
|
||||
require "pry"
|
||||
|
||||
class BaseTest < Minitest::Test
|
||||
ENV["shunter_enabled"] = 'true'
|
||||
|
||||
def test_throttling_works_on_inclusion
|
||||
ENV["shunter_default_adapter"] = "Shunter::Adapters::Memory"
|
||||
ENV["shunter_default_threshold"] = "100"
|
||||
adapter = ENV["shunter_default_adapter"].constantize.new
|
||||
adapter.clear!
|
||||
|
||||
TestKlass.new.throttle do
|
||||
TestKlass.new.test
|
||||
end
|
||||
end
|
||||
|
||||
class TestKlass < ::ActionController::Base
|
||||
THROTTLED_ACTIONS = %i[test].freeze
|
||||
include Shunter::Integration::Throttle
|
||||
|
||||
def test
|
||||
"test"
|
||||
end
|
||||
|
||||
def throttled_user
|
||||
@throttled_user ||= OpenStruct.new(id: 1)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -18,7 +18,6 @@ require 'capybara/minitest'
|
|||
require 'webmock/minitest'
|
||||
require 'support/assertions/epp_assertions'
|
||||
require 'sidekiq/testing'
|
||||
require 'spy/integration'
|
||||
|
||||
Sidekiq::Testing.fake!
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue