response_bank 1.1.0 → 1.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2a5d65c4fd4310708e807cde576efeebdc590c16eb8e16190aef77a715ed48c2
4
- data.tar.gz: 498da7cd7cfcc2da09b517e6694093551e79288ec4e52e815950448353ba4a0f
3
+ metadata.gz: 7d25739f9bd846e1cd3364a422cc90590b32fcb5f7e56cd307af0e83f0d5ddac
4
+ data.tar.gz: 14c1a7f96abcc45025cee9f998342fb6185779774dc72986ebb1a5bbcb96c413
5
5
  SHA512:
6
- metadata.gz: 5985c959c55ebf8917d9687dd0e3c3d82cae36684f7466d7c1d1805097da2fd57cadaa6efa9f7d618a757e24949a7e493aefe0e298f5c0254d9b595f7871bba8
7
- data.tar.gz: 5bc8ac60f2d2ce149120a8f0bc98aca57899511359f5a67a125b6cc3894edf0043dffad8be635ae9fa95a9d1b295220a091b0d2faa118982f9740eac5e3d6786
6
+ metadata.gz: b6120bc7a707fa5ddd1cd3a143e40681e375864b075fe0e74b1128cc4ee460690400a70c47599772addba8d2bee56b02ce14a820c5e4af376f0eaa154f1fe88c
7
+ data.tar.gz: 5ae1545f2c40d7abbf06b8ba43c8d353cb171fc97ce667fa280c5f991c37f7e514c7673898a99242e446f9975ef22f4cb91eaa053165664a9c309fa4d6b39197
data/README.md CHANGED
@@ -1,126 +1,128 @@
1
- # ResponseBank [![Build Status](https://secure.travis-ci.org/Shopify/response_bank.png)](http://travis-ci.org/Shopify/response_bank)
1
+ # ResponseBank [![Build Status](https://secure.travis-ci.org/Shopify/response_bank.png)](http://travis-ci.org/Shopify/response_bank) [![CI Status](https://github.com/Shopify/response_bank/actions/workflows/ci.yml/badge.svg)](https://github.com/Shopify/response_bank/actions/workflows/ci.yml)
2
2
 
3
- ### Features
3
+ ## Features
4
4
 
5
5
  * Serve gzip'd content
6
6
  * Add ETag and 304 Not Modified headers
7
7
  * Generational caching
8
8
  * No explicit expiry
9
9
 
10
- ### Support
10
+ ## Support
11
11
 
12
12
  This gem supports the following versions of Ruby and Rails:
13
13
 
14
- * Ruby 2.4.0+
15
- * Rails 5.0.0+
14
+ * Ruby 2.7.0+
15
+ * Rails 6.0.0+
16
16
 
17
- ### Usage
17
+ ## Usage
18
18
 
19
19
  1. include the gem in your Gemfile
20
20
 
21
- ```ruby
22
- gem 'response_bank'
23
- ```
21
+ ```ruby
22
+ gem 'response_bank'
23
+ ```
24
24
 
25
25
  2. add an initializer file. We need to configure the `acquire_lock` method, set the cache store and the logger
26
26
 
27
- ```ruby
28
- require 'response_bank'
27
+ ```ruby
28
+ require 'response_bank'
29
29
 
30
- module ResponseBank
31
- LOCK_TTL = 90
30
+ module ResponseBank
31
+ LOCK_TTL = 90
32
32
 
33
- class << self
34
- def acquire_lock(cache_key)
35
- cache_store.write("#{cache_key}:lock", '1', unless_exist: true, expires_in: LOCK_TTL, raw: true)
33
+ class << self
34
+ def acquire_lock(cache_key)
35
+ cache_store.write("#{cache_key}:lock", '1', unless_exist: true, expires_in: LOCK_TTL, raw: true)
36
+ end
37
+ end
36
38
  end
37
- end
38
- end
39
39
 
40
- ResponseBank.cache_store = ActiveSupport::Cache.lookup_store(Rails.configuration.cache_store)
41
- ResponseBank.logger = Rails.logger
40
+ ResponseBank.cache_store = ActiveSupport::Cache.lookup_store(Rails.configuration.cache_store)
41
+ ResponseBank.logger = Rails.logger
42
42
 
43
- ```
43
+ ```
44
44
 
45
45
  3. enables caching on your application
