rack-cache 0.3.0 → 0.4
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.
Potentially problematic release.
This version of rack-cache might be problematic. Click here for more details.
- data/CHANGES +43 -0
- data/README +18 -9
- data/Rakefile +1 -14
- data/TODO +13 -14
- data/doc/configuration.markdown +7 -153
- data/doc/faq.markdown +8 -0
- data/doc/index.markdown +7 -9
- data/example/sinatra/app.rb +25 -0
- data/example/sinatra/views/index.erb +44 -0
- data/lib/rack/cache.rb +5 -11
- data/lib/rack/cache/cachecontrol.rb +193 -0
- data/lib/rack/cache/context.rb +190 -52
- data/lib/rack/cache/entitystore.rb +10 -4
- data/lib/rack/cache/key.rb +52 -0
- data/lib/rack/cache/metastore.rb +52 -16
- data/lib/rack/cache/options.rb +60 -39
- data/lib/rack/cache/request.rb +11 -15
- data/lib/rack/cache/response.rb +221 -30
- data/lib/rack/cache/storage.rb +1 -2
- data/rack-cache.gemspec +9 -15
- data/test/cache_test.rb +9 -6
- data/test/cachecontrol_test.rb +139 -0
- data/test/context_test.rb +251 -169
- data/test/entitystore_test.rb +12 -11
- data/test/key_test.rb +50 -0
- data/test/metastore_test.rb +57 -14
- data/test/options_test.rb +11 -0
- data/test/request_test.rb +19 -0
- data/test/response_test.rb +164 -23
- data/test/spec_setup.rb +7 -0
- metadata +12 -20
- data/doc/events.dot +0 -27
- data/lib/rack/cache/config.rb +0 -65
- data/lib/rack/cache/config/busters.rb +0 -16
- data/lib/rack/cache/config/default.rb +0 -133
- data/lib/rack/cache/config/no-cache.rb +0 -13
- data/lib/rack/cache/core.rb +0 -299
- data/lib/rack/cache/headers.rb +0 -325
- data/lib/rack/utils/environment_headers.rb +0 -78
- data/test/config_test.rb +0 -66
- data/test/core_test.rb +0 -84
- data/test/environment_headers_test.rb +0 -69
- data/test/headers_test.rb +0 -298
- data/test/logging_test.rb +0 -45
data/test/spec_setup.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'pp'
|
2
2
|
require 'tmpdir'
|
3
|
+
require 'stringio'
|
3
4
|
|
4
5
|
[ STDOUT, STDERR ].each { |io| io.sync = true }
|
5
6
|
|
@@ -27,6 +28,7 @@ rescue LoadError => boom
|
|
27
28
|
$memcached = false
|
28
29
|
false
|
29
30
|
rescue => boom
|
31
|
+
STDERR.puts "memcached not working. related tests will be skipped."
|
30
32
|
$memcached = false
|
31
33
|
false
|
32
34
|
end
|
@@ -192,4 +194,9 @@ class Object
|
|
192
194
|
def class_def name, &blk
|
193
195
|
class_eval { define_method name, &blk }
|
194
196
|
end
|
197
|
+
|
198
|
+
# True when the Object is neither false or nil.
|
199
|
+
def truthy?
|
200
|
+
!!self
|
201
|
+
end
|
195
202
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rack-cache
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: "0.4"
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ryan Tomayko
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date:
|
12
|
+
date: 2009-03-16 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -40,7 +40,6 @@ files:
|
|
40
40
|
- Rakefile
|
41
41
|
- TODO
|
42
42
|
- doc/configuration.markdown
|
43
|
-
- doc/events.dot
|
44
43
|
- doc/faq.markdown
|
45
44
|
- doc/index.markdown
|
46
45
|
- doc/layout.html.erb
|
@@ -48,33 +47,28 @@ files:
|
|
48
47
|
- doc/rack-cache.css
|
49
48
|
- doc/server.ru
|
50
49
|
- doc/storage.markdown
|
50
|
+
- example/sinatra/app.rb
|
51
|
+
- example/sinatra/views/index.erb
|
51
52
|
- lib/rack/cache.rb
|
52
|
-
- lib/rack/cache/
|
53
|
-
- lib/rack/cache/config/busters.rb
|
54
|
-
- lib/rack/cache/config/default.rb
|
55
|
-
- lib/rack/cache/config/no-cache.rb
|
53
|
+
- lib/rack/cache/cachecontrol.rb
|
56
54
|
- lib/rack/cache/context.rb
|
57
|
-
- lib/rack/cache/core.rb
|
58
55
|
- lib/rack/cache/entitystore.rb
|
59
|
-
- lib/rack/cache/
|
56
|
+
- lib/rack/cache/key.rb
|
60
57
|
- lib/rack/cache/metastore.rb
|
61
58
|
- lib/rack/cache/options.rb
|
62
59
|
- lib/rack/cache/request.rb
|
63
60
|
- lib/rack/cache/response.rb
|
64
61
|
- lib/rack/cache/storage.rb
|
65
|
-
- lib/rack/utils/environment_headers.rb
|
66
62
|
- rack-cache.gemspec
|
67
63
|
- test/cache_test.rb
|
68
|
-
- test/
|
64
|
+
- test/cachecontrol_test.rb
|
69
65
|
- test/context_test.rb
|
70
|
-
- test/core_test.rb
|
71
66
|
- test/entitystore_test.rb
|
72
|
-
- test/
|
73
|
-
- test/headers_test.rb
|
74
|
-
- test/logging_test.rb
|
67
|
+
- test/key_test.rb
|
75
68
|
- test/metastore_test.rb
|
76
69
|
- test/options_test.rb
|
77
70
|
- test/pony.jpg
|
71
|
+
- test/request_test.rb
|
78
72
|
- test/response_test.rb
|
79
73
|
- test/spec_setup.rb
|
80
74
|
- test/storage_test.rb
|
@@ -111,14 +105,12 @@ specification_version: 2
|
|
111
105
|
summary: HTTP Caching for Rack
|
112
106
|
test_files:
|
113
107
|
- test/cache_test.rb
|
114
|
-
- test/
|
108
|
+
- test/cachecontrol_test.rb
|
115
109
|
- test/context_test.rb
|
116
|
-
- test/core_test.rb
|
117
110
|
- test/entitystore_test.rb
|
118
|
-
- test/
|
119
|
-
- test/headers_test.rb
|
120
|
-
- test/logging_test.rb
|
111
|
+
- test/key_test.rb
|
121
112
|
- test/metastore_test.rb
|
122
113
|
- test/options_test.rb
|
114
|
+
- test/request_test.rb
|
123
115
|
- test/response_test.rb
|
124
116
|
- test/storage_test.rb
|
data/doc/events.dot
DELETED
@@ -1,27 +0,0 @@
|
|
1
|
-
digraph cache_logic {
|
2
|
-
nodesep=1.25;
|
3
|
-
center=true;
|
4
|
-
|
5
|
-
node[fontname="Lucida Sans Unicode",labelloc=c,margin=0.10,0.03]
|
6
|
-
edge[fontname="Lucida Sans Unicode",fontcolor="#444444",labeldistance=20];
|
7
|
-
|
8
|
-
receive -> pass[label="uncacheable request",color=grey];
|
9
|
-
receive -> lookup[label="cacheable request"];
|
10
|
-
|
11
|
-
pass -> deliver[label="",color=grey];
|
12
|
-
|
13
|
-
lookup -> hit[label="fresh"];
|
14
|
-
lookup -> fetch[label="stale (needs validation)"];
|
15
|
-
lookup -> miss[label="uncached"];
|
16
|
-
|
17
|
-
hit -> deliver[label="sizzling"];
|
18
|
-
hit -> pass[label="bailing...",color=grey];
|
19
|
-
|
20
|
-
miss -> fetch[label=""];
|
21
|
-
miss -> pass[color=grey];
|
22
|
-
|
23
|
-
fetch -> store[label="cacheable"];
|
24
|
-
fetch -> deliver[label="not cacheable",color=grey];
|
25
|
-
|
26
|
-
store -> deliver[label="KTHX"];
|
27
|
-
}
|
data/lib/rack/cache/config.rb
DELETED
@@ -1,65 +0,0 @@
|
|
1
|
-
require 'set'
|
2
|
-
|
3
|
-
module Rack::Cache
|
4
|
-
# Provides cache configuration methods. This module is included in the cache
|
5
|
-
# context object.
|
6
|
-
|
7
|
-
module Config
|
8
|
-
# Evaluate a block of configuration code within the scope of receiver.
|
9
|
-
def configure(&block)
|
10
|
-
instance_eval(&block) if block_given?
|
11
|
-
end
|
12
|
-
|
13
|
-
# Import the configuration file specified. This has the same basic semantics
|
14
|
-
# as Ruby's built-in +require+ statement but always evaluates the source
|
15
|
-
# file within the scope of the receiver. The file may exist anywhere on the
|
16
|
-
# $LOAD_PATH.
|
17
|
-
def import(file)
|
18
|
-
return false if imported_features.include?(file)
|
19
|
-
path = add_file_extension(file, 'rb')
|
20
|
-
if path = locate_file_on_load_path(path)
|
21
|
-
source = File.read(path)
|
22
|
-
imported_features.add(file)
|
23
|
-
instance_eval source, path, 1
|
24
|
-
true
|
25
|
-
else
|
26
|
-
raise LoadError, 'no such file to load -- %s' % [file]
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
private
|
31
|
-
# Load the default configuration and evaluate the block provided within
|
32
|
-
# the scope of the receiver.
|
33
|
-
def initialize_config(&block)
|
34
|
-
import 'rack/cache/config/default'
|
35
|
-
configure(&block)
|
36
|
-
end
|
37
|
-
|
38
|
-
# Set of files that have been imported.
|
39
|
-
def imported_features
|
40
|
-
@imported_features ||= Set.new
|
41
|
-
end
|
42
|
-
|
43
|
-
# Attempt to expand +file+ to a full path by possibly adding an .rb
|
44
|
-
# extension and traversing the $LOAD_PATH looking for matches.
|
45
|
-
def locate_file_on_load_path(file)
|
46
|
-
if file[0,1] == '/'
|
47
|
-
file if File.exist?(file)
|
48
|
-
else
|
49
|
-
$LOAD_PATH.
|
50
|
-
map { |base| File.join(base, file) }.
|
51
|
-
detect { |p| File.exist?(p) }
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
# Add an extension to the filename provided if the file doesn't
|
56
|
-
# already have extension.
|
57
|
-
def add_file_extension(file, extension='rb')
|
58
|
-
if file =~ /\.\w+$/
|
59
|
-
file
|
60
|
-
else
|
61
|
-
"#{file}.#{extension}"
|
62
|
-
end
|
63
|
-
end
|
64
|
-
end
|
65
|
-
end
|
@@ -1,16 +0,0 @@
|
|
1
|
-
# Adds a very long max-age response header when the requested url
|
2
|
-
# looks like it includes a cache busting timestamp. Cache busting
|
3
|
-
# URLs look like this:
|
4
|
-
# http://HOST/PATH?DIGITS
|
5
|
-
#
|
6
|
-
# DIGITS is typically the number of seconds since some epoch but
|
7
|
-
# this can theoretically be any set of digits. Example:
|
8
|
-
# http://example.com/css/foo.css?7894387283
|
9
|
-
#
|
10
|
-
on :fetch do
|
11
|
-
next if response.freshness_information?
|
12
|
-
if request.url =~ /\?\d+$/
|
13
|
-
trace 'adding huge max-age to response for cache busting URL'
|
14
|
-
response.ttl = 100000000000000
|
15
|
-
end
|
16
|
-
end
|
@@ -1,133 +0,0 @@
|
|
1
|
-
# Called at the beginning of request processing, after the complete
|
2
|
-
# request has been fully received. Its purpose is to decide whether or
|
3
|
-
# not to serve the request and how to do it.
|
4
|
-
#
|
5
|
-
# The request should not be modified.
|
6
|
-
#
|
7
|
-
# Possible transitions from receive:
|
8
|
-
#
|
9
|
-
# * pass! - pass the request to the backend the response upstream,
|
10
|
-
# bypassing all caching features.
|
11
|
-
#
|
12
|
-
# * lookup! - attempt to locate the entry in the cache. Control will
|
13
|
-
# pass to the +hit+, +miss+, or +fetch+ event based on the result of
|
14
|
-
# the cache lookup.
|
15
|
-
#
|
16
|
-
# * error! - return the error code specified, abandoning the request.
|
17
|
-
#
|
18
|
-
on :receive do
|
19
|
-
pass! unless request.method? 'GET', 'HEAD'
|
20
|
-
pass! if request.header? 'Expect'
|
21
|
-
lookup!
|
22
|
-
end
|
23
|
-
|
24
|
-
# Called upon entering pass mode. The request is sent to the backend,
|
25
|
-
# and the backend's response is sent to the client, but is not entered
|
26
|
-
# into the cache. The event is triggered immediately after the response
|
27
|
-
# is received from the backend but before the it has been sent upstream.
|
28
|
-
#
|
29
|
-
# Possible transitions from pass:
|
30
|
-
#
|
31
|
-
# * finish! - deliver the response upstream.
|
32
|
-
#
|
33
|
-
# * error! - return the error code specified, abandoning the request.
|
34
|
-
#
|
35
|
-
on :pass do
|
36
|
-
finish!
|
37
|
-
end
|
38
|
-
|
39
|
-
# Called after a cache lookup when no matching entry is found in the
|
40
|
-
# cache. Its purpose is to decide whether or not to attempt to retrieve
|
41
|
-
# the response from the backend and in what manner.
|
42
|
-
#
|
43
|
-
# Possible transitions from miss:
|
44
|
-
#
|
45
|
-
# * fetch! - retrieve the requested document from the backend with
|
46
|
-
# caching features enabled.
|
47
|
-
#
|
48
|
-
# * pass! - pass the request to the backend and the response upstream,
|
49
|
-
# bypassing all caching features.
|
50
|
-
#
|
51
|
-
# * error! - return the error code specified and abandon request.
|
52
|
-
#
|
53
|
-
# The default configuration transfers control to the fetch event.
|
54
|
-
on :miss do
|
55
|
-
fetch!
|
56
|
-
end
|
57
|
-
|
58
|
-
# Called after a cache lookup when the requested document is found in
|
59
|
-
# the cache and is fresh.
|
60
|
-
#
|
61
|
-
# Possible transitions from hit:
|
62
|
-
#
|
63
|
-
# * deliver! - transfer control to the deliver event, sending the cached
|
64
|
-
# response upstream.
|
65
|
-
#
|
66
|
-
# * pass! - abandon the cache entry and transfer to pass mode. The
|
67
|
-
# original request is sent to the backend and the response sent
|
68
|
-
# upstream, bypassing all caching features.
|
69
|
-
#
|
70
|
-
# * error! - return the error code specified and abandon request.
|
71
|
-
#
|
72
|
-
on :hit do
|
73
|
-
deliver!
|
74
|
-
end
|
75
|
-
|
76
|
-
# Called after a document is successfully retrieved from the backend
|
77
|
-
# application or after a cache entry is validated with the backend.
|
78
|
-
# During validation, the original request is used as a template for a
|
79
|
-
# conditional GET request with the backend. The +original_response+
|
80
|
-
# object contains the response as received from the backend and +entry+
|
81
|
-
# is set to the cached response that triggered validation.
|
82
|
-
#
|
83
|
-
# Possible transitions from fetch:
|
84
|
-
#
|
85
|
-
# * store! - store the fetched response in the cache or, when
|
86
|
-
# validating, update the cached response with validated results.
|
87
|
-
#
|
88
|
-
# * deliver! - deliver the response upstream without entering it
|
89
|
-
# into the cache.
|
90
|
-
#
|
91
|
-
# * error! return the error code specified and abandon request.
|
92
|
-
#
|
93
|
-
on :fetch do
|
94
|
-
store! if response.cacheable?
|
95
|
-
deliver!
|
96
|
-
end
|
97
|
-
|
98
|
-
# Called immediately before an entry is written to the underlying
|
99
|
-
# cache. The +entry+ object may be modified.
|
100
|
-
#
|
101
|
-
# Possible transitions from store:
|
102
|
-
#
|
103
|
-
# * persist! - commit the object to cache and transfer control to
|
104
|
-
# the deliver event.
|
105
|
-
#
|
106
|
-
# * deliver! - transfer control to the deliver event without committing
|
107
|
-
# the object to cache.
|
108
|
-
#
|
109
|
-
# * error! - return the error code specified and abandon request.
|
110
|
-
#
|
111
|
-
on :store do
|
112
|
-
trace 'store backend response in cache (ttl: %ds)', entry.ttl
|
113
|
-
persist!
|
114
|
-
end
|
115
|
-
|
116
|
-
# Called immediately before +response+ is delivered upstream. +response+
|
117
|
-
# may be modified at this point but the changes will not effect the
|
118
|
-
# cache since the entry has already been persisted.
|
119
|
-
#
|
120
|
-
# * finish! - complete processing and send the response upstream
|
121
|
-
#
|
122
|
-
# * error! - return the error code specified and abandon request.
|
123
|
-
#
|
124
|
-
on :deliver do
|
125
|
-
finish!
|
126
|
-
end
|
127
|
-
|
128
|
-
# Called when an error! transition is triggered. The +response+ has the
|
129
|
-
# error code, headers, and body that will be delivered to upstream and
|
130
|
-
# may be modified if needed.
|
131
|
-
on :error do
|
132
|
-
finish!
|
133
|
-
end
|
@@ -1,13 +0,0 @@
|
|
1
|
-
# The default configuration ignores the `Cache-Control: no-cache` directive on
|
2
|
-
# requests. Per RFC 2616, the presence of the no-cache directive should cause
|
3
|
-
# intermediaries to process requests as if no cached version were available.
|
4
|
-
# However, this directive is most often targetted at shared proxy caches, not
|
5
|
-
# gateway caches, and so we've chosen to break with the spec in our default
|
6
|
-
# configuration.
|
7
|
-
#
|
8
|
-
# Import 'rack/cache/config/no-cache' to enable standards-based
|
9
|
-
# processing.
|
10
|
-
|
11
|
-
on :receive do
|
12
|
-
pass! if request.header['Cache-Control'] =~ /no-cache/
|
13
|
-
end
|
data/lib/rack/cache/core.rb
DELETED
@@ -1,299 +0,0 @@
|
|
1
|
-
require 'rack/cache/request'
|
2
|
-
require 'rack/cache/response'
|
3
|
-
|
4
|
-
module Rack::Cache
|
5
|
-
# Raised when an attempt is made to transition to an event that can
|
6
|
-
# not be transitioned from the current event.
|
7
|
-
class IllegalTransition < Exception
|
8
|
-
end
|
9
|
-
|
10
|
-
# The core logic engine and state machine. When a request is received,
|
11
|
-
# the engine begins transitioning from state to state based on the
|
12
|
-
# advice given by events. Each transition performs some piece of core
|
13
|
-
# logic, calls out to an event handler, and then kicks off the next
|
14
|
-
# transition.
|
15
|
-
#
|
16
|
-
# Five objects of interest are made available during execution:
|
17
|
-
#
|
18
|
-
# * +original_request+ - The request as originally received. This object
|
19
|
-
# is never modified.
|
20
|
-
# * +request+ - The request that may eventually be sent downstream in
|
21
|
-
# case of pass or miss. This object defaults to the +original_request+
|
22
|
-
# but may be modified or replaced entirely.
|
23
|
-
# * +original_response+ - The response exactly as specified by the
|
24
|
-
# downstream application; +nil+ on cache hit.
|
25
|
-
# * +entry+ - The response loaded from cache or stored to cache. This
|
26
|
-
# object becomes +response+ if the cached response is valid.
|
27
|
-
# * +response+ - The response that will be delivered upstream after
|
28
|
-
# processing is complete. This object may be modified as necessary.
|
29
|
-
#
|
30
|
-
# These objects can be accessed and modified from within event handlers
|
31
|
-
# to perform various types of request/response manipulation.
|
32
|
-
module Core
|
33
|
-
|
34
|
-
# The request exactly as received. The object is an instance of the
|
35
|
-
# Rack::Cache::Request class, which includes many utility methods for
|
36
|
-
# inspecting the state of the request.
|
37
|
-
#
|
38
|
-
# This object cannot be modified. If the request requires modification
|
39
|
-
# before being delivered to the downstream application, use the
|
40
|
-
# #request object.
|
41
|
-
attr_reader :original_request
|
42
|
-
|
43
|
-
# The response exactly as received from the downstream application. The
|
44
|
-
# object is an instance of the Rack::Cache::Response class, which includes
|
45
|
-
# utility methods for inspecting the state of the response.
|
46
|
-
#
|
47
|
-
# The original response should not be modified. Use the #response object to
|
48
|
-
# access the response to be sent back upstream.
|
49
|
-
attr_reader :original_response
|
50
|
-
|
51
|
-
# A response object retrieved from cache, or the response that is to be
|
52
|
-
# saved to cache, or nil if no cached response was found. The object is
|
53
|
-
# an instance of the Rack::Cache::Response class.
|
54
|
-
attr_reader :entry
|
55
|
-
|
56
|
-
# The request that will be made downstream on the application. This
|
57
|
-
# defaults to the request exactly as received (#original_request). The
|
58
|
-
# object is an instance of the Rack::Cache::Request class, which includes
|
59
|
-
# utility methods for inspecting and modifying various aspects of the
|
60
|
-
# HTTP request.
|
61
|
-
attr_reader :request
|
62
|
-
|
63
|
-
# The response that will be sent upstream. Defaults to the response
|
64
|
-
# received from the downstream application (#original_response) but
|
65
|
-
# is set to the cached #entry when valid. In any case, the object
|
66
|
-
# is an instance of the Rack::Cache::Response class, which includes a
|
67
|
-
# variety of utility methods for inspecting and modifying the HTTP
|
68
|
-
# response.
|
69
|
-
attr_reader :response
|
70
|
-
|
71
|
-
# Has the given event been performed at any time during the
|
72
|
-
# request life-cycle? Useful for testing.
|
73
|
-
def performed?(event)
|
74
|
-
@triggered.include?(event)
|
75
|
-
end
|
76
|
-
|
77
|
-
# Event handlers.
|
78
|
-
attr_reader :events
|
79
|
-
private :events
|
80
|
-
|
81
|
-
public
|
82
|
-
# Attach custom logic to one or more events.
|
83
|
-
def on(*events, &block)
|
84
|
-
events.each { |event| @events[event].unshift(block) }
|
85
|
-
nil
|
86
|
-
end
|
87
|
-
|
88
|
-
private
|
89
|
-
# Transitioning statements
|
90
|
-
|
91
|
-
def pass! ; throw(:transition, [:pass]) ; end
|
92
|
-
def lookup! ; throw(:transition, [:lookup]) ; end
|
93
|
-
def store! ; throw(:transition, [:store]) ; end
|
94
|
-
def fetch! ; throw(:transition, [:fetch]) ; end
|
95
|
-
def persist! ; throw(:transition, [:persist]) ; end
|
96
|
-
def deliver! ; throw(:transition, [:deliver]) ; end
|
97
|
-
def finish! ; throw(:transition, [:finish]) ; end
|
98
|
-
|
99
|
-
def error!(code=500, headers={}, body=nil)
|
100
|
-
throw(:transition, [:error, code, headers, body])
|
101
|
-
end
|
102
|
-
|
103
|
-
private
|
104
|
-
# Does the request include authorization or other sensitive information
|
105
|
-
# that should cause the response to be considered private by default?
|
106
|
-
# Private responses are not stored in the cache.
|
107
|
-
def private_request?
|
108
|
-
request.header?(*private_headers)
|
109
|
-
end
|
110
|
-
|
111
|
-
# Determine if the #response validators (ETag, Last-Modified) matches
|
112
|
-
# a conditional value specified in #original_request.
|
113
|
-
def not_modified?
|
114
|
-
response.etag_matches?(original_request.if_none_match) ||
|
115
|
-
response.last_modified_at?(original_request.if_modified_since)
|
116
|
-
end
|
117
|
-
|
118
|
-
# Delegate the request to the backend and create the response.
|
119
|
-
def fetch_from_backend
|
120
|
-
status, headers, body = backend.call(request.env)
|
121
|
-
response = Response.new(status, headers, body)
|
122
|
-
@response = response.dup
|
123
|
-
@original_response = response.freeze
|
124
|
-
end
|
125
|
-
|
126
|
-
private
|
127
|
-
def perform_receive
|
128
|
-
@original_request = Request.new(@env.dup.freeze)
|
129
|
-
@env['REQUEST_METHOD'] = 'GET' if @original_request.head?
|
130
|
-
@request = Request.new(@env)
|
131
|
-
info "%s %s", @original_request.request_method, @original_request.fullpath
|
132
|
-
transition(from=:receive, to=[:pass, :lookup, :error])
|
133
|
-
end
|
134
|
-
|
135
|
-
def perform_pass
|
136
|
-
trace 'passing'
|
137
|
-
request.env['REQUEST_METHOD'] = @original_request.request_method
|
138
|
-
fetch_from_backend
|
139
|
-
transition(from=:pass, to=[:pass, :finish, :error]) do |event|
|
140
|
-
if event == :pass
|
141
|
-
:finish
|
142
|
-
else
|
143
|
-
event
|
144
|
-
end
|
145
|
-
end
|
146
|
-
end
|
147
|
-
|
148
|
-
def perform_error(code=500, headers={}, body=nil)
|
149
|
-
body, headers = headers, {} unless headers.is_a?(Hash)
|
150
|
-
headers = {} if headers.nil?
|
151
|
-
body = [] if body.nil? || body == ''
|
152
|
-
@response = Rack::Cache::Response.new(code, headers, body)
|
153
|
-
transition(from=:error, to=[:finish])
|
154
|
-
end
|
155
|
-
|
156
|
-
def perform_lookup
|
157
|
-
if @entry = metastore.lookup(original_request, entitystore)
|
158
|
-
if @entry.fresh?
|
159
|
-
trace 'cache hit (ttl: %ds)', @entry.ttl
|
160
|
-
transition(from=:hit, to=[:deliver, :pass, :error]) do |event|
|
161
|
-
@response = @entry if event == :deliver
|
162
|
-
event
|
163
|
-
end
|
164
|
-
else
|
165
|
-
trace 'cache stale (ttl: %ds), validating...', @entry.ttl
|
166
|
-
perform_validate
|
167
|
-
end
|
168
|
-
else
|
169
|
-
trace 'cache miss'
|
170
|
-
transition(from=:miss, to=[:fetch, :pass, :error])
|
171
|
-
end
|
172
|
-
end
|
173
|
-
|
174
|
-
def perform_validate
|
175
|
-
# add our cached validators to the backend request
|
176
|
-
request.headers['If-Modified-Since'] = entry.last_modified
|
177
|
-
request.headers['If-None-Match'] = entry.etag
|
178
|
-
fetch_from_backend
|
179
|
-
|
180
|
-
if original_response.status == 304
|
181
|
-
trace "cache entry valid"
|
182
|
-
@response = entry.dup
|
183
|
-
@response.headers.delete('Age')
|
184
|
-
@response.headers.delete('Date')
|
185
|
-
@response.headers['X-Origin-Status'] = '304'
|
186
|
-
%w[Date Expires Cache-Control Etag Last-Modified].each do |name|
|
187
|
-
next unless value = original_response.headers[name]
|
188
|
-
@response[name] = value
|
189
|
-
end
|
190
|
-
@response.activate!
|
191
|
-
else
|
192
|
-
trace "cache entry invalid"
|
193
|
-
@entry = nil
|
194
|
-
end
|
195
|
-
transition(from=:fetch, to=[:store, :deliver, :error])
|
196
|
-
end
|
197
|
-
|
198
|
-
def perform_fetch
|
199
|
-
trace "fetching response from backend"
|
200
|
-
request.env.delete('HTTP_IF_MODIFIED_SINCE')
|
201
|
-
request.env.delete('HTTP_IF_NONE_MATCH')
|
202
|
-
fetch_from_backend
|
203
|
-
|
204
|
-
# mark the response as explicitly private if any of the private
|
205
|
-
# request headers are present and the response was not explicitly
|
206
|
-
# declared public.
|
207
|
-
if private_request? && !@response.public?
|
208
|
-
@response.private = true
|
209
|
-
else
|
210
|
-
# assign a default TTL for the cache entry if none was specified in
|
211
|
-
# the response; the must-revalidate cache control directive disables
|
212
|
-
# default ttl assigment.
|
213
|
-
if default_ttl > 0 && @response.ttl.nil? && !@response.must_revalidate?
|
214
|
-
@response.ttl = default_ttl
|
215
|
-
end
|
216
|
-
end
|
217
|
-
transition(from=:fetch, to=[:store, :deliver, :error])
|
218
|
-
end
|
219
|
-
|
220
|
-
def perform_store
|
221
|
-
@entry = @response
|
222
|
-
transition(from=:store, to=[:persist, :deliver, :error]) do |event|
|
223
|
-
if event == :persist
|
224
|
-
if @response.private?
|
225
|
-
warn 'forced to store response marked as private.'
|
226
|
-
else
|
227
|
-
trace "storing response in cache"
|
228
|
-
end
|
229
|
-
metastore.store(original_request, @entry, entitystore)
|
230
|
-
@response = @entry
|
231
|
-
:deliver
|
232
|
-
else
|
233
|
-
event
|
234
|
-
end
|
235
|
-
end
|
236
|
-
end
|
237
|
-
|
238
|
-
def perform_deliver
|
239
|
-
trace "delivering response ..."
|
240
|
-
response.not_modified! if not_modified?
|
241
|
-
response.body = [] if @original_request.head?
|
242
|
-
transition(from=:deliver, to=[:finish, :error])
|
243
|
-
end
|
244
|
-
|
245
|
-
def perform_finish
|
246
|
-
response.headers.delete 'X-Status'
|
247
|
-
response.to_a
|
248
|
-
end
|
249
|
-
|
250
|
-
private
|
251
|
-
# Transition from the currently processing event to another event
|
252
|
-
# after triggering event handlers.
|
253
|
-
def transition(from, to)
|
254
|
-
ev, *args = trigger(from)
|
255
|
-
raise IllegalTransition, "No transition to :#{ev}" unless to.include?(ev)
|
256
|
-
ev = yield ev if block_given?
|
257
|
-
send "perform_#{ev}", *args
|
258
|
-
end
|
259
|
-
|
260
|
-
# Trigger processing of the event specified and return an array containing
|
261
|
-
# the name of the next transition and any arguments provided to the
|
262
|
-
# transitioning statement.
|
263
|
-
def trigger(event)
|
264
|
-
if @events.include? event
|
265
|
-
@triggered << event
|
266
|
-
catch(:transition) do
|
267
|
-
@events[event].each { |block| instance_eval(&block) }
|
268
|
-
nil
|
269
|
-
end
|
270
|
-
else
|
271
|
-
raise NameError, "No such event: #{event}"
|
272
|
-
end
|
273
|
-
end
|
274
|
-
|
275
|
-
private
|
276
|
-
# Setup the core prototype. The object's state after execution
|
277
|
-
# of this method will be duped and used for individual request.
|
278
|
-
def initialize_core
|
279
|
-
@triggered = []
|
280
|
-
@events = Hash.new { |h,k| h[k.to_sym] = [] }
|
281
|
-
|
282
|
-
# initialize some instance variables; we won't use them until we dup to
|
283
|
-
# process a request.
|
284
|
-
@request = nil
|
285
|
-
@response = nil
|
286
|
-
@original_request = nil
|
287
|
-
@original_response = nil
|
288
|
-
@entry = nil
|
289
|
-
end
|
290
|
-
|
291
|
-
# Process a request. This method is compatible with Rack's #call
|
292
|
-
# interface.
|
293
|
-
def process_request(env)
|
294
|
-
@triggered = []
|
295
|
-
@env = @default_options.merge(env)
|
296
|
-
perform_receive
|
297
|
-
end
|
298
|
-
end
|
299
|
-
end
|