Skip to content

Commit d6bc89e

Browse files
committed
Move from proxies to well-defined adapters
1 parent a7ce9a8 commit d6bc89e

34 files changed

+325
-531
lines changed

.rubocop.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,3 +97,7 @@ Style/SpecialGlobalVars:
9797

9898
Style/UnneededPercentQ:
9999
Enabled: true
100+
101+
Lint/UnusedMethodArgument:
102+
Exclude:
103+
- "lib/rack/attack/store_adapter.rb"

lib/rack/attack.rb

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@
66
require 'rack/attack/configuration'
77
require 'rack/attack/path_normalizer'
88
require 'rack/attack/request'
9-
require 'rack/attack/store_proxy/dalli_proxy'
10-
require 'rack/attack/store_proxy/mem_cache_store_proxy'
11-
require 'rack/attack/store_proxy/redis_proxy'
12-
require 'rack/attack/store_proxy/redis_store_proxy'
13-
require 'rack/attack/store_proxy/redis_cache_store_proxy'
14-
require 'rack/attack/store_proxy/active_support_redis_store_proxy'
9+
require 'rack/attack/store_adapters/dalli_adapter'
10+
require 'rack/attack/store_adapters/mem_cache_store_adapter'
11+
require 'rack/attack/store_adapters/redis_adapter'
12+
require 'rack/attack/store_adapters/redis_store_adapter'
13+
require 'rack/attack/store_adapters/redis_cache_store_adapter'
14+
require 'rack/attack/store_adapters/active_support_redis_store_adapter'
1515

1616
require 'rack/attack/railtie' if defined?(::Rails)
1717

lib/rack/attack/base_proxy.rb

Lines changed: 0 additions & 27 deletions
This file was deleted.

lib/rack/attack/cache.rb

Lines changed: 13 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,16 @@ def initialize
1313

1414
attr_reader :store
1515
def store=(store)
16-
@store =
17-
if (proxy = BaseProxy.lookup(store))
18-
proxy.new(store)
19-
else
20-
store
21-
end
16+
raise Rack::Attack::MissingStoreError if store.nil?
17+
18+
adapter = StoreAdapter.lookup(store)
19+
if adapter
20+
@store = adapter.new(store)
21+
elsif store?(store)
22+
@store = store
23+
else
24+
raise Rack::Attack::MisconfiguredStoreError
25+
end
2226
end
2327

2428
def count(unprefixed_key, period)
@@ -27,9 +31,6 @@ def count(unprefixed_key, period)
2731
end
2832

2933
def read(unprefixed_key)
30-
enforce_store_presence!
31-
enforce_store_method_presence!(:read)
32-
3334
store.read("#{prefix}:#{unprefixed_key}")
3435
end
3536

@@ -67,33 +68,19 @@ def key_and_expiry(unprefixed_key, period)
6768
end
6869

6970
def do_count(key, expires_in)
70-
enforce_store_presence!
71-
enforce_store_method_presence!(:increment)
72-
7371
result = store.increment(key, 1, expires_in: expires_in)
7472

7573
# NB: Some stores return nil when incrementing uninitialized values
7674
if result.nil?
77-
enforce_store_method_presence!(:write)
78-
7975
store.write(key, 1, expires_in: expires_in)
8076
end
8177
result || 1
8278
end
8379

84-
def enforce_store_presence!
85-
if store.nil?
86-
raise Rack::Attack::MissingStoreError
87-
end
88-
end
80+
STORE_METHODS = [:read, :write, :increment, :delete].freeze
8981

90-
def enforce_store_method_presence!(method_name)
91-
if !store.respond_to?(method_name)
92-
raise(
93-
Rack::Attack::MisconfiguredStoreError,
94-
"Configured store #{store.class.name} doesn't respond to ##{method_name} method"
95-
)
96-
end
82+
def store?(object)
83+
STORE_METHODS.all? { |meth| object.respond_to?(meth) }
9784
end
9885
end
9986
end