46
- ```ruby
47
- config.action_controller.perform_caching = true
48
- ```
46
+
47
+ ```ruby
48
+ config.action_controller.perform_caching = true
49
+ ```
49
50
 
50
51
  4. use `#response_cache` method to any desired controller's action
51
52
 
52
- ```ruby
53
- class PostsController < ApplicationController
54
- def show
55
- response_cache do
56
- @post = @shop.posts.find(params[:id])
57
- respond_with(@post)
53
+ ```ruby
54
+ class PostsController < ApplicationController
55
+ def show
56
+ response_cache do
57
+ @post = @shop.posts.find(params[:id])
58
+ respond_with(@post)
59
+ end
60
+ end
58
61
  end
59
- end
60
- end
61
- ```
62
+ ```
62
63
 
63
64
  5. **(optional)** set a custom TTL for the cache by overriding the `write_to_backing_cache_store` method in your initializer file
64
- ```ruby
65
- module ResponseBank
66
- CACHE_TTL = 30.minutes
67
- def write_to_backing_cache_store(_env, key, payload, expires_in: CACHE_TTL)
68
- cache_store.write(key, payload, raw: true, expires_in: expires_in)
69
- end
70
- end
71
- ```
72
65
 
73
- 6. **(optional)** override custom cache key data. For default, cache key is defined by URL and query string
66
+ ```ruby
67
+ module ResponseBank
68
+ CACHE_TTL = 30.minutes
69
+ def write_to_backing_cache_store(_env, key, payload, expires_in: nil)
70
+ cache_store.write(key, payload, raw: true, expires_in: expires_in || CACHE_TTL)
71
+ end
72
+ end
73
+ ```
74
74
 
75
- ```ruby
76
- class PostsController < ApplicationController
77
- before_action :set_shop
75
+ 6. **(optional)** override custom cache key data. For default, cache key is defined by URL and query string
78
76
 
79
- def index
80
- response_cache do
81
- @post = @shop.posts
82
- respond_with(@post)
77
+ ```ruby
78
+ class PostsController < ApplicationController
79
+ before_action :set_shop
80
+
81
+ def index
82
+ response_cache do
83
+ @post = @shop.posts
84
+ respond_with(@post)
85
+ end
86
+ end
87
+
88
+ def show
89
+ response_cache do
90
+ @post = @shop.posts.find(params[:id])
91
+ respond_with(@post)
92
+ end
93
+ end
94
+
95
+ def another_action
96
+ # custom cache key data
97
+ cache_key = {
98
+ action: action_name,
99
+ format: request.format,
100
+ shop_updated_at: @shop.updated_at
101
+ # you may add more keys here
102
+ }
103
+ response_cache cache_key do
104
+ @post = @shop.posts.find(params[:id])
105
+ respond_with(@post)
106
+ end
107
+ end
108
+
109
+ # override default cache key data globally per class
110
+ def cache_key_data
111
+ {
112
+ action: action_name,
113
+ format: request.format,
114
+ params: params.slice(:id),
115
+ shop_version: @shop.version
116
+ # you may add more keys here
117
+ }
118
+ end
119
+
120
+ def set_shop
121
+ # @shop = ...
122
+ end
83
123
  end
84
- end
124
+ ```
85
125
 
86
- def show
87
- response_cache do
88
- @post = @shop.posts.find(params[:id])
89
- respond_with(@post)
90
- end
91
- end
92
-
93
- def another_action
94
- # custom cache key data
95
- cache_key = {
96
- action: action_name,
97
- format: request.format,
98
- shop_updated_at: @shop.updated_at
99
- # you may add more keys here
100
- }
101
- response_cache cache_key do
102
- @post = @shop.posts.find(params[:id])
103
- respond_with(@post)
104
- end
105
- end
106
-
107
- # override default cache key data globally per class
108
- def cache_key_data
109
- {
110
- action: action_name,
111
- format: request.format,
112
- params: params.slice(:id),
113
- shop_version: @shop.version
114
- # you may add more keys here
115
- }
116
- end
117
-
118
- def set_shop
119
- # @shop = ...
120
- end
121
- end
122
- ```
123
-
124
- ### License
126
+ ## License
125
127
 
126
128
  ResponseBank is released under the [MIT License](LICENSE.txt).
