response_bank 1.0.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 +7 -0
- data/LICENSE.txt +20 -0
- data/README.md +91 -0
- data/lib/response_bank.rb +72 -0
- data/lib/response_bank/controller.rb +67 -0
- data/lib/response_bank/middleware.rb +88 -0
- data/lib/response_bank/model_extensions.rb +19 -0
- data/lib/response_bank/railtie.rb +20 -0
- data/lib/response_bank/response_cache_handler.rb +176 -0
- data/lib/response_bank/version.rb +4 -0
- metadata +152 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 22412918297172ba47f51811bb250ace6dbb2b3d8eb26405f0f970a18b2479de
|
4
|
+
data.tar.gz: 94022c0b2e796e9a0a8a0fcc47f7f4ba40f64118458695554683d21452009388
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: eb15b72956d34a954d829f1ac5666568e5b0c1b4eeebd7a127d8f88b5d103efbd856fe0d4466c675826868a2e8fb8fd572004891753a0bf1799ba3bc04a1cc81
|
7
|
+
data.tar.gz: 86ac266abcbe74dbf7fb72c833def9283fdd5943c0666fc1bf6dbed34008161c3458d85f1d365f7b8d6aacdd4af1a5b8822bc792c33de094668490d6108d4a87
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2008-2019 Tobias Lütke
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
# ResponseBank [](http://travis-ci.org/Shopify/response_bank)
|
2
|
+
|
3
|
+
### Features
|
4
|
+
|
5
|
+
* Serve gzip'd content
|
6
|
+
* Add ETag and 304 Not Modified headers
|
7
|
+
* Generational caching
|
8
|
+
* No explicit expiry
|
9
|
+
|
10
|
+
### Support
|
11
|
+
|
12
|
+
This gem supports the following versions of Ruby and Rails:
|
13
|
+
|
14
|
+
* Ruby 2.4.0+
|
15
|
+
* Rails 5.0.0+
|
16
|
+
|
17
|
+
### Usage
|
18
|
+
|
19
|
+
1. include the gem in your Gemfile
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
gem 'response_bank'
|
23
|
+
```
|
24
|
+
|
25
|
+
2. use `#response_cache` method to any desired controller's action
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
class PostsController < ApplicationController
|
29
|
+
def show
|
30
|
+
response_cache do
|
31
|
+
@post = @shop.posts.find(params[:id])
|
32
|
+
respond_with(@post)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
```
|
37
|
+
|
38
|
+
3. **(optional)** override custom cache key data. For default, cache key is defined by URL and query string
|
39
|
+
|
40
|
+
```ruby
|
41
|
+
class PostsController < ApplicationController
|
42
|
+
before_action :set_shop
|
43
|
+
|
44
|
+
def index
|
45
|
+
response_cache do
|
46
|
+
@post = @shop.posts
|
47
|
+
respond_with(@post)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def show
|
52
|
+
response_cache do
|
53
|
+
@post = @shop.posts.find(params[:id])
|
54
|
+
respond_with(@post)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def another_action
|
59
|
+
# custom cache key data
|
60
|
+
cache_key = {
|
61
|
+
action: action_name,
|
62
|
+
format: request.format,
|
63
|
+
shop_updated_at: @shop.updated_at
|
64
|
+
# you may add more keys here
|
65
|
+
}
|
66
|
+
response_cache cache_key do
|
67
|
+
@post = @shop.posts.find(params[:id])
|
68
|
+
respond_with(@post)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# override default cache key data globally per class
|
73
|
+
def cache_key_data
|
74
|
+
{
|
75
|
+
action: action_name,
|
76
|
+
format: request.format,
|
77
|
+
params: params.slice(:id),
|
78
|
+
shop_version: @shop.version
|
79
|
+
# you may add more keys here
|
80
|
+
}
|
81
|
+
end
|
82
|
+
|
83
|
+
def set_shop
|
84
|
+
# @shop = ...
|
85
|
+
end
|
86
|
+
end
|
87
|
+
```
|
88
|
+
|
89
|
+
### License
|
90
|
+
|
91
|
+
ResponseBank is released under the [MIT License](LICENSE.txt).
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'response_bank/middleware'
|
3
|
+
require 'response_bank/railtie' if defined?(Rails)
|
4
|
+
require 'response_bank/response_cache_handler'
|
5
|
+
require 'msgpack'
|
6
|
+
|
7
|
+
module ResponseBank
|
8
|
+
class << self
|
9
|
+
attr_accessor :cache_store
|
10
|
+
attr_writer :logger
|
11
|
+
|
12
|
+
def log(message)
|
13
|
+
@logger.info("[ResponseBank] #{message}")
|
14
|
+
end
|
15
|
+
|
16
|
+
def acquire_lock(_cache_key)
|
17
|
+
raise NotImplementedError, "Override ResponseBank.acquire_lock in an initializer."
|
18
|
+
end
|
19
|
+
|
20
|
+
def write_to_cache(_key)
|
21
|
+
yield
|
22
|
+
end
|
23
|
+
|
24
|
+
def compress(content)
|
25
|
+
io = StringIO.new
|
26
|
+
gz = Zlib::GzipWriter.new(io)
|
27
|
+
gz.write(content)
|
28
|
+
io.string
|
29
|
+
ensure
|
30
|
+
gz.close
|
31
|
+
end
|
32
|
+
|
33
|
+
def decompress(content)
|
34
|
+
Zlib::GzipReader.new(StringIO.new(content)).read
|
35
|
+
end
|
36
|
+
|
37
|
+
def cache_key_for(data)
|
38
|
+
case data
|
39
|
+
when Hash
|
40
|
+
return data.inspect unless data.key?(:key)
|
41
|
+
|
42
|
+
key = hash_value_str(data[:key])
|
43
|
+
|
44
|
+
return key unless data.key?(:version)
|
45
|
+
|
46
|
+
version = hash_value_str(data[:version])
|
47
|
+
|
48
|
+
[key, version].join(":")
|
49
|
+
when Array
|
50
|
+
data.inspect
|
51
|
+
when Time, DateTime
|
52
|
+
data.to_i
|
53
|
+
when Date
|
54
|
+
data.to_time.to_i
|
55
|
+
when true, false, Integer, Symbol, String
|
56
|
+
data.inspect
|
57
|
+
else
|
58
|
+
data.to_s.inspect
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def hash_value_str(data)
|
65
|
+
if data.is_a?(Hash)
|
66
|
+
data.values.join(",")
|
67
|
+
else
|
68
|
+
data.to_s
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module ResponseBank
|
3
|
+
module Controller
|
4
|
+
private
|
5
|
+
|
6
|
+
# Only get? and head? requests should be cached.
|
7
|
+
def cacheable_request?
|
8
|
+
(request.get? || request.head?) && (request.params[:cache] != 'false')
|
9
|
+
end
|
10
|
+
|
11
|
+
# Override this method with additional information that changes to invalidate the cache.
|
12
|
+
def cache_version_data
|
13
|
+
{}
|
14
|
+
end
|
15
|
+
|
16
|
+
def cache_key_data
|
17
|
+
{ 'request' => { 'env' => request.env.slice('PATH_INFO', 'QUERY_STRING') } }
|
18
|
+
end
|
19
|
+
|
20
|
+
def force_refill_cache?
|
21
|
+
params[:fill_cache] == "true"
|
22
|
+
end
|
23
|
+
|
24
|
+
def serve_unversioned_cacheable_entry?
|
25
|
+
false
|
26
|
+
end
|
27
|
+
|
28
|
+
# If you're okay with serving pages that are not at the newest version, bump this up
|
29
|
+
# to whatever number of seconds you're comfortable with.
|
30
|
+
def cache_age_tolerance_in_seconds
|
31
|
+
0
|
32
|
+
end
|
33
|
+
|
34
|
+
def response_cache(key_data = nil, version_data = nil, &block)
|
35
|
+
cacheable_req = cacheable_request?
|
36
|
+
|
37
|
+
unless cache_configured? && cacheable_req
|
38
|
+
ResponseBank.log("Uncacheable request. cache_configured='#{!!cache_configured?}'" \
|
39
|
+
" cacheable_request='#{cacheable_req}' params_cache='#{request.params[:cache] != 'false'}'")
|
40
|
+
response.headers['Cache-Control'] = 'no-cache, no-store' unless cacheable_req
|
41
|
+
return yield
|
42
|
+
end
|
43
|
+
|
44
|
+
handler = ResponseBank::ResponseCacheHandler.new(
|
45
|
+
key_data: key_data || cache_key_data,
|
46
|
+
version_data: version_data || cache_version_data,
|
47
|
+
env: request.env,
|
48
|
+
cache_age_tolerance: cache_age_tolerance_in_seconds,
|
49
|
+
serve_unversioned: serve_unversioned_cacheable_entry?,
|
50
|
+
force_refill_cache: force_refill_cache?,
|
51
|
+
headers: response.headers,
|
52
|
+
&block
|
53
|
+
)
|
54
|
+
|
55
|
+
status, _headers, body = handler.run!
|
56
|
+
|
57
|
+
return if request.env['cacheable.miss']
|
58
|
+
|
59
|
+
case request.env['cacheable.store']
|
60
|
+
when 'server'
|
61
|
+
render(status: status, plain: body.first)
|
62
|
+
when 'client'
|
63
|
+
head(:not_modified)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'useragent'
|
3
|
+
|
4
|
+
module ResponseBank
|
5
|
+
class Middleware
|
6
|
+
REQUESTED_WITH = "HTTP_X_REQUESTED_WITH"
|
7
|
+
ACCEPT = "HTTP_ACCEPT"
|
8
|
+
USER_AGENT = "HTTP_USER_AGENT"
|
9
|
+
|
10
|
+
def initialize(app)
|
11
|
+
@app = app
|
12
|
+
end
|
13
|
+
|
14
|
+
def call(env)
|
15
|
+
env['cacheable.cache'] = false
|
16
|
+
gzip = env['gzip'] = env['HTTP_ACCEPT_ENCODING'].to_s.include?("gzip")
|
17
|
+
|
18
|
+
status, headers, body = @app.call(env)
|
19
|
+
|
20
|
+
if env['cacheable.cache']
|
21
|
+
if [200, 404, 301, 304].include?(status)
|
22
|
+
headers['ETag'] = env['cacheable.key']
|
23
|
+
headers['X-Alternate-Cache-Key'] = env['cacheable.unversioned-key']
|
24
|
+
|
25
|
+
if ie_ajax_request?(env)
|
26
|
+
headers["Expires"] = "-1"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
if [200, 404, 301].include?(status) && env['cacheable.miss']
|
31
|
+
# Flatten down the result so that it can be stored to memcached.
|
32
|
+
if body.is_a?(String)
|
33
|
+
body_string = body
|
34
|
+
else
|
35
|
+
body_string = +""
|
36
|
+
body.each { |part| body_string << part }
|
37
|
+
end
|
38
|
+
|
39
|
+
body_gz = ResponseBank.compress(body_string)
|
40
|
+
|
41
|
+
# Store result
|
42
|
+
cache_data = [status, headers['Content-Type'], body_gz, timestamp]
|
43
|
+
cache_data << headers['Location'] if status == 301
|
44
|
+
|
45
|
+
ResponseBank.write_to_cache(env['cacheable.key']) do
|
46
|
+
payload = MessagePack.dump(cache_data)
|
47
|
+
ResponseBank.cache_store.write(env['cacheable.key'], payload, raw: true)
|
48
|
+
|
49
|
+
if env['cacheable.unversioned-key']
|
50
|
+
ResponseBank.cache_store.write(env['cacheable.unversioned-key'], payload, raw: true)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# since we had to generate the gz version above already we may
|
55
|
+
# as well serve it if the client wants it
|
56
|
+
if gzip
|
57
|
+
headers['Content-Encoding'] = "gzip"
|
58
|
+
body = [body_gz]
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# Add X-Cache header
|
63
|
+
miss = env['cacheable.miss']
|
64
|
+
x_cache = miss ? 'miss' : 'hit'
|
65
|
+
x_cache += ", #{env['cacheable.store']}" unless miss
|
66
|
+
headers['X-Cache'] = x_cache
|
67
|
+
end
|
68
|
+
|
69
|
+
[status, headers, body]
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def timestamp
|
75
|
+
Time.now.to_i
|
76
|
+
end
|
77
|
+
|
78
|
+
def ie_ajax_request?(env)
|
79
|
+
return false unless !env[USER_AGENT].nil? && !env[USER_AGENT].empty?
|
80
|
+
|
81
|
+
if env[REQUESTED_WITH] == "XmlHttpRequest" || env[ACCEPT] == "application/json"
|
82
|
+
UserAgent.parse(env["HTTP_USER_AGENT"]).is_a?(UserAgent::Browsers::InternetExplorer)
|
83
|
+
else
|
84
|
+
false
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module ResponseBank
|
3
|
+
module ModelExtensions
|
4
|
+
def self.included(base)
|
5
|
+
super
|
6
|
+
base.extend(ClassMethods)
|
7
|
+
end
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
def cache_store
|
11
|
+
ActionController::Base.cache_store
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def cache_store
|
16
|
+
ActionController::Base.cache_store
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'rails'
|
3
|
+
require 'response_bank/controller'
|
4
|
+
require 'response_bank/model_extensions'
|
5
|
+
|
6
|
+
module ResponseBank
|
7
|
+
class Railtie < ::Rails::Railtie
|
8
|
+
initializer "cachable.configure_active_record" do |config|
|
9
|
+
config.middleware.insert_after(Rack::Head, ResponseBank::Middleware)
|
10
|
+
|
11
|
+
ActiveSupport.on_load(:action_controller) do
|
12
|
+
include ResponseBank::Controller
|
13
|
+
end
|
14
|
+
|
15
|
+
ActiveSupport.on_load(:active_record) do
|
16
|
+
include ResponseBank::ModelExtensions
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,176 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'digest/md5'
|
3
|
+
|
4
|
+
module ResponseBank
|
5
|
+
class ResponseCacheHandler
|
6
|
+
def initialize(
|
7
|
+
key_data:,
|
8
|
+
version_data:,
|
9
|
+
env:,
|
10
|
+
cache_age_tolerance:,
|
11
|
+
serve_unversioned:,
|
12
|
+
headers:,
|
13
|
+
force_refill_cache: false,
|
14
|
+
cache_store: ResponseBank.cache_store,
|
15
|
+
&block
|
16
|
+
)
|
17
|
+
@cache_miss_block = block
|
18
|
+
|
19
|
+
@key_data = key_data
|
20
|
+
@version_data = version_data
|
21
|
+
@env = env
|
22
|
+
@cache_age_tolerance = cache_age_tolerance
|
23
|
+
|
24
|
+
@serve_unversioned = serve_unversioned
|
25
|
+
@force_refill_cache = force_refill_cache
|
26
|
+
@cache_store = cache_store
|
27
|
+
@headers = headers || {}
|
28
|
+
end
|
29
|
+
|
30
|
+
def run!
|
31
|
+
@env['cacheable.cache'] = true
|
32
|
+
@env['cacheable.key'] = versioned_key_hash
|
33
|
+
@env['cacheable.unversioned-key'] = unversioned_key_hash
|
34
|
+
|
35
|
+
ResponseBank.log(cacheable_info_dump)
|
36
|
+
|
37
|
+
if @force_refill_cache
|
38
|
+
refill_cache
|
39
|
+
else
|
40
|
+
try_to_serve_from_cache
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def versioned_key_hash
|
45
|
+
@versioned_key_hash ||= key_hash(versioned_key)
|
46
|
+
end
|
47
|
+
|
48
|
+
def unversioned_key_hash
|
49
|
+
@unversioned_key_hash ||= key_hash(unversioned_key)
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def key_hash(key)
|
55
|
+
"cacheable:#{Digest::MD5.hexdigest(key)}"
|
56
|
+
end
|
57
|
+
|
58
|
+
def versioned_key
|
59
|
+
@versioned_key ||= ResponseBank.cache_key_for(key: @key_data, version: @version_data)
|
60
|
+
end
|
61
|
+
|
62
|
+
def unversioned_key
|
63
|
+
@unversioned_key ||= ResponseBank.cache_key_for(key: @key_data)
|
64
|
+
end
|
65
|
+
|
66
|
+
def cacheable_info_dump
|
67
|
+
log_info = [
|
68
|
+
"Raw cacheable.key: #{versioned_key}",
|
69
|
+
"cacheable.key: #{versioned_key_hash}",
|
70
|
+
]
|
71
|
+
|
72
|
+
if @env['HTTP_IF_NONE_MATCH']
|
73
|
+
log_info.push("If-None-Match: #{@env['HTTP_IF_NONE_MATCH']}")
|
74
|
+
end
|
75
|
+
|
76
|
+
log_info.join(', ')
|
77
|
+
end
|
78
|
+
|
79
|
+
def try_to_serve_from_cache
|
80
|
+
# Etag
|
81
|
+
response = serve_from_browser_cache(versioned_key_hash)
|
82
|
+
|
83
|
+
return response if response
|
84
|
+
|
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
|
+
|
92
|
+
return response if response
|
93
|
+
|
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
|
+
# No cache hit; this request cannot be handled from cache.
|
107
|
+
# Yield to the controller and mark for writing into cache.
|
108
|
+
refill_cache
|
109
|
+
end
|
110
|
+
|
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
|
117
|
+
@env['cacheable.miss'] = false
|
118
|
+
@env['cacheable.store'] = 'client'
|
119
|
+
|
120
|
+
@headers.delete('Content-Type')
|
121
|
+
@headers.delete('Content-Length')
|
122
|
+
|
123
|
+
ResponseBank.log("Cache hit: client")
|
124
|
+
|
125
|
+
[304, @headers, []]
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def serve_from_cache(cache_key_hash, message, cache_age_tolerance = nil)
|
130
|
+
raw = @cache_store.read(cache_key_hash)
|
131
|
+
|
132
|
+
if raw
|
133
|
+
hit = MessagePack.load(raw)
|
134
|
+
|
135
|
+
@env['cacheable.miss'] = false
|
136
|
+
@env['cacheable.store'] = 'server'
|
137
|
+
|
138
|
+
status, content_type, body, timestamp, location = hit
|
139
|
+
|
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})")
|
142
|
+
|
143
|
+
nil
|
144
|
+
else
|
145
|
+
@headers['Content-Type'] = content_type
|
146
|
+
|
147
|
+
@headers['Location'] = location if location
|
148
|
+
|
149
|
+
if @env["gzip"]
|
150
|
+
@headers['Content-Encoding'] = "gzip"
|
151
|
+
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)
|
155
|
+
end
|
156
|
+
|
157
|
+
ResponseBank.log(message)
|
158
|
+
|
159
|
+
[status, @headers, [body]]
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def page_too_old?(timestamp, cache_age_tolerance)
|
165
|
+
!timestamp || timestamp < (Time.now.to_i - cache_age_tolerance)
|
166
|
+
end
|
167
|
+
|
168
|
+
def refill_cache
|
169
|
+
@env['cacheable.miss'] = true
|
170
|
+
|
171
|
+
ResponseBank.log("Refilling cache")
|
172
|
+
|
173
|
+
@cache_miss_block.call
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
metadata
ADDED
@@ -0,0 +1,152 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: response_bank
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Tobias Lütke
|
8
|
+
- Burke Libbey
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2020-01-27 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: useragent
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - ">="
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '0'
|
21
|
+
type: :runtime
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '0'
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: msgpack
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - ">="
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '0'
|
35
|
+
type: :runtime
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '0'
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
name: minitest
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - ">="
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: 5.13.0
|
49
|
+
type: :development
|
50
|
+
prerelease: false
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: 5.13.0
|
56
|
+
- !ruby/object:Gem::Dependency
|
57
|
+
name: mocha
|
58
|
+
requirement: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: 1.10.0
|
63
|
+
type: :development
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: 1.10.0
|
70
|
+
- !ruby/object:Gem::Dependency
|
71
|
+
name: rake
|
72
|
+
requirement: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
77
|
+
type: :development
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - ">="
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '0'
|
84
|
+
- !ruby/object:Gem::Dependency
|
85
|
+
name: rails
|
86
|
+
requirement: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - ">="
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '5.0'
|
91
|
+
type: :development
|
92
|
+
prerelease: false
|
93
|
+
version_requirements: !ruby/object:Gem::Requirement
|
94
|
+
requirements:
|
95
|
+
- - ">="
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '5.0'
|
98
|
+
- !ruby/object:Gem::Dependency
|
99
|
+
name: tzinfo-data
|
100
|
+
requirement: !ruby/object:Gem::Requirement
|
101
|
+
requirements:
|
102
|
+
- - ">="
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: 1.2019.3
|
105
|
+
type: :development
|
106
|
+
prerelease: false
|
107
|
+
version_requirements: !ruby/object:Gem::Requirement
|
108
|
+
requirements:
|
109
|
+
- - ">="
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: 1.2019.3
|
112
|
+
description:
|
113
|
+
email:
|
114
|
+
- tobi@shopify.com
|
115
|
+
- burke@burkelibbey.org
|
116
|
+
executables: []
|
117
|
+
extensions: []
|
118
|
+
extra_rdoc_files: []
|
119
|
+
files:
|
120
|
+
- LICENSE.txt
|
121
|
+
- README.md
|
122
|
+
- lib/response_bank.rb
|
123
|
+
- lib/response_bank/controller.rb
|
124
|
+
- lib/response_bank/middleware.rb
|
125
|
+
- lib/response_bank/model_extensions.rb
|
126
|
+
- lib/response_bank/railtie.rb
|
127
|
+
- lib/response_bank/response_cache_handler.rb
|
128
|
+
- lib/response_bank/version.rb
|
129
|
+
homepage: ''
|
130
|
+
licenses:
|
131
|
+
- MIT
|
132
|
+
metadata: {}
|
133
|
+
post_install_message:
|
134
|
+
rdoc_options: []
|
135
|
+
require_paths:
|
136
|
+
- lib
|
137
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
138
|
+
requirements:
|
139
|
+
- - ">="
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: 2.4.0
|
142
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
143
|
+
requirements:
|
144
|
+
- - ">="
|
145
|
+
- !ruby/object:Gem::Version
|
146
|
+
version: '0'
|
147
|
+
requirements: []
|
148
|
+
rubygems_version: 3.1.2
|
149
|
+
signing_key:
|
150
|
+
specification_version: 4
|
151
|
+
summary: Simple response caching for Ruby applications
|
152
|
+
test_files: []
|