lib/rack/attack/store_adapter.rb

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# frozen_string_literal: true
2+
3+
module Rack
4+
class Attack
5+
class StoreAdapter
6+
class << self
7+
def adapters
8+
@@adapters ||= []
9+
end
10+
11+
def inherited(klass)
12+
adapters << klass
13+
end
14+
15+
def lookup(store)
16+
adapters.find { |adapter| adapter.handle?(store) }
17+
end
18+
19+
def handle?(store)
20+
raise NotImplementedError
21+
end
22+
end
23+
24+
attr_reader :store
25+
26+
def initialize(store)
27+
@store = store
28+
end
29+
30+
def read(key)
31+
raise NotImplementedError
32+
end
33+
34+
def write(key, value, options = {})
35+
raise NotImplementedError
36+
end
37+
38+
def increment(key, amount, options = {})
39+
raise NotImplementedError
40+
end
41+
42+
def delete(key, options = {})
43+
raise NotImplementedError
44+
end
45+
end
46+
end
47+
end
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# frozen_string_literal: true
2+
3+
require 'rack/attack/store_adapter'
4+
5+
module Rack
6+
class Attack
7+
module StoreAdapters
8+
class ActiveSupportRedisStoreAdapter < StoreAdapter
9+
def self.handle?(store)
10+
defined?(::Redis) &&
11+
defined?(::ActiveSupport::Cache::RedisStore) &&
12+
store.is_a?(::ActiveSupport::Cache::RedisStore)
13+
end
14+
15+
def read(key, options = {})
16+
store.read(key, options.merge!(raw: true))
17+
end
18+
19+
def write(key, value, options = {})
20+
store.write(key, value, options.merge!(raw: true))
21+
end
22+
23+
def increment(key, amount = 1, options = {})
24+
# #increment ignores options[:expires_in].
25+
#
26+
# So in order to workaround this we use #write (which sets expiration) to initialize
27+
# the counter. After that we continue using the original #increment.
28+
if options[:expires_in] && !read(key)
29+
write(key, amount, options)
30+
31+
amount
32+
else
33+
store.increment(key, amount, options)
34+
end
35+
end
36+
37+
def delete(key, options = {})
38+
store.delete(key, options)
39+
end
40+
41+
def delete_matched(matcher, options = nil)
42+
store.delete_matched(matcher, options)
43+
end
44+
end
45+
end
46+
end
47+
end

lib/rack/attack/store_proxy/dalli_proxy.rb renamed to lib/rack/attack/store_adapters/dalli_adapter.rb

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
# frozen_string_literal: true
22

3-
require 'rack/attack/base_proxy'
3+
require 'rack/attack/store_adapter'
44

55
module Rack
66
class Attack
7-
module StoreProxy
8-
class DalliProxy < BaseProxy
7+
module StoreAdapters
8+
class DalliAdapter < StoreAdapter
99
def self.handle?(store)
1010
return false unless defined?(::Dalli)
1111

@@ -18,38 +18,38 @@ def self.handle?(store)
1818
end
1919
end
2020

21-
def initialize(client)
22-
super(client)
21+
def initialize(store)
22+
super
2323
stub_with_if_missing
2424
end
2525

2626
def read(key)
2727
rescuing do
28-
with do |client|
28+
store.with do |client|
2929
client.get(key)
3030
end
3131
end
3232
end
3333

3434
def write(key, value, options = {})
3535
rescuing do
36-
with do |client|
36+
store.with do |client|
3737
client.set(key, value, options.fetch(:expires_in, 0), raw: true)
3838
end
3939
end
4040
end
4141

4242
def increment(key, amount, options = {})
4343
rescuing do
44-
with do |client|
44+
store.with do |client|
4545
client.incr(key, amount, options.fetch(:expires_in, 0), amount)
4646
end
4747
end
4848
end
4949