@@ -1,8 +1,11 @@
1
1
  # frozen_string_literal: true
2
- require 'useragent'
3
2
 
4
3
  module ResponseBank
5
4
  class Middleware
5
+ # Limit the cached headers
6
+ # TODO: Make this lowercase/case-insentitive as per rfc2616 §4.2
7
+ CACHEABLE_HEADERS = ["Location", "Content-Type", "ETag", "Content-Encoding", "Last-Modified", "Cache-Control", "Expires", "Link", "Surrogate-Keys", "Cache-Tags"].freeze
8
+
6
9
  REQUESTED_WITH = "HTTP_X_REQUESTED_WITH"
7
10
  ACCEPT = "HTTP_ACCEPT"
8
11
  USER_AGENT = "HTTP_USER_AGENT"
@@ -13,18 +16,14 @@ module ResponseBank
13
16
 
14
17
  def call(env)
15
18
  env['cacheable.cache'] = false
16
- gzip = env['gzip'] = env['HTTP_ACCEPT_ENCODING'].to_s.include?("gzip")
19
+ content_encoding = env['response_bank.server_cache_encoding'] = ResponseBank.check_encoding(env)
17
20
 
18
21
  status, headers, body = @app.call(env)
19
22
 
20
23
  if env['cacheable.cache']
21
24
  if [200, 404, 301, 304].include?(status)
22
25
  headers['ETag'] = env['cacheable.key']
23
- headers['X-Alternate-Cache-Key'] = env['cacheable.unversioned-key']
24
26
 
25
- if ie_ajax_request?(env)
26
- headers["Expires"] = "-1"
27
- end
28
27
  end
29
28
 
30
29
  if [200, 404, 301].include?(status) && env['cacheable.miss']
@@ -36,31 +35,35 @@ module ResponseBank
36
35
  body.each { |part| body_string << part }
37
36
  end
38
37
 
39
- body_gz = ResponseBank.compress(body_string)
38
+ body_compressed = nil
39
+ if body_string && body_string != ""
40
+ headers['Content-Encoding'] = content_encoding
41
+ body_compressed = ResponseBank.compress(body_string, content_encoding)
42
+ end
40
43
 
44
+ cached_headers = headers.slice(*CACHEABLE_HEADERS)
41
45
  # Store result
42
- cache_data = [status, headers['Content-Type'], body_gz, timestamp]
43
- cache_data << headers['Location'] if status == 301
46
+ cache_data = [status, cached_headers, body_compressed, timestamp]
44
47
 
45
48
  ResponseBank.write_to_cache(env['cacheable.key']) do
46
49
  payload = MessagePack.dump(cache_data)
47
50
  ResponseBank.write_to_backing_cache_store(
48
51
  env,
49
- env['cacheable.key'],
52
+ env['cacheable.unversioned-key'],
50
53
  payload,
51
54
  expires_in: env['cacheable.versioned-cache-expiry'],
52
55
  )
53
-
54
- if env['cacheable.unversioned-key']
55
- ResponseBank.write_to_backing_cache_store(env, env['cacheable.unversioned-key'], payload)
56
- end
57
56
  end
58
57
 
59
- # since we had to generate the gz version above already we may
58
+ # since we had to generate the compressed version already we may
60
59
  # as well serve it if the client wants it
61
- if gzip
62
- headers['Content-Encoding'] = "gzip"
63
- body = [body_gz]
60
+ if body_compressed
61
+ if env['HTTP_ACCEPT_ENCODING'].to_s.include?(content_encoding)
62
+ body = [body_compressed]
63
+ else
64
+ # Remove content-encoding header for response with compressed content
65
+ headers.delete('Content-Encoding')
66
+ end
64
67
  end
65
68
  end
66
69
 
@@ -80,14 +83,5 @@ module ResponseBank
80
83
  Time.now.to_i
81
84
  end
82
85
 
83
- def ie_ajax_request?(env)
84
- return false unless !env[USER_AGENT].nil? && !env[USER_AGENT].empty?
85
-
86
- if env[REQUESTED_WITH] == "XmlHttpRequest" || env[ACCEPT] == "application/json"
87
- UserAgent.parse(env["HTTP_USER_AGENT"]).is_a?(UserAgent::Browsers::InternetExplorer)
88
- else
89
- false
90
- end
91
- end
92
86
  end
93
87
  end
