rack-attack浅析
配置
一般配置文件这样写
Rack::Attack.throttle("requests by ip", limit: 5, period: 2) do |request|
request.ip
end
在
Rack::Attack
上调用的throttle
等配置方法,其实都来自于Rack::Attack::Configuration
实例# rack-attack-6.6.1/lib/rack/attack.rb
module Rack
class Attack
class << self
extend Forwardable
def_delegators(
:@configuration,
# ...
:throttle
)
end
@configuration = Configuration.new
end
end
end
然后
Rack::Attack#call
时,会读取Rack::Attack::Configuration
实例的配置做相应处理module Rack
class Attack
def initialize(app)
@app = app
@configuration = self.class.configuration
end
def call(env)
return @app.call(env) if !self.class.enabled || env["rack.attack.called"]
env["rack.attack.called"] = true
env['PATH_INFO'] = PathNormalizer.normalize_path(env['PATH_INFO'])
request = Rack::Attack::Request.new(env)
if configuration.safelisted?(request)
# ...
elsif configuration.blocklisted?(request)
# ...
elsif configuration.throttled?(request)
# ...
else
configuration.tracked?(request)
@app.call(env)
end
end
end
end
限流
重新开放的时间点是固定的
@last_epoch_time / period
module Rack
class Attack
class Cache
def count(unprefixed_key, period)
key, expires_in = key_and_expiry(unprefixed_key, period)
do_count(key, expires_in)
end
def key_and_expiry(unprefixed_key, period)
@last_epoch_time = Time.now.to_i
# Add 1 to expires_in to avoid timing error: https://git.io/i1PHXA
expires_in = (period - (@last_epoch_time % period) + 1).to_i
["#{prefix}:#{(@last_epoch_time / period).to_i}:#{unprefixed_key}", expires_in]
end
def do_count(key, expires_in)
# ...
result = store.increment(key, 1, expires_in: expires_in)
# ...
result || 1
end
end
end
end
缓存
非Rails项目,需要注意调用一下
Rack::Attack.cache.store=
来设置缓存即使是Rails项目,也要注意dev环境下是否用了
ActiveSupport::Cache::NullStore
module Rack
class Attack
class Cache
def initialize
self.store = ::Rails.cache if defined?(::Rails.cache)
@prefix = 'rack::attack'
end
attr_reader :store
def store=(store)
@store =
if (proxy = BaseProxy.lookup(store))
proxy.new(store)
else
store
end
end
end
end
end
end