Skip to content

Commit c53990a

Browse files
author
Martín Peñaloza
committed
docs: Document bypassable_store_errors and lock proxy defaults via specs
Adds cross-proxy specs that pin the defaults (DalliProxy, RedisProxy, RedisStoreProxy, RedisCacheStoreProxy, MemCacheStoreProxy, BaseProxy) and ensures class-name defaults stay Strings. Also documents the new Cache#bypassable_store_errors= option with :all/:none/Array examples.
1 parent 7f5ee1a commit c53990a

2 files changed

Lines changed: 65 additions & 0 deletions

File tree

README.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,35 @@ Most applications should use a new, separate database used only for `rack-attack
315315

316316
Note that `Rack::Attack.cache` is only used for throttling, allow2ban and fail2ban filtering; not blocklisting and safelisting. Your cache store must implement `increment` and `write` like [ActiveSupport::Cache::Store](http://api.rubyonrails.org/classes/ActiveSupport/Cache/Store.html). This means that other cache stores which inherit from ActiveSupport::Cache::Store are also compatible. In-memory stores which are not backed by an external database, such as `ActiveSupport::Cache::MemoryStore.new`, will be mostly ineffective because each Ruby process in your deployment will have it's own state, effectively multiplying the number of requests each client can make by the number of Ruby processes you have deployed.
317317

318+
#### Bypassing store errors
319+
320+
By default, some store proxies will swallow the errors they historically rescued (`Redis::BaseConnectionError` for `Redis`, `Dalli::DalliError` for `Dalli`). When one of those errors is raised inside the proxy, the request goes through as if no throttling were applied, which keeps your app available if the dedicated rack-attack store goes down.
321+
322+
You can customize this behavior through `Rack::Attack.cache.bypassable_store_errors`:
323+
324+
```ruby
325+
# Use the proxy's built-in defaults (this is the default)
326+
Rack::Attack.cache.store = ActiveSupport::Cache::RedisCacheStore.new(url: "...")
327+
328+
# Bypass ALL errors raised by the store — requests continue serving even if the
329+
# store misbehaves in unexpected ways (e.g. Redis OOM, timeouts, protocol errors).
330+
Rack::Attack.cache.bypassable_store_errors = :all
331+
332+
# Bypass NO errors — any error from the store will propagate. This disables the
333+
# proxy's historical default rescue behavior as well.
334+
Rack::Attack.cache.bypassable_store_errors = :none
335+
336+
# Bypass a specific list of error classes (or class-name Strings). This REPLACES
337+
# the proxy's built-in defaults - include any you still want to rescue.
338+
Rack::Attack.cache.bypassable_store_errors = [
339+
Redis::BaseConnectionError,
340+
Redis::TimeoutError,
341+
"Redis::CommandError"
342+
]
343+
```
344+
345+
`bypassable_store_errors` can be set before or after assigning `cache.store`; the store is re-wrapped automatically.
346+
318347
## Customizing responses
319348

320349
Customize the response of blocklisted and throttled requests using an object that adheres to the [Rack app interface](http://www.rubydoc.info/github/rack/rack/file/SPEC.rdoc).
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# frozen_string_literal: true
2+
3+
require_relative 'spec_helper'
4+
5+
describe "Store proxy defaults (String-based)" do
6+
it "RedisStoreProxy inherits Redis default bypass list from RedisProxy" do
7+
skip 'redis gem not available' unless defined?(::Redis)
8+
_(Rack::Attack::StoreProxy::RedisStoreProxy.default_bypassable_store_errors)
9+
.must_equal ['Redis::BaseConnectionError']
10+
end
11+
12+
it "RedisCacheStoreProxy has no default bypass list (preserves upstream behavior)" do
13+
_(Rack::Attack::StoreProxy::RedisCacheStoreProxy.default_bypassable_store_errors).must_equal []
14+
end
15+
16+
it "MemCacheStoreProxy has no default bypass list (preserves upstream behavior)" do
17+
_(Rack::Attack::StoreProxy::MemCacheStoreProxy.default_bypassable_store_errors).must_equal []
18+
end
19+
20+
it "BaseProxy base default is an empty Array" do
21+
_(Rack::Attack::BaseProxy.default_bypassable_store_errors).must_equal []
22+
end
23+
24+
it "default list entries are Strings so missing gems don't break loading" do
25+
[
26+
Rack::Attack::StoreProxy::DalliProxy,
27+
Rack::Attack::StoreProxy::RedisProxy
28+
].each do |proxy_class|
29+
defaults = proxy_class.default_bypassable_store_errors
30+
_(defaults.all? { |e| e.is_a?(String) }).must_equal(
31+
true,
32+
"#{proxy_class}: expected all defaults to be Strings, got #{defaults.inspect}"
33+
)
34+
end
35+
end
36+
end

0 commit comments

Comments
 (0)