rtomayko-rack-cache 0.3.0 → 0.3.9
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.
- 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/cache_test.rb
CHANGED
@@ -12,22 +12,25 @@ describe 'Rack::Cache::new' do
|
|
12
12
|
Rack::Cache.new(@app).
|
13
13
|
should.respond_to :call
|
14
14
|
end
|
15
|
+
|
15
16
|
it 'takes an options Hash' do
|
16
17
|
lambda { Rack::Cache.new(@app, {}) }.
|
17
18
|
should.not.raise(ArgumentError)
|
18
19
|
end
|
20
|
+
|
19
21
|
it 'sets options provided in the options Hash' do
|
20
22
|
object = Rack::Cache.new(@app, :foo => 'bar', 'foo.bar' => 'bling')
|
21
23
|
object.options['foo.bar'].should.equal 'bling'
|
22
24
|
object.options['rack-cache.foo'].should.equal 'bar'
|
23
25
|
end
|
26
|
+
|
24
27
|
it 'takes a block; executes it during initialization' do
|
25
28
|
state, block_scope = 'not invoked', nil
|
26
29
|
object =
|
27
30
|
Rack::Cache.new @app do
|
28
31
|
block_scope = self
|
29
32
|
state = 'invoked'
|
30
|
-
should.respond_to :
|
33
|
+
should.respond_to :set
|
31
34
|
end
|
32
35
|
state.should.equal 'invoked'
|
33
36
|
object.should.be block_scope
|
@@ -0,0 +1,139 @@
|
|
1
|
+
require "#{File.dirname(__FILE__)}/spec_setup"
|
2
|
+
require 'rack/cache/cachecontrol'
|
3
|
+
|
4
|
+
describe 'Rack::Cache::CacheControl' do
|
5
|
+
it 'takes no args and initializes with an empty set of values' do
|
6
|
+
cache_control = Rack::Cache::CacheControl.new
|
7
|
+
cache_control.should.be.empty
|
8
|
+
cache_control.to_s.should.equal ''
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'takes a String and parses it into a Hash when created' do
|
12
|
+
cache_control = Rack::Cache::CacheControl.new('max-age=600, foo')
|
13
|
+
cache_control['max-age'].should.equal '600'
|
14
|
+
cache_control['foo'].should.be true
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'takes a String with a single name=value pair' do
|
18
|
+
cache_control = Rack::Cache::CacheControl.new('max-age=600')
|
19
|
+
cache_control['max-age'].should.equal '600'
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'takes a String with multiple name=value pairs' do
|
23
|
+
cache_control = Rack::Cache::CacheControl.new('max-age=600, max-stale=300, min-fresh=570')
|
24
|
+
cache_control['max-age'].should.equal '600'
|
25
|
+
cache_control['max-stale'].should.equal '300'
|
26
|
+
cache_control['min-fresh'].should.equal '570'
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'takes a String with a single flag value' do
|
30
|
+
cache_control = Rack::Cache::CacheControl.new('no-cache')
|
31
|
+
cache_control.should.include 'no-cache'
|
32
|
+
cache_control['no-cache'].should.be true
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'takes a String with a bunch of all kinds of stuff' do
|
36
|
+
cache_control =
|
37
|
+
Rack::Cache::CacheControl.new('max-age=600,must-revalidate,min-fresh=3000,foo=bar,baz')
|
38
|
+
cache_control['max-age'].should.equal '600'
|
39
|
+
cache_control['must-revalidate'].should.be true
|
40
|
+
cache_control['min-fresh'].should.equal '3000'
|
41
|
+
cache_control['foo'].should.equal 'bar'
|
42
|
+
cache_control['baz'].should.be true
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'strips leading and trailing spaces from header value' do
|
46
|
+
cache_control = Rack::Cache::CacheControl.new(' public, max-age = 600 ')
|
47
|
+
cache_control.should.include 'public'
|
48
|
+
cache_control.should.include 'max-age'
|
49
|
+
cache_control['max-age'].should.equal '600'
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'removes all directives with #clear' do
|
53
|
+
cache_control = Rack::Cache::CacheControl.new('max-age=600, must-revalidate')
|
54
|
+
cache_control.clear
|
55
|
+
cache_control.should.be.empty
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'converts self into header String with #to_s' do
|
59
|
+
cache_control = Rack::Cache::CacheControl.new
|
60
|
+
cache_control['public'] = true
|
61
|
+
cache_control['max-age'] = '600'
|
62
|
+
cache_control.to_s.split(', ').sort.should.equal ['max-age=600', 'public']
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'sorts alphabetically with boolean directives before value directives' do
|
66
|
+
cache_control = Rack::Cache::CacheControl.new('foo=bar, z, x, y, bling=baz, zoom=zib, b, a')
|
67
|
+
cache_control.to_s.should.equal 'a, b, x, y, z, bling=baz, foo=bar, zoom=zib'
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'responds to #max_age with an integer when max-age directive present' do
|
71
|
+
cache_control = Rack::Cache::CacheControl.new('public, max-age=600')
|
72
|
+
cache_control.max_age.should.equal 600
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'responds to #max_age with nil when no max-age directive present' do
|
76
|
+
cache_control = Rack::Cache::CacheControl.new('public')
|
77
|
+
cache_control.max_age.should.be nil
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'responds to #shared_max_age with an integer when s-maxage directive present' do
|
81
|
+
cache_control = Rack::Cache::CacheControl.new('public, s-maxage=600')
|
82
|
+
cache_control.shared_max_age.should.equal 600
|
83
|
+
end
|
84
|
+
|
85
|
+
it 'responds to #shared_max_age with nil when no s-maxage directive present' do
|
86
|
+
cache_control = Rack::Cache::CacheControl.new('public')
|
87
|
+
cache_control.shared_max_age.should.be nil
|
88
|
+
end
|
89
|
+
|
90
|
+
it 'responds to #public? truthfully when public directive present' do
|
91
|
+
cache_control = Rack::Cache::CacheControl.new('public')
|
92
|
+
cache_control.should.be.public
|
93
|
+
end
|
94
|
+
|
95
|
+
it 'responds to #public? non-truthfully when no public directive present' do
|
96
|
+
cache_control = Rack::Cache::CacheControl.new('private')
|
97
|
+
cache_control.should.not.be.public
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'responds to #private? truthfully when private directive present' do
|
101
|
+
cache_control = Rack::Cache::CacheControl.new('private')
|
102
|
+
cache_control.should.be.private
|
103
|
+
end
|
104
|
+
|
105
|
+
it 'responds to #private? non-truthfully when no private directive present' do
|
106
|
+
cache_control = Rack::Cache::CacheControl.new('public')
|
107
|
+
cache_control.should.not.be.private
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'responds to #no_cache? truthfully when no-cache directive present' do
|
111
|
+
cache_control = Rack::Cache::CacheControl.new('no-cache')
|
112
|
+
cache_control.should.be.no_cache
|
113
|
+
end
|
114
|
+
|
115
|
+
it 'responds to #no_cache? non-truthfully when no no-cache directive present' do
|
116
|
+
cache_control = Rack::Cache::CacheControl.new('max-age=600')
|
117
|
+
cache_control.should.not.be.no_cache
|
118
|
+
end
|
119
|
+
|
120
|
+
it 'responds to #must_revalidate? truthfully when must-revalidate directive present' do
|
121
|
+
cache_control = Rack::Cache::CacheControl.new('must-revalidate')
|
122
|
+
cache_control.should.be.must_revalidate
|
123
|
+
end
|
124
|
+
|
125
|
+
it 'responds to #must_revalidate? non-truthfully when no must-revalidate directive present' do
|
126
|
+
cache_control = Rack::Cache::CacheControl.new('max-age=600')
|
127
|
+
cache_control.should.not.be.no_cache
|
128
|
+
end
|
129
|
+
|
130
|
+
it 'responds to #proxy_revalidate? truthfully when proxy-revalidate directive present' do
|
131
|
+
cache_control = Rack::Cache::CacheControl.new('proxy-revalidate')
|
132
|
+
cache_control.should.be.proxy_revalidate
|
133
|
+
end
|
134
|
+
|
135
|
+
it 'responds to #proxy_revalidate? non-truthfully when no proxy-revalidate directive present' do
|
136
|
+
cache_control = Rack::Cache::CacheControl.new('max-age=600')
|
137
|
+
cache_control.should.not.be.no_cache
|
138
|
+
end
|
139
|
+
end
|
data/test/context_test.rb
CHANGED
@@ -11,21 +11,31 @@ describe 'Rack::Cache::Context' do
|
|
11
11
|
|
12
12
|
app.should.be.called
|
13
13
|
response.should.be.ok
|
14
|
-
cache.should.
|
14
|
+
cache.trace.should.include :pass
|
15
15
|
response.headers.should.not.include 'Age'
|
16
16
|
end
|
17
17
|
|
18
|
+
%w[post put delete].each do |request_method|
|
19
|
+
it "invalidates on #{request_method} requests" do
|
20
|
+
respond_with 200
|
21
|
+
request request_method, '/'
|
22
|
+
|
23
|
+
app.should.be.called
|
24
|
+
response.should.be.ok
|
25
|
+
cache.trace.should.include :invalidate
|
26
|
+
cache.trace.should.include :pass
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
18
30
|
it 'does not cache with Authorization request header and non public response' do
|
19
|
-
respond_with 200, '
|
31
|
+
respond_with 200, 'ETag' => '"FOO"'
|
20
32
|
get '/', 'HTTP_AUTHORIZATION' => 'basic foobarbaz'
|
21
33
|
|
22
34
|
app.should.be.called
|
23
35
|
response.should.be.ok
|
24
36
|
response.headers['Cache-Control'].should.equal 'private'
|
25
|
-
cache.should.
|
26
|
-
cache.should.
|
27
|
-
cache.should.a.not.performed :store
|
28
|
-
cache.should.a.performed :deliver
|
37
|
+
cache.trace.should.include :miss
|
38
|
+
cache.trace.should.not.include :store
|
29
39
|
response.headers.should.not.include 'Age'
|
30
40
|
end
|
31
41
|
|
@@ -35,24 +45,21 @@ describe 'Rack::Cache::Context' do
|
|
35
45
|
|
36
46
|
app.should.be.called
|
37
47
|
response.should.be.ok
|
38
|
-
cache.should.
|
39
|
-
cache.should.
|
40
|
-
cache.should.a.performed :store
|
48
|
+
cache.trace.should.include :miss
|
49
|
+
cache.trace.should.include :store
|
41
50
|
response.headers.should.include 'Age'
|
42
51
|
response.headers['Cache-Control'].should.equal 'public'
|
43
52
|
end
|
44
53
|
|
45
54
|
it 'does not cache with Cookie header and non public response' do
|
46
|
-
respond_with 200, '
|
55
|
+
respond_with 200, 'ETag' => '"FOO"'
|
47
56
|
get '/', 'HTTP_COOKIE' => 'foo=bar'
|
48
57
|
|
49
58
|
app.should.be.called
|
50
59
|
response.should.be.ok
|
51
60
|
response.headers['Cache-Control'].should.equal 'private'
|
52
|
-
cache.should.
|
53
|
-
cache.should.
|
54
|
-
cache.should.a.not.performed :store
|
55
|
-
cache.should.a.performed :deliver
|
61
|
+
cache.trace.should.include :miss
|
62
|
+
cache.trace.should.not.include :store
|
56
63
|
response.headers.should.not.include 'Age'
|
57
64
|
end
|
58
65
|
|
@@ -62,9 +69,8 @@ describe 'Rack::Cache::Context' do
|
|
62
69
|
|
63
70
|
response.should.be.ok
|
64
71
|
app.should.be.called
|
65
|
-
cache.should.
|
66
|
-
cache.should.
|
67
|
-
cache.should.a.performed :deliver
|
72
|
+
cache.trace.should.include :miss
|
73
|
+
cache.trace.should.not.include :store
|
68
74
|
response.headers.should.not.include 'Age'
|
69
75
|
response.headers['Cache-Control'].should.equal 'private'
|
70
76
|
end
|
@@ -85,14 +91,14 @@ describe 'Rack::Cache::Context' do
|
|
85
91
|
response.headers.should.not.include 'Content-Length'
|
86
92
|
response.headers.should.not.include 'Content-Type'
|
87
93
|
response.body.should.empty
|
88
|
-
cache.should.
|
89
|
-
cache.should.
|
94
|
+
cache.trace.should.include :miss
|
95
|
+
cache.trace.should.include :store
|
90
96
|
end
|
91
97
|
|
92
98
|
it 'responds with 304 when If-None-Match matches ETag' do
|
93
99
|
respond_with do |req,res|
|
94
100
|
res.status = 200
|
95
|
-
res['
|
101
|
+
res['ETag'] = '12345'
|
96
102
|
res['Content-Type'] = 'text/plain'
|
97
103
|
res.body = ['Hello World']
|
98
104
|
end
|
@@ -103,28 +109,78 @@ describe 'Rack::Cache::Context' do
|
|
103
109
|
response.status.should.equal 304
|
104
110
|
response.headers.should.not.include 'Content-Length'
|
105
111
|
response.headers.should.not.include 'Content-Type'
|
106
|
-
response.headers.should.include '
|
112
|
+
response.headers.should.include 'ETag'
|
107
113
|
response.body.should.empty
|
108
|
-
cache.should.
|
109
|
-
cache.should.
|
114
|
+
cache.trace.should.include :miss
|
115
|
+
cache.trace.should.include :store
|
110
116
|
end
|
111
117
|
|
112
|
-
it '
|
118
|
+
it 'stores responses when no-cache request directive present' do
|
113
119
|
respond_with 200, 'Expires' => (Time.now + 5).httpdate
|
114
|
-
get '/', 'HTTP_CACHE_CONTROL' => 'no-cache'
|
115
120
|
|
121
|
+
get '/', 'HTTP_CACHE_CONTROL' => 'no-cache'
|
116
122
|
response.should.be.ok
|
117
|
-
cache.should.
|
123
|
+
cache.trace.should.include :store
|
118
124
|
response.headers.should.include 'Age'
|
119
125
|
end
|
120
126
|
|
127
|
+
it 'reloads responses when cache hits but no-cache request directive present' do
|
128
|
+
count = 0
|
129
|
+
respond_with 200, 'Cache-Control' => 'max-age=10000' do |req,res|
|
130
|
+
count+= 1
|
131
|
+
res.body = (count == 1) ? ['Hello World'] : ['Goodbye World']
|
132
|
+
end
|
133
|
+
|
134
|
+
get '/'
|
135
|
+
response.should.be.ok
|
136
|
+
response.body.should.equal 'Hello World'
|
137
|
+
cache.trace.should.include :store
|
138
|
+
|
139
|
+
get '/'
|
140
|
+
response.should.be.ok
|
141
|
+
response.body.should.equal 'Hello World'
|
142
|
+
cache.trace.should.include :fresh
|
143
|
+
|
144
|
+
get '/', 'HTTP_CACHE_CONTROL' => 'no-cache'
|
145
|
+
response.should.be.ok
|
146
|
+
response.body.should.equal 'Goodbye World'
|
147
|
+
cache.trace.should.include :reload
|
148
|
+
cache.trace.should.include :store
|
149
|
+
end
|
150
|
+
|
151
|
+
it 'revalidates fresh cache entry when max-age request directive is exceeded' do
|
152
|
+
count = 0
|
153
|
+
respond_with do |req,res|
|
154
|
+
count+= 1
|
155
|
+
res['Cache-Control'] = 'max-age=10000'
|
156
|
+
res['ETag'] = count.to_s
|
157
|
+
res.body = (count == 1) ? ['Hello World'] : ['Goodbye World']
|
158
|
+
end
|
159
|
+
|
160
|
+
get '/'
|
161
|
+
response.should.be.ok
|
162
|
+
response.body.should.equal 'Hello World'
|
163
|
+
cache.trace.should.include :store
|
164
|
+
|
165
|
+
get '/'
|
166
|
+
response.should.be.ok
|
167
|
+
response.body.should.equal 'Hello World'
|
168
|
+
cache.trace.should.include :fresh
|
169
|
+
|
170
|
+
get '/', 'HTTP_CACHE_CONTROL' => 'max-age=0'
|
171
|
+
response.should.be.ok
|
172
|
+
response.body.should.equal 'Goodbye World'
|
173
|
+
cache.trace.should.include :stale
|
174
|
+
cache.trace.should.include :invalid
|
175
|
+
cache.trace.should.include :store
|
176
|
+
end
|
177
|
+
|
121
178
|
it 'fetches response from backend when cache misses' do
|
122
179
|
respond_with 200, 'Expires' => (Time.now + 5).httpdate
|
123
180
|
get '/'
|
124
181
|
|
125
182
|
response.should.be.ok
|
126
|
-
cache.should.
|
127
|
-
cache.should.a.performed :fetch
|
183
|
+
cache.trace.should.include :miss
|
128
184
|
response.headers.should.include 'Age'
|
129
185
|
end
|
130
186
|
|
@@ -134,7 +190,7 @@ describe 'Rack::Cache::Context' do
|
|
134
190
|
respond_with response_code, 'Expires' => (Time.now + 5).httpdate
|
135
191
|
get '/'
|
136
192
|
|
137
|
-
cache.should.
|
193
|
+
cache.trace.should.not.include :store
|
138
194
|
response.status.should.equal response_code
|
139
195
|
response.headers.should.not.include 'Age'
|
140
196
|
end
|
@@ -148,7 +204,7 @@ describe 'Rack::Cache::Context' do
|
|
148
204
|
get '/'
|
149
205
|
|
150
206
|
response.should.be.ok
|
151
|
-
cache.should.
|
207
|
+
cache.trace.should.not.include :store
|
152
208
|
response.headers.should.not.include 'Age'
|
153
209
|
end
|
154
210
|
|
@@ -157,7 +213,7 @@ describe 'Rack::Cache::Context' do
|
|
157
213
|
get '/'
|
158
214
|
|
159
215
|
response.should.be.ok
|
160
|
-
cache.should.
|
216
|
+
cache.trace.should.not.include :store
|
161
217
|
end
|
162
218
|
|
163
219
|
it "caches responses with explicit no-cache directive" do
|
@@ -167,7 +223,7 @@ describe 'Rack::Cache::Context' do
|
|
167
223
|
get '/'
|
168
224
|
|
169
225
|
response.should.be.ok
|
170
|
-
cache.should.
|
226
|
+
cache.trace.should.include :store
|
171
227
|
response.headers.should.include 'Age'
|
172
228
|
end
|
173
229
|
|
@@ -180,8 +236,8 @@ describe 'Rack::Cache::Context' do
|
|
180
236
|
response.headers.should.include 'Date'
|
181
237
|
response['Age'].should.not.be.nil
|
182
238
|
response['X-Content-Digest'].should.not.be.nil
|
183
|
-
cache.should.
|
184
|
-
cache.should.
|
239
|
+
cache.trace.should.include :miss
|
240
|
+
cache.trace.should.include :store
|
185
241
|
cache.metastore.to_hash.keys.length.should.equal 1
|
186
242
|
end
|
187
243
|
|
@@ -194,8 +250,8 @@ describe 'Rack::Cache::Context' do
|
|
194
250
|
response.headers.should.include 'Date'
|
195
251
|
response['Age'].should.not.be.nil
|
196
252
|
response['X-Content-Digest'].should.not.be.nil
|
197
|
-
cache.should.
|
198
|
-
cache.should.
|
253
|
+
cache.trace.should.include :miss
|
254
|
+
cache.trace.should.include :store
|
199
255
|
cache.metastore.to_hash.keys.length.should.equal 1
|
200
256
|
end
|
201
257
|
|
@@ -208,8 +264,8 @@ describe 'Rack::Cache::Context' do
|
|
208
264
|
response.headers.should.include 'Date'
|
209
265
|
response['Age'].should.not.be.nil
|
210
266
|
response['X-Content-Digest'].should.not.be.nil
|
211
|
-
cache.should.
|
212
|
-
cache.should.
|
267
|
+
cache.trace.should.include :miss
|
268
|
+
cache.trace.should.include :store
|
213
269
|
cache.metastore.to_hash.keys.length.should.equal 1
|
214
270
|
end
|
215
271
|
|
@@ -219,18 +275,18 @@ describe 'Rack::Cache::Context' do
|
|
219
275
|
|
220
276
|
response.should.be.ok
|
221
277
|
response.body.should.equal 'Hello World'
|
222
|
-
cache.should.
|
223
|
-
cache.should.
|
278
|
+
cache.trace.should.include :miss
|
279
|
+
cache.trace.should.include :store
|
224
280
|
end
|
225
281
|
|
226
282
|
it 'caches responses with an ETag validator but no freshness information' do
|
227
|
-
respond_with 200, '
|
283
|
+
respond_with 200, 'ETag' => '"123456"'
|
228
284
|
get '/'
|
229
285
|
|
230
286
|
response.should.be.ok
|
231
287
|
response.body.should.equal 'Hello World'
|
232
|
-
cache.should.
|
233
|
-
cache.should.
|
288
|
+
cache.trace.should.include :miss
|
289
|
+
cache.trace.should.include :store
|
234
290
|
end
|
235
291
|
|
236
292
|
it 'hits cached response with Expires header' do
|
@@ -242,8 +298,8 @@ describe 'Rack::Cache::Context' do
|
|
242
298
|
app.should.be.called
|
243
299
|
response.should.be.ok
|
244
300
|
response.headers.should.include 'Date'
|
245
|
-
cache.should.
|
246
|
-
cache.should.
|
301
|
+
cache.trace.should.include :miss
|
302
|
+
cache.trace.should.include :store
|
247
303
|
response.body.should.equal 'Hello World'
|
248
304
|
|
249
305
|
get '/'
|
@@ -252,8 +308,8 @@ describe 'Rack::Cache::Context' do
|
|
252
308
|
response['Date'].should.equal responses.first['Date']
|
253
309
|
response['Age'].to_i.should.satisfy { |age| age > 0 }
|
254
310
|
response['X-Content-Digest'].should.not.be.nil
|
255
|
-
cache.should.
|
256
|
-
cache.should.
|
311
|
+
cache.trace.should.include :fresh
|
312
|
+
cache.trace.should.not.include :store
|
257
313
|
response.body.should.equal 'Hello World'
|
258
314
|
end
|
259
315
|
|
@@ -266,8 +322,8 @@ describe 'Rack::Cache::Context' do
|
|
266
322
|
app.should.be.called
|
267
323
|
response.should.be.ok
|
268
324
|
response.headers.should.include 'Date'
|
269
|
-
cache.should.
|
270
|
-
cache.should.
|
325
|
+
cache.trace.should.include :miss
|
326
|
+
cache.trace.should.include :store
|
271
327
|
response.body.should.equal 'Hello World'
|
272
328
|
|
273
329
|
get '/'
|
@@ -276,8 +332,8 @@ describe 'Rack::Cache::Context' do
|
|
276
332
|
response['Date'].should.equal responses.first['Date']
|
277
333
|
response['Age'].to_i.should.satisfy { |age| age > 0 }
|
278
334
|
response['X-Content-Digest'].should.not.be.nil
|
279
|
-
cache.should.
|
280
|
-
cache.should.
|
335
|
+
cache.trace.should.include :fresh
|
336
|
+
cache.trace.should.not.include :store
|
281
337
|
response.body.should.equal 'Hello World'
|
282
338
|
end
|
283
339
|
|
@@ -290,8 +346,8 @@ describe 'Rack::Cache::Context' do
|
|
290
346
|
app.should.be.called
|
291
347
|
response.should.be.ok
|
292
348
|
response.headers.should.include 'Date'
|
293
|
-
cache.should.
|
294
|
-
cache.should.
|
349
|
+
cache.trace.should.include :miss
|
350
|
+
cache.trace.should.include :store
|
295
351
|
response.body.should.equal 'Hello World'
|
296
352
|
|
297
353
|
get '/'
|
@@ -300,8 +356,8 @@ describe 'Rack::Cache::Context' do
|
|
300
356
|
response['Date'].should.equal responses.first['Date']
|
301
357
|
response['Age'].to_i.should.satisfy { |age| age > 0 }
|
302
358
|
response['X-Content-Digest'].should.not.be.nil
|
303
|
-
cache.should.
|
304
|
-
cache.should.
|
359
|
+
cache.trace.should.include :fresh
|
360
|
+
cache.trace.should.not.include :store
|
305
361
|
response.body.should.equal 'Hello World'
|
306
362
|
end
|
307
363
|
|
@@ -311,16 +367,16 @@ describe 'Rack::Cache::Context' do
|
|
311
367
|
get '/', 'rack-cache.default_ttl' => 10
|
312
368
|
app.should.be.called
|
313
369
|
response.should.be.ok
|
314
|
-
cache.should.
|
315
|
-
cache.should.
|
370
|
+
cache.trace.should.include :miss
|
371
|
+
cache.trace.should.include :store
|
316
372
|
response.body.should.equal 'Hello World'
|
317
373
|
response['Cache-Control'].should.include 's-maxage=10'
|
318
374
|
|
319
375
|
get '/', 'rack-cache.default_ttl' => 10
|
320
376
|
response.should.be.ok
|
321
377
|
app.should.not.be.called
|
322
|
-
cache.should.
|
323
|
-
cache.should.
|
378
|
+
cache.trace.should.include :fresh
|
379
|
+
cache.trace.should.not.include :store
|
324
380
|
response.body.should.equal 'Hello World'
|
325
381
|
end
|
326
382
|
|
@@ -331,8 +387,8 @@ describe 'Rack::Cache::Context' do
|
|
331
387
|
get '/', 'rack-cache.default_ttl' => 10
|
332
388
|
app.should.be.called
|
333
389
|
response.should.be.ok
|
334
|
-
cache.should.
|
335
|
-
cache.should.
|
390
|
+
cache.trace.should.include :miss
|
391
|
+
cache.trace.should.not.include :store
|
336
392
|
response['Cache-Control'].should.not.include 's-maxage'
|
337
393
|
response.body.should.equal 'Hello World'
|
338
394
|
end
|
@@ -347,8 +403,8 @@ describe 'Rack::Cache::Context' do
|
|
347
403
|
response.headers.should.include 'Date'
|
348
404
|
response.headers.should.include 'X-Content-Digest'
|
349
405
|
response.headers.should.include 'Age'
|
350
|
-
cache.should.
|
351
|
-
cache.should.
|
406
|
+
cache.trace.should.include :miss
|
407
|
+
cache.trace.should.include :store
|
352
408
|
response.body.should.equal 'Hello World'
|
353
409
|
|
354
410
|
# go in and play around with the cached metadata directly ...
|
@@ -361,10 +417,10 @@ describe 'Rack::Cache::Context' do
|
|
361
417
|
response.should.be.ok
|
362
418
|
response['Age'].to_i.should.equal 0
|
363
419
|
response.headers.should.include 'X-Content-Digest'
|
364
|
-
cache.should.
|
365
|
-
cache.should.
|
366
|
-
cache.should.
|
367
|
-
cache.should.
|
420
|
+
cache.trace.should.include :stale
|
421
|
+
cache.trace.should.not.include :fresh
|
422
|
+
cache.trace.should.not.include :miss
|
423
|
+
cache.trace.should.include :store
|
368
424
|
response.body.should.equal 'Hello World'
|
369
425
|
end
|
370
426
|
|
@@ -385,8 +441,9 @@ describe 'Rack::Cache::Context' do
|
|
385
441
|
response.headers.should.include 'Last-Modified'
|
386
442
|
response.headers.should.include 'X-Content-Digest'
|
387
443
|
response.body.should.equal 'Hello World'
|
388
|
-
cache.should.
|
389
|
-
cache.should.
|
444
|
+
cache.trace.should.include :miss
|
445
|
+
cache.trace.should.include :store
|
446
|
+
cache.trace.should.not.include :stale
|
390
447
|
|
391
448
|
# build subsequent request; should be found but miss due to freshness
|
392
449
|
get '/'
|
@@ -395,11 +452,11 @@ describe 'Rack::Cache::Context' do
|
|
395
452
|
response.headers.should.include 'Last-Modified'
|
396
453
|
response.headers.should.include 'X-Content-Digest'
|
397
454
|
response['Age'].to_i.should.equal 0
|
398
|
-
response['X-Origin-Status'].should.equal '304'
|
399
455
|
response.body.should.equal 'Hello World'
|
400
|
-
cache.should.
|
401
|
-
cache.should.
|
402
|
-
cache.should.
|
456
|
+
cache.trace.should.include :stale
|
457
|
+
cache.trace.should.include :valid
|
458
|
+
cache.trace.should.include :store
|
459
|
+
cache.trace.should.not.include :miss
|
403
460
|
end
|
404
461
|
|
405
462
|
it 'validates cached responses with ETag and no freshness information' do
|
@@ -416,24 +473,24 @@ describe 'Rack::Cache::Context' do
|
|
416
473
|
get '/'
|
417
474
|
app.should.be.called
|
418
475
|
response.should.be.ok
|
419
|
-
response.headers.should.include '
|
476
|
+
response.headers.should.include 'ETag'
|
420
477
|
response.headers.should.include 'X-Content-Digest'
|
421
478
|
response.body.should.equal 'Hello World'
|
422
|
-
cache.should.
|
423
|
-
cache.should.
|
479
|
+
cache.trace.should.include :miss
|
480
|
+
cache.trace.should.include :store
|
424
481
|
|
425
482
|
# build subsequent request; should be found but miss due to freshness
|
426
483
|
get '/'
|
427
484
|
app.should.be.called
|
428
485
|
response.should.be.ok
|
429
|
-
response.headers.should.include '
|
486
|
+
response.headers.should.include 'ETag'
|
430
487
|
response.headers.should.include 'X-Content-Digest'
|
431
488
|
response['Age'].to_i.should.equal 0
|
432
|
-
response['X-Origin-Status'].should.equal '304'
|
433
489
|
response.body.should.equal 'Hello World'
|
434
|
-
cache.should.
|
435
|
-
cache.should.
|
436
|
-
cache.should.
|
490
|
+
cache.trace.should.include :stale
|
491
|
+
cache.trace.should.include :valid
|
492
|
+
cache.trace.should.include :store
|
493
|
+
cache.trace.should.not.include :miss
|
437
494
|
end
|
438
495
|
|
439
496
|
it 'replaces cached responses when validation results in non-304 response' do
|
@@ -442,8 +499,8 @@ describe 'Rack::Cache::Context' do
|
|
442
499
|
respond_with do |req,res|
|
443
500
|
res['Last-Modified'] = timestamp
|
444
501
|
case (count+=1)
|
445
|
-
when 1 ; res.body = 'first response'
|
446
|
-
when 2 ; res.body = 'second response'
|
502
|
+
when 1 ; res.body = ['first response']
|
503
|
+
when 2 ; res.body = ['second response']
|
447
504
|
when 3
|
448
505
|
res.body = []
|
449
506
|
res.status = 304
|
@@ -475,11 +532,7 @@ describe 'Rack::Cache::Context' do
|
|
475
532
|
req.request_method.should.equal 'HEAD'
|
476
533
|
end
|
477
534
|
|
478
|
-
|
479
|
-
on(:receive) { pass! }
|
480
|
-
end
|
481
|
-
|
482
|
-
head '/'
|
535
|
+
head '/', 'HTTP_EXPECT' => 'something ...'
|
483
536
|
app.should.be.called
|
484
537
|
response.body.should.equal ''
|
485
538
|
end
|
@@ -503,6 +556,53 @@ describe 'Rack::Cache::Context' do
|
|
503
556
|
response['Content-Length'].should.equal 'Hello World'.length.to_s
|
504
557
|
end
|
505
558
|
|
559
|
+
it 'invalidates cached responses on POST' do
|
560
|
+
respond_with do |req,res|
|
561
|
+
if req.request_method == 'GET'
|
562
|
+
res.status = 200
|
563
|
+
res['Cache-Control'] = 'public, max-age=500'
|
564
|
+
res.body = ['Hello World']
|
565
|
+
elsif req.request_method == 'POST'
|
566
|
+
res.status = 303
|
567
|
+
res['Location'] = '/'
|
568
|
+
res.headers.delete('Cache-Control')
|
569
|
+
res.body = []
|
570
|
+
end
|
571
|
+
end
|
572
|
+
|
573
|
+
# build initial request to enter into the cache
|
574
|
+
get '/'
|
575
|
+
app.should.be.called
|
576
|
+
response.should.be.ok
|
577
|
+
response.body.should.equal 'Hello World'
|
578
|
+
cache.trace.should.include :miss
|
579
|
+
cache.trace.should.include :store
|
580
|
+
|
581
|
+
# make sure it is valid
|
582
|
+
get '/'
|
583
|
+
app.should.not.called
|
584
|
+
response.should.be.ok
|
585
|
+
response.body.should.equal 'Hello World'
|
586
|
+
cache.trace.should.include :fresh
|
587
|
+
|
588
|
+
# now POST to same URL
|
589
|
+
post '/'
|
590
|
+
app.should.be.called
|
591
|
+
response.should.be.redirect
|
592
|
+
response['Location'].should.equal '/'
|
593
|
+
cache.trace.should.include :invalidate
|
594
|
+
cache.trace.should.include :pass
|
595
|
+
response.body.should.equal ''
|
596
|
+
|
597
|
+
# now make sure it was actually invalidated
|
598
|
+
get '/'
|
599
|
+
app.should.be.called
|
600
|
+
response.should.be.ok
|
601
|
+
response.body.should.equal 'Hello World'
|
602
|
+
cache.trace.should.include :stale
|
603
|
+
cache.trace.should.include :invalid
|
604
|
+
cache.trace.should.include :store
|
605
|
+
end
|
506
606
|
|
507
607
|
describe 'with responses that include a Vary header' do
|
508
608
|
before(:each) do
|
@@ -511,7 +611,7 @@ describe 'Rack::Cache::Context' do
|
|
511
611
|
res['Vary'] = 'Accept User-Agent Foo'
|
512
612
|
res['Cache-Control'] = 'max-age=10'
|
513
613
|
res['X-Response-Count'] = (count+=1).to_s
|
514
|
-
res.body = req.env['HTTP_USER_AGENT']
|
614
|
+
res.body = [req.env['HTTP_USER_AGENT']]
|
515
615
|
end
|
516
616
|
end
|
517
617
|
|
@@ -521,16 +621,16 @@ describe 'Rack::Cache::Context' do
|
|
521
621
|
'HTTP_USER_AGENT' => 'Bob/1.0'
|
522
622
|
response.should.be.ok
|
523
623
|
response.body.should.equal 'Bob/1.0'
|
524
|
-
cache.should.
|
525
|
-
cache.should.
|
624
|
+
cache.trace.should.include :miss
|
625
|
+
cache.trace.should.include :store
|
526
626
|
|
527
627
|
get '/',
|
528
628
|
'HTTP_ACCEPT' => 'text/html',
|
529
629
|
'HTTP_USER_AGENT' => 'Bob/1.0'
|
530
630
|
response.should.be.ok
|
531
631
|
response.body.should.equal 'Bob/1.0'
|
532
|
-
cache.should.
|
533
|
-
cache.should.
|
632
|
+
cache.trace.should.include :fresh
|
633
|
+
cache.trace.should.not.include :store
|
534
634
|
response.headers.should.include 'X-Content-Digest'
|
535
635
|
end
|
536
636
|
|
@@ -545,101 +645,30 @@ describe 'Rack::Cache::Context' do
|
|
545
645
|
get '/',
|
546
646
|
'HTTP_ACCEPT' => 'text/html',
|
547
647
|
'HTTP_USER_AGENT' => 'Bob/2.0'
|
548
|
-
cache.should.
|
549
|
-
cache.should.
|
648
|
+
cache.trace.should.include :miss
|
649
|
+
cache.trace.should.include :store
|
550
650
|
response.body.should.equal 'Bob/2.0'
|
551
651
|
response['X-Response-Count'].should.equal '2'
|
552
652
|
|
553
653
|
get '/',
|
554
654
|
'HTTP_ACCEPT' => 'text/html',
|
555
655
|
'HTTP_USER_AGENT' => 'Bob/1.0'
|
556
|
-
cache.should.
|
656
|
+
cache.trace.should.include :fresh
|
557
657
|
response.body.should.equal 'Bob/1.0'
|
558
658
|
response['X-Response-Count'].should.equal '1'
|
559
659
|
|
560
660
|
get '/',
|
561
661
|
'HTTP_ACCEPT' => 'text/html',
|
562
662
|
'HTTP_USER_AGENT' => 'Bob/2.0'
|
563
|
-
cache.should.
|
663
|
+
cache.trace.should.include :fresh
|
564
664
|
response.body.should.equal 'Bob/2.0'
|
565
665
|
response['X-Response-Count'].should.equal '2'
|
566
666
|
|
567
667
|
get '/',
|
568
668
|
'HTTP_USER_AGENT' => 'Bob/2.0'
|
569
|
-
cache.should.
|
669
|
+
cache.trace.should.include :miss
|
570
670
|
response.body.should.equal 'Bob/2.0'
|
571
671
|
response['X-Response-Count'].should.equal '3'
|
572
672
|
end
|
573
673
|
end
|
574
|
-
|
575
|
-
describe 'when transitioning to the error state' do
|
576
|
-
|
577
|
-
setup { respond_with(200) }
|
578
|
-
|
579
|
-
it 'creates a blank slate response object with 500 status with no args' do
|
580
|
-
cache_config do
|
581
|
-
on(:receive) { error! }
|
582
|
-
end
|
583
|
-
get '/'
|
584
|
-
response.status.should.equal 500
|
585
|
-
response.body.should.be.empty
|
586
|
-
cache.should.a.performed :error
|
587
|
-
end
|
588
|
-
|
589
|
-
it 'sets the status code with one arg' do
|
590
|
-
cache_config do
|
591
|
-
on(:receive) { error! 505 }
|
592
|
-
end
|
593
|
-
get '/'
|
594
|
-
response.status.should.equal 505
|
595
|
-
end
|
596
|
-
|
597
|
-
it 'sets the status and headers with args: status, Hash' do
|
598
|
-
cache_config do
|
599
|
-
on(:receive) { error! 504, 'Content-Type' => 'application/x-foo' }
|
600
|
-
end
|
601
|
-
get '/'
|
602
|
-
response.status.should.equal 504
|
603
|
-
response['Content-Type'].should.equal 'application/x-foo'
|
604
|
-
response.body.should.be.empty
|
605
|
-
end
|
606
|
-
|
607
|
-
it 'sets the status and body with args: status, String' do
|
608
|
-
cache_config do
|
609
|
-
on(:receive) { error! 503, 'foo bar baz' }
|
610
|
-
end
|
611
|
-
get '/'
|
612
|
-
response.status.should.equal 503
|
613
|
-
response.body.should.equal 'foo bar baz'
|
614
|
-
end
|
615
|
-
|
616
|
-
it 'sets the status and body with args: status, Array' do
|
617
|
-
cache_config do
|
618
|
-
on(:receive) { error! 503, ['foo bar baz'] }
|
619
|
-
end
|
620
|
-
get '/'
|
621
|
-
response.status.should.equal 503
|
622
|
-
response.body.should.equal 'foo bar baz'
|
623
|
-
end
|
624
|
-
|
625
|
-
it 'fires the error event before finishing' do
|
626
|
-
fired = false
|
627
|
-
cache_config do
|
628
|
-
on(:receive) { error! }
|
629
|
-
on(:error) {
|
630
|
-
fired = true
|
631
|
-
response.status.should.equal 500
|
632
|
-
response['Content-Type'] = 'application/x-foo'
|
633
|
-
response.body = ['overridden response body']
|
634
|
-
}
|
635
|
-
end
|
636
|
-
get '/'
|
637
|
-
fired.should.be true
|
638
|
-
response.status.should.equal 500
|
639
|
-
response.body.should.equal 'overridden response body'
|
640
|
-
response['Content-Type'].should.equal 'application/x-foo'
|
641
|
-
end
|
642
|
-
|
643
|
-
end
|
644
|
-
|
645
674
|
end
|