@@ -3,6 +3,8 @@ require 'digest/md5'
3
3
 
4
4
  module ResponseBank
5
5
  class ResponseCacheHandler
6
+ CACHE_KEY_SCHEMA_VERSION = 1
7
+
6
8
  def initialize(
7
9
  key_data:,
8
10
  version_data:,
@@ -25,12 +27,13 @@ module ResponseBank
25
27
  @force_refill_cache = force_refill_cache
26
28
  @cache_store = cache_store
27
29
  @headers = headers || {}
30
+ @key_schema_version = @env.key?('cacheable.key_version') ? @env.key['cacheable.key_version'] : CACHE_KEY_SCHEMA_VERSION
28
31
  end
29
32
 
30
33
  def run!
31
34
  @env['cacheable.cache'] = true
32
- @env['cacheable.key'] = versioned_key_hash
33
- @env['cacheable.unversioned-key'] = unversioned_key_hash
35
+ @env['cacheable.key'] = entity_tag_hash
36
+ @env['cacheable.unversioned-key'] = cache_key_hash
34
37
 
35
38
  ResponseBank.log(cacheable_info_dump)
36
39
 
@@ -41,32 +44,32 @@ module ResponseBank
41
44
  end
42
45
  end
43
46
 
44
- def versioned_key_hash
45
- @versioned_key_hash ||= key_hash(versioned_key)
47
+ def entity_tag_hash
48
+ @entity_tag_hash ||= hash(entity_tag)
46
49
  end
47
50
 
48
- def unversioned_key_hash
49
- @unversioned_key_hash ||= key_hash(unversioned_key)
51
+ def cache_key_hash
52
+ @cache_key_hash ||= hash(cache_key)
50
53
  end
51
54
 
52
55
  private
53
56
 
54
- def key_hash(key)
55
- "cacheable:#{Digest::MD5.hexdigest(key)}"
57
+ def hash(key)
58
+ "cacheable:" + Digest::MD5.hexdigest(key)
56
59
  end
57
60
 
58
- def versioned_key
59
- @versioned_key ||= ResponseBank.cache_key_for(key: @key_data, version: @version_data)
61
+ def entity_tag
62
+ @entity_tag ||= ResponseBank.cache_key_for(key: @key_data, version: @version_data, key_schema_version: @key_schema_version)
60
63
  end
61
64
 
62
- def unversioned_key
63
- @unversioned_key ||= ResponseBank.cache_key_for(key: @key_data)
65
+ def cache_key
66
+ @cache_key ||= ResponseBank.cache_key_for(key: @key_data, key_schema_version: @key_schema_version, encoding: @env['response_bank.server_cache_encoding'])
64
67
  end
65
68
 
66
69
  def cacheable_info_dump
67
70
  log_info = [
68
- "Raw cacheable.key: #{versioned_key}",
69
- "cacheable.key: #{versioned_key_hash}",
71
+ "Raw cacheable.key: #{entity_tag}",
72
+ "cacheable.key: #{entity_tag_hash}",
70
73
  ]
71
74
 
72
75
  if @env['HTTP_IF_NONE_MATCH']
@@ -78,42 +81,19 @@ module ResponseBank
78
81
 
79
82
  def try_to_serve_from_cache
80
83
  # Etag
81
- response = serve_from_browser_cache(versioned_key_hash)
82
-
84
+ response = serve_from_browser_cache(entity_tag_hash, @env['HTTP_IF_NONE_MATCH'])
83
85
  return response if response
84
86
 
85
- # Memcached
86
- response = if @serve_unversioned
87
- serve_from_cache(unversioned_key_hash, "Cache hit: server (unversioned)")
88
- else
89
- serve_from_cache(versioned_key_hash, "Cache hit: server")
90
- end
91
-
87
+ response = serve_from_cache(cache_key_hash, @serve_unversioned ? "*" : entity_tag_hash, @cache_age_tolerance)
92
88
  return response if response
93
89
 
94
- @env['cacheable.locked'] ||= false
95
-
96
- if @env['cacheable.locked'] || ResponseBank.acquire_lock(versioned_key_hash)
97
- # execute if we can get the lock
98
- @env['cacheable.locked'] = true
99
- elsif serving_from_noncurrent_but_recent_version_acceptable?
100
- # serve a stale version
101
- response = serve_from_cache(unversioned_key_hash, "Cache hit: server (recent)", @cache_age_tolerance)
102
-
103
- return response if response
104
- end
105
-
106
90
  # No cache hit; this request cannot be handled from cache.
107
91
  # Yield to the controller and mark for writing into cache.
108
92
  refill_cache
109
93
  end
110
94
 
111
- def serving_from_noncurrent_but_recent_version_acceptable?
112
- @cache_age_tolerance > 0
113
- end
114
-
115
- def serve_from_browser_cache(cache_key_hash)
116
- if @env["HTTP_IF_NONE_MATCH"] == cache_key_hash
95
+ def serve_from_browser_cache(entity_tag, if_none_match)
96
+ if etag_matches?(entity_tag, if_none_match)
117
97
  @env['cacheable.miss'] = false
118
98
  @env['cacheable.store'] = 'client'
119
99
 
@@ -126,7 +106,7 @@ module ResponseBank
126
106
  end
127
107
  end
128
108
 
129
- def serve_from_cache(cache_key_hash, message, cache_age_tolerance = nil)
109
+ def serve_from_cache(cache_key_hash, match_entity_tag = "*", cache_age_tolerance = nil)
130
110
  raw = ResponseBank.read_from_backing_cache_store(@env, cache_key_hash, backing_cache_store: @cache_store)
131
111
 
132
112
  if raw
@@ -135,37 +115,74 @@ module ResponseBank
135
115
  @env['cacheable.miss'] = false
136
116
  @env['cacheable.store'] = 'server'
137
117
 
138
- status, content_type, body, timestamp, location = hit
118
+ status, headers, body, timestamp = hit
139
119
 
140
- if cache_age_tolerance && page_too_old?(timestamp, cache_age_tolerance)
141
- ResponseBank.log("Found an unversioned cache entry, but it was too old (#{timestamp})")
120
+ @env['cacheable.locked'] ||= false
142
121
 
143
- nil
122
+ # to preserve the unversioned/versioned logging messages from past releases we split the match_entity_tag test
123
+ if match_entity_tag == "*"
124
+ ResponseBank.log("Cache hit: server (unversioned)")
125
+ # page tolerance only applies for versioned + etag mismatch
126
+ elsif etag_matches?(headers['ETag'], match_entity_tag)
127
+ ResponseBank.log("Cache hit: server")
144
128
  else
145
- @headers['Content-Type'] = content_type
146
-
147
- @headers['Location'] = location if location
148
-
149
- if @env["gzip"]
150
- @headers['Content-Encoding'] = "gzip"
129
+ # cache miss; check to see if any parallel requests already are regenerating the cache
130
+ if ResponseBank.acquire_lock(match_entity_tag)
131
+ # execute if we can get the lock
132
+ @env['cacheable.locked'] = true
133
+ return
134
+ elsif stale_while_revalidate?(timestamp, cache_age_tolerance)
135
+ # cache is being regenerated, can we avoid piling on and use a stale version in the interim?
136
+ ResponseBank.log("Cache hit: server (recent)")
151
137
  else
152
- # we have to uncompress because the client doesn't support gzip
153
- ResponseBank.log("uncompressing for client without gzip")
154
- body = ResponseBank.decompress(body)
138
+ ResponseBank.log("Found an unversioned cache entry, but it was too old (#{timestamp})")
139
+ return
155
140
  end
141
+ end
156
142
 
157
- ResponseBank.log(message)
143
+ # version check
144
+ # unversioned but tolerance threshold
145
+ # regen
146
+ @headers.merge!(headers)
158
147
 
159
- [status, @headers, [body]]
148
+ # if a cache key hit and client doesn't match encoding, return the raw body
149
+ if !@env['HTTP_ACCEPT_ENCODING'].to_s.include?(@headers['Content-Encoding'])
150
+ ResponseBank.log("uncompressing payload for client as client doesn't require encoding")
151
+ body = ResponseBank.decompress(body, @headers['Content-Encoding'])
152
+ @headers.delete('Content-Encoding')
160
153
  end
154
+
155
+ [status, @headers, [body]]
156
+
161
157
  end
162
158
  end
163
159
 
164
- def page_too_old?(timestamp, cache_age_tolerance)
165
- !timestamp || timestamp < (Time.now.to_i - cache_age_tolerance)
160
+ def etag_matches?(entity_tag, if_none_match)
161
+ # Support for Etag variations including:
162
+ # If-None-Match: abc
163
+ # If-None-Match: "abc"
164
+ # If-None-Match: W/"abc"
165
+ # If-None-Match: "abc", "def"
166
+ # If-None-Match: "*"
167
+ return false unless entity_tag
168
+ return false unless if_none_match
169
+
170
+ # strictly speaking an unquoted etag is not valid, yet common
171
+ # to avoid unintended greedy matches in we check for naked entity then includes with quoted entity values
172
+ if_none_match == "*" || if_none_match == entity_tag || if_none_match.include?(%{"#{entity_tag}"})
173
+ end
174
+
175
+ def stale_while_revalidate?(timestamp, cache_age_tolerance)
176
+ return false if !cache_age_tolerance
177
+ return false if !timestamp
178
+
179
+ timestamp >= (Time.now.to_i - cache_age_tolerance)
166
180
  end
167
181
 
168
182
  def refill_cache
183
+ # non cache hits do not yet have the lock
184
+ ResponseBank.acquire_lock(entity_tag_hash) unless @env['cacheable.locked']
185
+ @env['cacheable.locked'] = true
169
186
  @env['cacheable.miss'] = true
170
187
 
171
188
  ResponseBank.log("Refilling cache")
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module ResponseBank
3
- VERSION = "1.1.0"
3
+ VERSION = "1.3.0"
4
4
  end
data/lib/response_bank.rb CHANGED
@@ -3,6 +3,7 @@ require 'response_bank/middleware'
3
3
  require 'response_bank/railtie' if defined?(Rails)
4
4
  require 'response_bank/response_cache_handler'
5
5
  require 'msgpack'
6
+ require 'brotli'
6
7
 
7
8
  module ResponseBank
8
9
  class << self
@@ -29,17 +30,26 @@ module ResponseBank
29
30
  backing_cache_store.read(cache_key, raw: true)
30
31
  end
31
32
 
32
- def compress(content)
33
- io = StringIO.new
34
- gz = Zlib::GzipWriter.new(io)
35
- gz.write(content)
36
- io.string
37
- ensure
38
- gz.close
33
+ def compress(content, encoding = "gzip")
34
+ case encoding
35
+ when 'gzip'
36
+ Zlib.gzip(content, level: Zlib::BEST_COMPRESSION)
37
+ when 'br'
38
+ Brotli.deflate(content, mode: :text, quality: 7)
39
+ else
40
+ raise ArgumentError, "Unsupported encoding: #{encoding}"
41
+ end
39
42
  end
40
43
 
41
- def decompress(content)
42
- Zlib::GzipReader.new(StringIO.new(content)).read
44
+ def decompress(content, encoding = "gzip")
45
+ case encoding
46
+ when 'gzip'
47
+ Zlib.gunzip(content)
48
+ when 'br'
49
+ Brotli.inflate(content)
50
+ else
51
+ raise ArgumentError, "Unsupported encoding: #{encoding}"
52
+ end
43
53
  end
44
54
 
45
55
  def cache_key_for(data)
@@ -49,17 +59,20 @@ module ResponseBank
49
59
 
50
60
  key = hash_value_str(data[:key])
51
61
 
52
- return key unless data.key?(:version)
62
+ key = %{#{data[:key_schema_version]}:#{key}} if data[:key_schema_version]
63
+
64
+ key = %{#{key}:#{hash_value_str(data[:version])}} if data[:version]
53
65
 
54
- version = hash_value_str(data[:version])
66
+ # add the encoding to only the cache key but don't expose this detail in the entity_tag
67
+ key = %{#{key}:#{hash_value_str(data[:encoding])}} if data[:encoding] && data[:encoding] != "gzip"
55
68
 
56
- [key, version].join(":")
69
+ key
57
70
  when Array
58
71
  data.inspect
59
72
  when Time, DateTime
60
73
  data.to_i
61
74
  when Date
62
- data.to_time.to_i
75
+ data.to_s # Date#to_i does not support timezones, using iso8601 instead
63
76
  when true, false, Integer, Symbol, String
64
77
  data.inspect
65
78
  else
@@ -67,6 +80,17 @@ module ResponseBank
67
80
  end
68
81
  end
69
82
 
83
+ def check_encoding(env, default_encoding = 'br')
84
+ if env['HTTP_ACCEPT_ENCODING'].to_s.include?('br')
85
+ 'br'
86
+ elsif env['HTTP_ACCEPT_ENCODING'].to_s.include?('gzip')
87
+ 'gzip'
88
+ else
89
+ # No encoding requested from client, but we still need to cache the page in server cache
90
+ default_encoding
91
+ end
92
+ end
93
+
70
94
  private
71
95
 
72
96
  def hash_value_str(data)
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.1.0
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tobias Lütke
@@ -9,10 +9,10 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2021-08-04 00:00:00.000000000 Z
12
+ date: 2023-04-04 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
- name: useragent
15
+ name: msgpack
16
16
  requirement: !ruby/object:Gem::Requirement
17
17
  requirements:
18
18
  - - ">="
@@ -26,7 +26,7 @@ dependencies:
26
26
  - !ruby/object:Gem::Version
27
27
  version: '0'
28
28
  - !ruby/object:Gem::Dependency
29
- name: msgpack
29
+ name: brotli
30
30
  requirement: !ruby/object:Gem::Requirement
31
31
  requirements:
32
32
  - - ">="
@@ -45,28 +45,28 @@ dependencies:
45
45
  requirements:
46
46
  - - ">="
47
47
  - !ruby/object:Gem::Version
48
- version: 5.13.0
48
+ version: 5.18.0
49
49
  type: :development
50
50
  prerelease: false
51
51
  version_requirements: !ruby/object:Gem::Requirement
52
52
  requirements:
53
53
  - - ">="
54
54
  - !ruby/object:Gem::Version
55
- version: 5.13.0
55
+ version: 5.18.0
56
56
  - !ruby/object:Gem::Dependency
57
57
  name: mocha
58
58
  requirement: !ruby/object:Gem::Requirement
59
59
  requirements:
60
60
  - - ">="
61
61
  - !ruby/object:Gem::Version
62
- version: 1.10.0
62
+ version: 2.0.0
63
63
  type: :development
64
64
  prerelease: false
65
65
  version_requirements: !ruby/object:Gem::Requirement
66
66
  requirements:
67
67
  - - ">="
68
68
  - !ruby/object:Gem::Version
69
- version: 1.10.0
69
+ version: 2.0.0
70
70
  - !ruby/object:Gem::Dependency
71
71
  name: rake
72
72
  requirement: !ruby/object:Gem::Requirement
@@ -87,28 +87,28 @@ dependencies:
87
87
  requirements:
88
88
  - - ">="
89
89
  - !ruby/object:Gem::Version
90
- version: '5.0'
90
+ version: '6.1'
91
91
  type: :development
92
92
  prerelease: false
93
93
  version_requirements: !ruby/object:Gem::Requirement
94
94
  requirements:
95
95
  - - ">="
96
96
  - !ruby/object:Gem::Version
97
- version: '5.0'
97
+ version: '6.1'
98
98
  - !ruby/object:Gem::Dependency
99
- name: tzinfo-data
99
+ name: pry
100
100
  requirement: !ruby/object:Gem::Requirement
101
101
  requirements:
102
102
  - - ">="
103
103
  - !ruby/object:Gem::Version
104
- version: 1.2019.3
104
+ version: '0'
105
105
  type: :development
106
106
  prerelease: false
107
107
  version_requirements: !ruby/object:Gem::Requirement
108
108
  requirements:
109
109
  - - ">="
110
110
  - !ruby/object:Gem::Version
111
- version: 1.2019.3
111
+ version: '0'
112
112
  description:
113
113
  email:
114
114
  - tobi@shopify.com
@@ -139,14 +139,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
139
139
  requirements:
140
140
  - - ">="
141
141
  - !ruby/object:Gem::Version
142
- version: 2.4.0
142
+ version: 2.7.0
143
143
  required_rubygems_version: !ruby/object:Gem::Requirement
144
144
  requirements:
145
145
  - - ">="
146
146
  - !ruby/object:Gem::Version
147
147
  version: '0'
148
148
  requirements: []
149
- rubygems_version: 3.2.20
149
+ rubygems_version: 3.4.10
150
150
  signing_key:
151
151
  specification_version: 4
152
152
  summary: Simple response caching for Ruby applications