response_bank 1.1.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
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