response_bank 1.5.0 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +32 -1
- data/lib/response_bank/cache_policy.rb +7 -0
- data/lib/response_bank/cache_writer.rb +26 -1
- data/lib/response_bank/response_cache_handler.rb +11 -0
- data/lib/response_bank/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 54fd4c438eab18c36f842002725ec243f6ca9837f03a2d70dc594a8bf4765d8d
|
|
4
|
+
data.tar.gz: cc5512b2fa88dd622da3ef9fae6abda48f9c7122eed828dd988a17343607f0cd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9637541060aab3ac047247fdd13e83d57b99e3921689a7dd354ce684c247f9decb04de2c784e6f04bb57796e71279098a2f1a3db670d67ad25bb8406a7eb30f3
|
|
7
|
+
data.tar.gz: d3ba8c122b58152cd1e10e8d1fb504b65c2e8c906d3a862be98050f7b252129d392bbc83d0e220a2c79248f0da16bb3f85d8ae441c0c2b03281ee123a872b4bf
|
data/README.md
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
This gem supports the following versions of Ruby and Rails:
|
|
13
13
|
|
|
14
|
-
* Ruby 3.
|
|
14
|
+
* Ruby 3.3.0+
|
|
15
15
|
* Rails 6.0.0+
|
|
16
16
|
|
|
17
17
|
## Usage
|
|
@@ -169,6 +169,24 @@ The headers passed to `complete` describe the cached representation. They can di
|
|
|
169
169
|
|
|
170
170
|
`abort` is idempotent and releases an owned fill lock through `ResponseBank.release_lock`. Its default implementation is a no-op. The existing `write_to_cache` hook remains responsible for cleanup after a write attempt. An integration that releases those fills from `write_to_cache` should also implement `release_lock` for abandoned fills and failures that happen before the write hook. A key-only lock cannot prevent an old fill from releasing a replacement lock after its lease expires; integrations that need that guarantee must use owner tokens in their lock implementation.
|
|
171
171
|
|
|
172
|
+
## Cache Entry Metadata
|
|
173
|
+
|
|
174
|
+
Applications can store a Hash inside a cache entry by setting
|
|
175
|
+
`env['cacheable.metadata']` before the response is stored:
|
|
176
|
+
|
|
177
|
+
```ruby
|
|
178
|
+
env['cacheable.metadata'] = { 'variant' => 'b' }
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
On a server cache hit, `env['cacheable.metadata']` holds the Hash stored with the
|
|
182
|
+
served entry, stale or fresh; the key is removed when the entry has none. The Hash
|
|
183
|
+
is stored once and shared by every request served from the entry, so it must not
|
|
184
|
+
contain anything visitor-specific. It is serialized with MessagePack (String keys on
|
|
185
|
+
read) and nested under `ResponseBank::APP_METADATA_KEY`, so it cannot collide with
|
|
186
|
+
ResponseBank's own slots. Keep it to plain MessagePack types: a Hash that does not
|
|
187
|
+
survive a MessagePack round-trip, because a value cannot be serialized or the nesting
|
|
188
|
+
is too deep for the unpacker, is logged and dropped, and the response is cached without it.
|
|
189
|
+
|
|
172
190
|
## Brotli Splice Slots
|
|
173
191
|
|
|
174
192
|
Applications that need per-request replacement inside cached Brotli HTML responses can pass an injector builder to `ResponseBank::Middleware`:
|
|
@@ -287,6 +305,19 @@ Advanced integrations can still install the per-request injector directly in the
|
|
|
287
305
|
env[ResponseBank::BrotliSpliceSlot::INJECTOR_ENV_KEY] = injector
|
|
288
306
|
```
|
|
289
307
|
|
|
308
|
+
## Observing server cache hits
|
|
309
|
+
|
|
310
|
+
After successfully serving an entry from the server cache, ResponseBank exposes its generation time and stale-while-revalidate status in the Rack environment:
|
|
311
|
+
|
|
312
|
+
```ruby
|
|
313
|
+
timestamp = env['cacheable.timestamp']
|
|
314
|
+
stale = env['cacheable.stale']
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
`cacheable.timestamp` is only set after successfully serving a server-cache entry.
|
|
318
|
+
`cacheable.stale` is initialized to `false` when a server-cache entry is evaluated
|
|
319
|
+
and set to `true` only when ResponseBank serves it through stale-while-revalidate.
|
|
320
|
+
|
|
290
321
|
## Exception Handling
|
|
291
322
|
|
|
292
323
|
ResponseBank handles all exceptions gracefully during cache operations. If an exception occurs while reading from cache, deserializing cached data, or writing to cache, the middleware will:
|
|
@@ -3,4 +3,11 @@
|
|
|
3
3
|
module ResponseBank
|
|
4
4
|
CACHEABLE_HEADERS = ["Location", "Content-Type", "ETag", "Content-Encoding", "Last-Modified", "Cache-Control", "Expires", "Link", "Surrogate-Keys", "Cache-Tags", "Speculation-Rules"].freeze
|
|
5
5
|
CACHEABLE_STATUSES = [200, 404, 301].freeze
|
|
6
|
+
|
|
7
|
+
# Application metadata stored inside the cache entry: set `env[METADATA_ENV_KEY]`
|
|
8
|
+
# (a Hash) before the response is stored; on a server cache hit it holds the Hash
|
|
9
|
+
# stored with the served entry.
|
|
10
|
+
METADATA_ENV_KEY = 'cacheable.metadata'
|
|
11
|
+
# Keeps application metadata apart from ResponseBank's own slots.
|
|
12
|
+
APP_METADATA_KEY = 'app'
|
|
6
13
|
end
|
|
@@ -157,9 +157,34 @@ module ResponseBank
|
|
|
157
157
|
end
|
|
158
158
|
cached_headers = representation_headers.slice(*ResponseBank::CACHEABLE_HEADERS)
|
|
159
159
|
data = [status, cached_headers, stored.compressed_body, timestamp, env['cacheable.compression_level']]
|
|
160
|
-
|
|
160
|
+
metadata = entry_metadata(env, stored.metadata)
|
|
161
|
+
data << metadata if metadata
|
|
161
162
|
data
|
|
162
163
|
end
|
|
164
|
+
|
|
165
|
+
# Application metadata never fails the write, nor the reads that follow: anything
|
|
166
|
+
# that is not a Hash, or that does not round-trip through MessagePack, is logged
|
|
167
|
+
# and left out of the entry.
|
|
168
|
+
def entry_metadata(env, metadata)
|
|
169
|
+
app_metadata = env[ResponseBank::METADATA_ENV_KEY]
|
|
170
|
+
return metadata if app_metadata.nil?
|
|
171
|
+
unless app_metadata.is_a?(Hash)
|
|
172
|
+
ResponseBank.log("Ignoring #{ResponseBank::METADATA_ENV_KEY}: expected a Hash, got #{app_metadata.class}")
|
|
173
|
+
return metadata
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
merged = (metadata || {}).merge(ResponseBank::APP_METADATA_KEY => app_metadata)
|
|
177
|
+
begin
|
|
178
|
+
# Loading catches what dumping accepts but the reader rejects, such as nesting
|
|
179
|
+
# past the unpacker's stack; the array puts the Hash at its depth in the entry.
|
|
180
|
+
MessagePack.load(MessagePack.dump([merged]))
|
|
181
|
+
rescue StandardError => error
|
|
182
|
+
ResponseBank.log("Ignoring #{ResponseBank::METADATA_ENV_KEY}: #{error.class} - #{error.message}")
|
|
183
|
+
return metadata
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
merged
|
|
187
|
+
end
|
|
163
188
|
end
|
|
164
189
|
end
|
|
165
190
|
end
|
|
@@ -133,6 +133,7 @@ module ResponseBank
|
|
|
133
133
|
@env['cacheable.compression_level'] = compression_level
|
|
134
134
|
|
|
135
135
|
@env['cacheable.locked'] ||= false
|
|
136
|
+
@env['cacheable.stale'] = false
|
|
136
137
|
|
|
137
138
|
# to preserve the unversioned/versioned logging messages from past releases we split the match_entity_tag test
|
|
138
139
|
if match_entity_tag == "*"
|
|
@@ -149,6 +150,7 @@ module ResponseBank
|
|
|
149
150
|
return
|
|
150
151
|
elsif stale_while_revalidate?(timestamp, cache_age_tolerance)
|
|
151
152
|
# cache is being regenerated, can we avoid piling on and use a stale version in the interim?
|
|
153
|
+
@env['cacheable.stale'] = true
|
|
152
154
|
ResponseBank.log("Cache hit: server (recent)")
|
|
153
155
|
else
|
|
154
156
|
ResponseBank.log("Found an unversioned cache entry, but it was too old (#{timestamp})")
|
|
@@ -176,6 +178,15 @@ module ResponseBank
|
|
|
176
178
|
ResponseBank.log("Cache hit, but missing content-encoding in the cache value headers, maybe because of 301 or 404 response or empty body string")
|
|
177
179
|
end
|
|
178
180
|
|
|
181
|
+
# After decompression and splicing, so a refill never inherits the rejected
|
|
182
|
+
# entry's metadata; cleared when the served entry has none.
|
|
183
|
+
if metadata&.key?(ResponseBank::APP_METADATA_KEY)
|
|
184
|
+
@env[ResponseBank::METADATA_ENV_KEY] = metadata[ResponseBank::APP_METADATA_KEY]
|
|
185
|
+
else
|
|
186
|
+
@env.delete(ResponseBank::METADATA_ENV_KEY)
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
@env['cacheable.timestamp'] = timestamp
|
|
179
190
|
[status, @headers, [body]]
|
|
180
191
|
|
|
181
192
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: response_bank
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.7.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Tobias Lütke
|
|
@@ -169,14 +169,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
169
169
|
requirements:
|
|
170
170
|
- - ">="
|
|
171
171
|
- !ruby/object:Gem::Version
|
|
172
|
-
version: 3.
|
|
172
|
+
version: 3.3.0
|
|
173
173
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
174
174
|
requirements:
|
|
175
175
|
- - ">="
|
|
176
176
|
- !ruby/object:Gem::Version
|
|
177
177
|
version: '0'
|
|
178
178
|
requirements: []
|
|
179
|
-
rubygems_version: 4.0.
|
|
179
|
+
rubygems_version: 4.0.21
|
|
180
180
|
specification_version: 4
|
|
181
181
|
summary: Simple response caching for Ruby applications
|
|
182
182
|
test_files: []
|