response_bank 1.2.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: 75476fbf8271b8744fdca486f31ddfff585ab0680e29c600e25393d24b6b6fbb
4
- data.tar.gz: 6092909983069ec5689e4e6ada0d241e632331fd6c97fb94e4ce5029c4cf228a
3
+ metadata.gz: 7d25739f9bd846e1cd3364a422cc90590b32fcb5f7e56cd307af0e83f0d5ddac
4
+ data.tar.gz: 14c1a7f96abcc45025cee9f998342fb6185779774dc72986ebb1a5bbcb96c413
5
5
  SHA512:
6
- metadata.gz: ccade0feb4bcc259e6bd0d3435799960014f204031b564ca88892e5c80afce36997f266b91acb0eb2af1e64f80488af49166fe4a99ab2d31874cac9827e37eaa
7
- data.tar.gz: 5e74d777bfd30f8eed957d5ed07d11ffab8e6d4d462194277b7a38c28b70728445d0c598a26173020fc1d26c3c11410aabc6cf7f79f5a06cd806546a687cff7e
6
+ metadata.gz: b6120bc7a707fa5ddd1cd3a143e40681e375864b075fe0e74b1128cc4ee460690400a70c47599772addba8d2bee56b02ce14a820c5e4af376f0eaa154f1fe88c
7
+ data.tar.gz: 5ae1545f2c40d7abbf06b8ba43c8d353cb171fc97ce667fa280c5f991c37f7e514c7673898a99242e446f9975ef22f4cb91eaa053165664a9c309fa4d6b39197
@@ -1,11 +1,10 @@
1
1
  # frozen_string_literal: true
2
- require 'useragent'
3
2
 
4
3
  module ResponseBank
5
4
  class Middleware
6
5
  # Limit the cached headers
7
6
  # TODO: Make this lowercase/case-insentitive as per rfc2616 §4.2
8
- CACHEABLE_HEADERS = ["Location", "Content-Type", "ETag", "Content-Encoding", "Last-Modified", "Cache-Control", "Expires", "Surrogate-Keys", "Cache-Tags"].freeze
7
+ CACHEABLE_HEADERS = ["Location", "Content-Type", "ETag", "Content-Encoding", "Last-Modified", "Cache-Control", "Expires", "Link", "Surrogate-Keys", "Cache-Tags"].freeze
9
8
 
10
9
  REQUESTED_WITH = "HTTP_X_REQUESTED_WITH"
11
10
  ACCEPT = "HTTP_ACCEPT"
@@ -17,7 +16,7 @@ module ResponseBank
17
16
 
18
17
  def call(env)
19
18
  env['cacheable.cache'] = false
20
- gzip = env['gzip'] = env['HTTP_ACCEPT_ENCODING'].to_s.include?("gzip")
19
+ content_encoding = env['response_bank.server_cache_encoding'] = ResponseBank.check_encoding(env)
21
20
 
22
21
  status, headers, body = @app.call(env)
23
22
 
@@ -25,9 +24,6 @@ module ResponseBank
25
24
  if [200, 404, 301, 304].include?(status)
26
25
  headers['ETag'] = env['cacheable.key']
27
26
 
28
- if ie_ajax_request?(env)
29
- headers["Expires"] = "-1"
30
- end
31
27
  end
32
28
 
33
29
  if [200, 404, 301].include?(status) && env['cacheable.miss']
@@ -39,11 +35,15 @@ module ResponseBank
39
35
  body.each { |part| body_string << part }
40
36
  end
41
37
 
42
- 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
43
43
 
44
44
  cached_headers = headers.slice(*CACHEABLE_HEADERS)
45
45
  # Store result
46
- cache_data = [status, cached_headers, body_gz, timestamp]
46
+ cache_data = [status, cached_headers, body_compressed, timestamp]
47
47
 
48
48
  ResponseBank.write_to_cache(env['cacheable.key']) do
49
49
  payload = MessagePack.dump(cache_data)
@@ -55,11 +55,15 @@ module ResponseBank
55
55
  )
56
56
  end
57
57
 
