rtomayko-rack-cache 0.3.0 → 0.3.9
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES +41 -0
- data/README +0 -1
- data/TODO +14 -10
- data/doc/configuration.markdown +7 -153
- data/doc/index.markdown +1 -3
- 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 +188 -51
- 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 +29 -13
- 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 -14
- data/test/cache_test.rb +4 -1
- data/test/cachecontrol_test.rb +139 -0
- data/test/context_test.rb +198 -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 +6 -0
- metadata +13 -19
- 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
@@ -27,6 +27,7 @@ rescue LoadError => boom
|
|
27
27
|
$memcached = false
|
28
28
|
false
|
29
29
|
rescue => boom
|
30
|
+
STDERR.puts "memcached not working. related tests will be skipped."
|
30
31
|
$memcached = false
|
31
32
|
false
|
32
33
|
end
|
@@ -192,4 +193,9 @@ class Object
|
|
192
193
|
def class_def name, &blk
|
193
194
|
class_eval { define_method name, &blk }
|
194
195
|
end
|
196
|
+
|
197
|
+
# True when the Object is neither false or nil.
|
198
|
+
def truthy?
|
199
|
+
!!self
|
200
|
+
end
|
195
201
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rtomayko-rack-cache
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.9
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ryan Tomayko
|
@@ -9,11 +9,12 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date:
|
12
|
+
date: 2009-03-07 00:00:00 -08:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: rack
|
17
|
+
type: :runtime
|
17
18
|
version_requirement:
|
18
19
|
version_requirements: !ruby/object:Gem::Requirement
|
19
20
|
requirements:
|
@@ -47,33 +48,28 @@ files:
|
|
47
48
|
- doc/rack-cache.css
|
48
49
|
- doc/server.ru
|
49
50
|
- doc/storage.markdown
|
51
|
+
- example/sinatra/app.rb
|
52
|
+
- example/sinatra/views/index.erb
|
50
53
|
- lib/rack/cache.rb
|
51
|
-
- lib/rack/cache/
|
52
|
-
- lib/rack/cache/config/busters.rb
|
53
|
-
- lib/rack/cache/config/default.rb
|
54
|
-
- lib/rack/cache/config/no-cache.rb
|
54
|
+
- lib/rack/cache/cachecontrol.rb
|
55
55
|
- lib/rack/cache/context.rb
|
56
|
-
- lib/rack/cache/core.rb
|
57
56
|
- lib/rack/cache/entitystore.rb
|
58
|
-
- lib/rack/cache/
|
57
|
+
- lib/rack/cache/key.rb
|
59
58
|
- lib/rack/cache/metastore.rb
|
60
59
|
- lib/rack/cache/options.rb
|
61
60
|
- lib/rack/cache/request.rb
|
62
61
|
- lib/rack/cache/response.rb
|
63
62
|
- lib/rack/cache/storage.rb
|
64
|
-
- lib/rack/utils/environment_headers.rb
|
65
63
|
- rack-cache.gemspec
|
66
64
|
- test/cache_test.rb
|
67
|
-
- test/
|
65
|
+
- test/cachecontrol_test.rb
|
68
66
|
- test/context_test.rb
|
69
|
-
- test/core_test.rb
|
70
67
|
- test/entitystore_test.rb
|
71
|
-
- test/
|
72
|
-
- test/headers_test.rb
|
73
|
-
- test/logging_test.rb
|
68
|
+
- test/key_test.rb
|
74
69
|
- test/metastore_test.rb
|
75
70
|
- test/options_test.rb
|
76
71
|
- test/pony.jpg
|
72
|
+
- test/request_test.rb
|
77
73
|
- test/response_test.rb
|
78
74
|
- test/spec_setup.rb
|
79
75
|
- test/storage_test.rb
|
@@ -110,14 +106,12 @@ specification_version: 2
|
|
110
106
|
summary: HTTP Caching for Rack
|
111
107
|
test_files:
|
112
108
|
- test/cache_test.rb
|
113
|
-
- test/
|
109
|
+
- test/cachecontrol_test.rb
|
114
110
|
- test/context_test.rb
|
115
|
-
- test/core_test.rb
|
116
111
|
- test/entitystore_test.rb
|
117
|
-
- test/
|
118
|
-
- test/headers_test.rb
|
119
|
-
- test/logging_test.rb
|
112
|
+
- test/key_test.rb
|
120
113
|
- test/metastore_test.rb
|
121
114
|
- test/options_test.rb
|
115
|
+
- test/request_test.rb
|
122
116
|
- test/response_test.rb
|
123
117
|
- test/storage_test.rb
|
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
|