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/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
|