58
- # since we had to generate the gz version above already we may
58
+ # since we had to generate the compressed version already we may
59
59
  # as well serve it if the client wants it
60
- if gzip
61
- headers['Content-Encoding'] = "gzip"
62
- 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
63
67
  end
64
68
  end
65
69
 
@@ -79,14 +83,5 @@ module ResponseBank
79
83
  Time.now.to_i
80
84
  end
81
85
 
82
- def ie_ajax_request?(env)
83
- return false unless !env[USER_AGENT].nil? && !env[USER_AGENT].empty?
84
-
85
- if env[REQUESTED_WITH] == "XmlHttpRequest" || env[ACCEPT] == "application/json"
86
- UserAgent.parse(env["HTTP_USER_AGENT"]).is_a?(UserAgent::Browsers::InternetExplorer)
87
- else
88
- false
89
- end
90
- end
91
86
  end
92
87
  end
@@ -55,7 +55,7 @@ module ResponseBank
55
55
  private
56
56
 
57
57
  def hash(key)
58
- "cacheable:#{Digest::MD5.hexdigest(key)}"
58
+ "cacheable:" + Digest::MD5.hexdigest(key)
59
59
  end
60
60
 
61
61
  def entity_tag
@@ -63,7 +63,7 @@ module ResponseBank
63
63
  end
64
64
 
65
65
  def cache_key
66
- @cache_key ||= ResponseBank.cache_key_for(key: @key_data, key_schema_version: @key_schema_version)
66
+ @cache_key ||= ResponseBank.cache_key_for(key: @key_data, key_schema_version: @key_schema_version, encoding: @env['response_bank.server_cache_encoding'])
67
67
  end
68
68
 
69
69
  def cacheable_info_dump
@@ -84,7 +84,7 @@ module ResponseBank
84
84
  response = serve_from_browser_cache(entity_tag_hash, @env['HTTP_IF_NONE_MATCH'])
85
85
  return response if response
86
86
 
87
- response = serve_from_cache(cache_key_hash, entity_tag_hash, @cache_age_tolerance)
87
+ response = serve_from_cache(cache_key_hash, @serve_unversioned ? "*" : entity_tag_hash, @cache_age_tolerance)
88
88
  return response if response
89
89
 
90
90
  # No cache hit; this request cannot be handled from cache.
@@ -143,16 +143,17 @@ module ResponseBank
143
143
  # version check
144
144
  # unversioned but tolerance threshold
145
145
  # regen
146
- @headers = @headers.merge(headers)
146
+ @headers.merge!(headers)
147
147
 
148
- if @env["gzip"]
149
- @headers['Content-Encoding'] = "gzip"
150
- else
151
- # we have to uncompress because the client doesn't support gzip
152
- ResponseBank.log("uncompressing for client without gzip")
153
- body = ResponseBank.decompress(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')
154
153
  end
154
+
155
155
  [status, @headers, [body]]
156
+
156
157
  end
157
158
  end
158
159
 
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module ResponseBank
3
- VERSION = "1.2.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)
@@ -53,6 +63,9 @@ module ResponseBank
53
63
 
54
64
  key = %{#{key}:#{hash_value_str(data[:version])}} if data[:version]
55
65
 
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"
68
+
56
69
  key
57
70
  when Array
58
71
  data.inspect
@@ -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.2.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: 2023-03-28 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
  - - ">="
@@ -95,6 +95,20 @@ dependencies:
95
95
  - - ">="
96
96
  - !ruby/object:Gem::Version
97
97
  version: '6.1'
98
+ - !ruby/object:Gem::Dependency
99
+ name: pry
100
+ requirement: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ type: :development
106
+ prerelease: false
107
+ version_requirements: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: '0'
98
112
  description:
99
113
  email:
100
114
  - tobi@shopify.com
@@ -132,7 +146,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
132
146
  - !ruby/object:Gem::Version
133
147
  version: '0'
134
148
  requirements: []
135
- rubygems_version: 3.4.9
149
+ rubygems_version: 3.4.10
136
150
  signing_key:
137
151
  specification_version: 4
138
152
  summary: Simple response caching for Ruby applications