5050
def delete(key)
5151
rescuing do
52-
with do |client|
52+
store.with do |client|
5353
client.delete(key)
5454
end
5555
end
@@ -58,10 +58,10 @@ def delete(key)
5858
private
5959

6060
def stub_with_if_missing
61-
unless __getobj__.respond_to?(:with)
62-
class << self
61+
unless store.respond_to?(:with)
62+
class << store
6363
def with
64-
yield __getobj__
64+
yield store
6565
end
6666
end
6767
end

lib/rack/attack/store_proxy/mem_cache_store_proxy.rb renamed to lib/rack/attack/store_adapters/mem_cache_store_adapter.rb

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,22 @@
11
# frozen_string_literal: true
22

3-
require 'rack/attack/base_proxy'
3+
require 'forwardable'
44

55
module Rack
66
class Attack
7-
module StoreProxy
8-
class MemCacheStoreProxy < BaseProxy
7+
module StoreAdapters
8+
class MemCacheStoreAdapter < StoreAdapter
99
def self.handle?(store)
1010
defined?(::Dalli) &&
1111
defined?(::ActiveSupport::Cache::MemCacheStore) &&
1212
store.is_a?(::ActiveSupport::Cache::MemCacheStore)
1313
end
1414

15-
def write(name, value, options = {})
16-
super(name, value, options.merge!(raw: true))
15+
extend Forwardable
16+
def_delegators :@store, :read, :increment, :delete
17+
18+
def write(key, value, options = {})
19+
store.write(key, value, options.merge!(raw: true))
1720
end
1821
end
1922
end

lib/rack/attack/store_proxy/redis_proxy.rb renamed to lib/rack/attack/store_adapters/redis_adapter.rb

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,46 @@
11
# frozen_string_literal: true
22

3-
require 'rack/attack/base_proxy'
3+
require 'rack/attack/store_adapter'
44

55
module Rack
66
class Attack
7-
module StoreProxy
8-
class RedisProxy < BaseProxy
9-
def initialize(*args)
7+
module StoreAdapters
8+
class RedisAdapter < StoreAdapter
9+
def initialize(store)
1010
if Gem::Version.new(Redis::VERSION) < Gem::Version.new("3")
1111
warn 'RackAttack requires Redis gem >= 3.0.0.'
1212
end
1313

14-
super(*args)
14+
super
1515
end
1616

1717
def self.handle?(store)
1818
defined?(::Redis) && store.class == ::Redis
1919
end
2020

2121
def read(key)
22-
rescuing { get(key) }
22+
rescuing { store.get(key) }
2323
end
2424

2525
def write(key, value, options = {})
2626
if (expires_in = options[:expires_in])
27-
rescuing { setex(key, expires_in, value) }
27+
rescuing { store.setex(key, expires_in, value) }
2828
else
29-
rescuing { set(key, value) }
29+
rescuing { store.set(key, value) }
3030
end
3131
end
3232

3333
def increment(key, amount, options = {})
3434
rescuing do
35-
pipelined do
36-
incrby(key, amount)
37-
expire(key, options[:expires_in]) if options[:expires_in]
35+
store.pipelined do
36+
store.incrby(key, amount)
37+
store.expire(key, options[:expires_in]) if options[:expires_in]
3838
end.first
3939
end
4040
end
4141

4242
def delete(key, _options = {})
43-
rescuing { del(key) }
43+
rescuing { store.del(key) }
4444
end
4545

4646
def delete_matched(matcher, _options = nil)
@@ -49,8 +49,8 @@ def delete_matched(matcher, _options = nil)
4949
rescuing do
5050
# Fetch keys in batches using SCAN to avoid blocking the Redis server.
5151
loop do
52-
cursor, keys = scan(cursor, match: matcher, count: 1000)
53-
del(*keys) unless keys.empty?
52+
cursor, keys = store.scan(cursor, match: matcher, count: 1000)
53+
store.del(*keys) unless keys.empty?
5454
break if cursor == "0"
5555
end
5656
end

0 commit comments

Comments
 (0)