rack-cache 1.2 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/{COPYING → MIT-LICENSE} +0 -0
- data/README.md +95 -0
- data/lib/rack/cache/cachecontrol.rb +14 -0
- data/lib/rack/cache/context.rb +16 -3
- data/lib/rack/cache/metastore.rb +1 -1
- data/lib/rack/cache/options.rb +4 -6
- data/lib/rack/cache/response.rb +13 -6
- metadata +76 -74
- data/Gemfile +0 -2
- data/README +0 -126
- data/Rakefile +0 -139
- data/TODO +0 -27
- data/doc/configuration.markdown +0 -127
- data/doc/faq.markdown +0 -148
- data/doc/index.markdown +0 -121
- data/doc/layout.html.erb +0 -34
- data/doc/license.markdown +0 -24
- data/doc/rack-cache.css +0 -362
- data/doc/server.ru +0 -34
- data/doc/storage.markdown +0 -164
- data/example/sinatra/app.rb +0 -21
- data/example/sinatra/views/index.erb +0 -44
- data/rack-cache.gemspec +0 -75
- data/test/cache_test.rb +0 -38
- data/test/cachecontrol_test.rb +0 -145
- data/test/context_test.rb +0 -916
- data/test/entitystore_test.rb +0 -268
- data/test/key_test.rb +0 -50
- data/test/metastore_test.rb +0 -338
- data/test/options_test.rb +0 -77
- data/test/pony.jpg +0 -0
- data/test/request_test.rb +0 -19
- data/test/response_test.rb +0 -184
- data/test/spec_setup.rb +0 -232
- data/test/storage_test.rb +0 -94
data/test/context_test.rb
DELETED
@@ -1,916 +0,0 @@
|
|
1
|
-
require "#{File.dirname(__FILE__)}/spec_setup"
|
2
|
-
require 'rack/cache/context'
|
3
|
-
|
4
|
-
describe 'Rack::Cache::Context' do
|
5
|
-
before { setup_cache_context }
|
6
|
-
after { teardown_cache_context }
|
7
|
-
|
8
|
-
it 'passes on non-GET/HEAD requests' do
|
9
|
-
respond_with 200
|
10
|
-
post '/'
|
11
|
-
|
12
|
-
app.should.be.called
|
13
|
-
response.should.be.ok
|
14
|
-
cache.trace.should.include :pass
|
15
|
-
response.headers.should.not.include 'Age'
|
16
|
-
end
|
17
|
-
|
18
|
-
it 'passes on rack-cache.force-pass' do
|
19
|
-
respond_with 200
|
20
|
-
get '/', {"rack-cache.force-pass" => true}
|
21
|
-
|
22
|
-
app.should.be.called
|
23
|
-
response.should.be.ok
|
24
|
-
cache.trace.should == [:pass]
|
25
|
-
response.headers.should.not.include 'Age'
|
26
|
-
end
|
27
|
-
|
28
|
-
%w[post put delete].each do |request_method|
|
29
|
-
it "invalidates on #{request_method} requests" do
|
30
|
-
respond_with 200
|
31
|
-
request request_method, '/'
|
32
|
-
|
33
|
-
app.should.be.called
|
34
|
-
response.should.be.ok
|
35
|
-
cache.trace.should.include :invalidate
|
36
|
-
cache.trace.should.include :pass
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
it 'does not cache with Authorization request header and non public response' do
|
41
|
-
respond_with 200, 'ETag' => '"FOO"'
|
42
|
-
get '/', 'HTTP_AUTHORIZATION' => 'basic foobarbaz'
|
43
|
-
|
44
|
-
app.should.be.called
|
45
|
-
response.should.be.ok
|
46
|
-
response.headers['Cache-Control'].should.equal 'private'
|
47
|
-
cache.trace.should.include :miss
|
48
|
-
cache.trace.should.not.include :store
|
49
|
-
response.headers.should.not.include 'Age'
|
50
|
-
end
|
51
|
-
|
52
|
-
it 'does cache with Authorization request header and public response' do
|
53
|
-
respond_with 200, 'Cache-Control' => 'public', 'ETag' => '"FOO"'
|
54
|
-
get '/', 'HTTP_AUTHORIZATION' => 'basic foobarbaz'
|
55
|
-
|
56
|
-
app.should.be.called
|
57
|
-
response.should.be.ok
|
58
|
-
cache.trace.should.include :miss
|
59
|
-
cache.trace.should.include :store
|
60
|
-
cache.trace.should.not.include :ignore
|
61
|
-
response.headers.should.include 'Age'
|
62
|
-
response.headers['Cache-Control'].should.equal 'public'
|
63
|
-
end
|
64
|
-
|
65
|
-
it 'does not cache with Cookie header and non public response' do
|
66
|
-
respond_with 200, 'ETag' => '"FOO"'
|
67
|
-
get '/', 'HTTP_COOKIE' => 'foo=bar'
|
68
|
-
|
69
|
-
app.should.be.called
|
70
|
-
response.should.be.ok
|
71
|
-
response.headers['Cache-Control'].should.equal 'private'
|
72
|
-
cache.trace.should.include :miss
|
73
|
-
cache.trace.should.not.include :store
|
74
|
-
response.headers.should.not.include 'Age'
|
75
|
-
end
|
76
|
-
|
77
|
-
it 'does not cache requests with a Cookie header' do
|
78
|
-
respond_with 200
|
79
|
-
get '/', 'HTTP_COOKIE' => 'foo=bar'
|
80
|
-
|
81
|
-
response.should.be.ok
|
82
|
-
app.should.be.called
|
83
|
-
cache.trace.should.include :miss
|
84
|
-
cache.trace.should.not.include :store
|
85
|
-
response.headers.should.not.include 'Age'
|
86
|
-
response.headers['Cache-Control'].should.equal 'private'
|
87
|
-
end
|
88
|
-
|
89
|
-
it 'does remove Set-Cookie response header from a cacheable response' do
|
90
|
-
respond_with 200, 'Cache-Control' => 'public', 'ETag' => '"FOO"', 'Set-Cookie' => 'TestCookie=OK'
|
91
|
-
get '/'
|
92
|
-
|
93
|
-
app.should.be.called
|
94
|
-
response.should.be.ok
|
95
|
-
cache.trace.should.include :store
|
96
|
-
cache.trace.should.include :ignore
|
97
|
-
response.headers['Set-Cookie'].should.be.nil
|
98
|
-
end
|
99
|
-
|
100
|
-
it 'does remove all configured ignore_headers from a cacheable response' do
|
101
|
-
respond_with 200, 'Cache-Control' => 'public', 'ETag' => '"FOO"', 'SET-COOKIE' => 'TestCookie=OK', 'X-Strip-Me' => 'Secret'
|
102
|
-
get '/', 'rack-cache.ignore_headers' => ['set-cookie', 'x-strip-me']
|
103
|
-
|
104
|
-
app.should.be.called
|
105
|
-
response.should.be.ok
|
106
|
-
cache.trace.should.include :store
|
107
|
-
cache.trace.should.include :ignore
|
108
|
-
response.headers['Set-Cookie'].should.be.nil
|
109
|
-
response.headers['x-strip-me'].should.be.nil
|
110
|
-
end
|
111
|
-
|
112
|
-
it 'does not remove Set-Cookie response header from a private response' do
|
113
|
-
respond_with 200, 'Cache-Control' => 'private', 'Set-Cookie' => 'TestCookie=OK'
|
114
|
-
get '/'
|
115
|
-
|
116
|
-
app.should.be.called
|
117
|
-
response.should.be.ok
|
118
|
-
cache.trace.should.not.include :store
|
119
|
-
cache.trace.should.not.include :ignore
|
120
|
-
response.headers['Set-Cookie'].should.equal 'TestCookie=OK'
|
121
|
-
end
|
122
|
-
|
123
|
-
it 'responds with 304 when If-Modified-Since matches Last-Modified' do
|
124
|
-
timestamp = Time.now.httpdate
|
125
|
-
respond_with do |req,res|
|
126
|
-
res.status = 200
|
127
|
-
res['Last-Modified'] = timestamp
|
128
|
-
res['Content-Type'] = 'text/plain'
|
129
|
-
res.body = ['Hello World']
|
130
|
-
end
|
131
|
-
|
132
|
-
get '/',
|
133
|
-
'HTTP_IF_MODIFIED_SINCE' => timestamp
|
134
|
-
app.should.be.called
|
135
|
-
response.status.should.equal 304
|
136
|
-
response.original_headers.should.not.include 'Content-Length'
|
137
|
-
response.original_headers.should.not.include 'Content-Type'
|
138
|
-
response.body.should.empty
|
139
|
-
cache.trace.should.include :miss
|
140
|
-
cache.trace.should.include :store
|
141
|
-
end
|
142
|
-
|
143
|
-
it 'responds with 304 when If-None-Match matches ETag' do
|
144
|
-
respond_with do |req,res|
|
145
|
-
res.status = 200
|
146
|
-
res['ETag'] = '12345'
|
147
|
-
res['Content-Type'] = 'text/plain'
|
148
|
-
res.body = ['Hello World']
|
149
|
-
end
|
150
|
-
|
151
|
-
get '/',
|
152
|
-
'HTTP_IF_NONE_MATCH' => '12345'
|
153
|
-
app.should.be.called
|
154
|
-
response.status.should.equal 304
|
155
|
-
response.original_headers.should.not.include 'Content-Length'
|
156
|
-
response.original_headers.should.not.include 'Content-Type'
|
157
|
-
response.headers.should.include 'ETag'
|
158
|
-
response.body.should.empty
|
159
|
-
cache.trace.should.include :miss
|
160
|
-
cache.trace.should.include :store
|
161
|
-
end
|
162
|
-
|
163
|
-
it 'responds with 304 only if If-None-Match and If-Modified-Since both match' do
|
164
|
-
timestamp = Time.now
|
165
|
-
|
166
|
-
respond_with do |req,res|
|
167
|
-
res.status = 200
|
168
|
-
res['ETag'] = '12345'
|
169
|
-
res['Last-Modified'] = timestamp.httpdate
|
170
|
-
res['Content-Type'] = 'text/plain'
|
171
|
-
res.body = ['Hello World']
|
172
|
-
end
|
173
|
-
|
174
|
-
# Only etag matches
|
175
|
-
get '/',
|
176
|
-
'HTTP_IF_NONE_MATCH' => '12345', 'HTTP_IF_MODIFIED_SINCE' => (timestamp - 1).httpdate
|
177
|
-
app.should.be.called
|
178
|
-
response.status.should.equal 200
|
179
|
-
|
180
|
-
# Only last-modified matches
|
181
|
-
get '/',
|
182
|
-
'HTTP_IF_NONE_MATCH' => '1234', 'HTTP_IF_MODIFIED_SINCE' => timestamp.httpdate
|
183
|
-
app.should.be.called
|
184
|
-
response.status.should.equal 200
|
185
|
-
|
186
|
-
# Both matches
|
187
|
-
get '/',
|
188
|
-
'HTTP_IF_NONE_MATCH' => '12345', 'HTTP_IF_MODIFIED_SINCE' => timestamp.httpdate
|
189
|
-
app.should.be.called
|
190
|
-
response.status.should.equal 304
|
191
|
-
end
|
192
|
-
|
193
|
-
it 'validates private responses cached on the client' do
|
194
|
-
respond_with do |req,res|
|
195
|
-
etags = req.env['HTTP_IF_NONE_MATCH'].to_s.split(/\s*,\s*/)
|
196
|
-
if req.env['HTTP_COOKIE'] == 'authenticated'
|
197
|
-
res['Cache-Control'] = 'private, no-store'
|
198
|
-
res['ETag'] = '"private tag"'
|
199
|
-
if etags.include?('"private tag"')
|
200
|
-
res.status = 304
|
201
|
-
else
|
202
|
-
res.status = 200
|
203
|
-
res['Content-Type'] = 'text/plain'
|
204
|
-
res.body = ['private data']
|
205
|
-
end
|
206
|
-
else
|
207
|
-
res['ETag'] = '"public tag"'
|
208
|
-
if etags.include?('"public tag"')
|
209
|
-
res.status = 304
|
210
|
-
else
|
211
|
-
res.status = 200
|
212
|
-
res['Content-Type'] = 'text/plain'
|
213
|
-
res.body = ['public data']
|
214
|
-
end
|
215
|
-
end
|
216
|
-
end
|
217
|
-
|
218
|
-
get '/'
|
219
|
-
app.should.be.called
|
220
|
-
response.status.should.equal 200
|
221
|
-
response.headers['ETag'].should == '"public tag"'
|
222
|
-
response.body.should == 'public data'
|
223
|
-
cache.trace.should.include :miss
|
224
|
-
cache.trace.should.include :store
|
225
|
-
|
226
|
-
get '/', 'HTTP_COOKIE' => 'authenticated'
|
227
|
-
app.should.be.called
|
228
|
-
response.status.should.equal 200
|
229
|
-
response.headers['ETag'].should == '"private tag"'
|
230
|
-
response.body.should == 'private data'
|
231
|
-
cache.trace.should.include :stale
|
232
|
-
cache.trace.should.include :invalid
|
233
|
-
cache.trace.should.not.include :store
|
234
|
-
|
235
|
-
get '/',
|
236
|
-
'HTTP_IF_NONE_MATCH' => '"public tag"'
|
237
|
-
app.should.be.called
|
238
|
-
response.status.should.equal 304
|
239
|
-
response.headers['ETag'].should == '"public tag"'
|
240
|
-
cache.trace.should.include :stale
|
241
|
-
cache.trace.should.include :valid
|
242
|
-
cache.trace.should.include :store
|
243
|
-
|
244
|
-
get '/',
|
245
|
-
'HTTP_IF_NONE_MATCH' => '"private tag"',
|
246
|
-
'HTTP_COOKIE' => 'authenticated'
|
247
|
-
app.should.be.called
|
248
|
-
response.status.should.equal 304
|
249
|
-
response.headers['ETag'].should == '"private tag"'
|
250
|
-
cache.trace.should.include :valid
|
251
|
-
cache.trace.should.not.include :store
|
252
|
-
end
|
253
|
-
|
254
|
-
it 'stores responses when no-cache request directive present' do
|
255
|
-
respond_with 200, 'Expires' => (Time.now + 5).httpdate
|
256
|
-
|
257
|
-
get '/', 'HTTP_CACHE_CONTROL' => 'no-cache'
|
258
|
-
response.should.be.ok
|
259
|
-
cache.trace.should.include :store
|
260
|
-
response.headers.should.include 'Age'
|
261
|
-
end
|
262
|
-
|
263
|
-
it 'reloads responses when cache hits but no-cache request directive present ' +
|
264
|
-
'when allow_reload is set true' do
|
265
|
-
count = 0
|
266
|
-
respond_with 200, 'Cache-Control' => 'max-age=10000' do |req,res|
|
267
|
-
count+= 1
|
268
|
-
res.body = (count == 1) ? ['Hello World'] : ['Goodbye World']
|
269
|
-
end
|
270
|
-
|
271
|
-
get '/'
|
272
|
-
response.should.be.ok
|
273
|
-
response.body.should.equal 'Hello World'
|
274
|
-
cache.trace.should.include :store
|
275
|
-
|
276
|
-
get '/'
|
277
|
-
response.should.be.ok
|
278
|
-
response.body.should.equal 'Hello World'
|
279
|
-
cache.trace.should.include :fresh
|
280
|
-
|
281
|
-
get '/',
|
282
|
-
'rack-cache.allow_reload' => true,
|
283
|
-
'HTTP_CACHE_CONTROL' => 'no-cache'
|
284
|
-
response.should.be.ok
|
285
|
-
response.body.should.equal 'Goodbye World'
|
286
|
-
cache.trace.should.include :reload
|
287
|
-
cache.trace.should.include :store
|
288
|
-
end
|
289
|
-
|
290
|
-
it 'does not reload responses when allow_reload is set false (default)' do
|
291
|
-
count = 0
|
292
|
-
respond_with 200, 'Cache-Control' => 'max-age=10000' do |req,res|
|
293
|
-
count+= 1
|
294
|
-
res.body = (count == 1) ? ['Hello World'] : ['Goodbye World']
|
295
|
-
end
|
296
|
-
|
297
|
-
get '/'
|
298
|
-
response.should.be.ok
|
299
|
-
response.body.should.equal 'Hello World'
|
300
|
-
cache.trace.should.include :store
|
301
|
-
|
302
|
-
get '/'
|
303
|
-
response.should.be.ok
|
304
|
-
response.body.should.equal 'Hello World'
|
305
|
-
cache.trace.should.include :fresh
|
306
|
-
|
307
|
-
get '/',
|
308
|
-
'rack-cache.allow_reload' => false,
|
309
|
-
'HTTP_CACHE_CONTROL' => 'no-cache'
|
310
|
-
response.should.be.ok
|
311
|
-
response.body.should.equal 'Hello World'
|
312
|
-
cache.trace.should.not.include :reload
|
313
|
-
|
314
|
-
# test again without explicitly setting the allow_reload option to false
|
315
|
-
get '/',
|
316
|
-
'HTTP_CACHE_CONTROL' => 'no-cache'
|
317
|
-
response.should.be.ok
|
318
|
-
response.body.should.equal 'Hello World'
|
319
|
-
cache.trace.should.not.include :reload
|
320
|
-
end
|
321
|
-
|
322
|
-
it 'revalidates fresh cache entry when max-age request directive is exceeded ' +
|
323
|
-
'when allow_revalidate option is set true' do
|
324
|
-
count = 0
|
325
|
-
respond_with do |req,res|
|
326
|
-
count+= 1
|
327
|
-
res['Cache-Control'] = 'max-age=10000'
|
328
|
-
res['ETag'] = count.to_s
|
329
|
-
res.body = (count == 1) ? ['Hello World'] : ['Goodbye World']
|
330
|
-
end
|
331
|
-
|
332
|
-
get '/'
|
333
|
-
response.should.be.ok
|
334
|
-
response.body.should.equal 'Hello World'
|
335
|
-
cache.trace.should.include :store
|
336
|
-
|
337
|
-
get '/'
|
338
|
-
response.should.be.ok
|
339
|
-
response.body.should.equal 'Hello World'
|
340
|
-
cache.trace.should.include :fresh
|
341
|
-
|
342
|
-
get '/',
|
343
|
-
'rack-cache.allow_revalidate' => true,
|
344
|
-
'HTTP_CACHE_CONTROL' => 'max-age=0'
|
345
|
-
response.should.be.ok
|
346
|
-
response.body.should.equal 'Goodbye World'
|
347
|
-
cache.trace.should.include :stale
|
348
|
-
cache.trace.should.include :invalid
|
349
|
-
cache.trace.should.include :store
|
350
|
-
end
|
351
|
-
|
352
|
-
it 'does not revalidate fresh cache entry when enable_revalidate option is set false (default)' do
|
353
|
-
count = 0
|
354
|
-
respond_with do |req,res|
|
355
|
-
count+= 1
|
356
|
-
res['Cache-Control'] = 'max-age=10000'
|
357
|
-
res['ETag'] = count.to_s
|
358
|
-
res.body = (count == 1) ? ['Hello World'] : ['Goodbye World']
|
359
|
-
end
|
360
|
-
|
361
|
-
get '/'
|
362
|
-
response.should.be.ok
|
363
|
-
response.body.should.equal 'Hello World'
|
364
|
-
cache.trace.should.include :store
|
365
|
-
|
366
|
-
get '/'
|
367
|
-
response.should.be.ok
|
368
|
-
response.body.should.equal 'Hello World'
|
369
|
-
cache.trace.should.include :fresh
|
370
|
-
|
371
|
-
get '/',
|
372
|
-
'rack-cache.allow_revalidate' => false,
|
373
|
-
'HTTP_CACHE_CONTROL' => 'max-age=0'
|
374
|
-
response.should.be.ok
|
375
|
-
response.body.should.equal 'Hello World'
|
376
|
-
cache.trace.should.not.include :stale
|
377
|
-
cache.trace.should.not.include :invalid
|
378
|
-
cache.trace.should.include :fresh
|
379
|
-
|
380
|
-
# test again without explicitly setting the allow_revalidate option to false
|
381
|
-
get '/',
|
382
|
-
'HTTP_CACHE_CONTROL' => 'max-age=0'
|
383
|
-
response.should.be.ok
|
384
|
-
response.body.should.equal 'Hello World'
|
385
|
-
cache.trace.should.not.include :stale
|
386
|
-
cache.trace.should.not.include :invalid
|
387
|
-
cache.trace.should.include :fresh
|
388
|
-
end
|
389
|
-
it 'fetches response from backend when cache misses' do
|
390
|
-
respond_with 200, 'Expires' => (Time.now + 5).httpdate
|
391
|
-
get '/'
|
392
|
-
|
393
|
-
response.should.be.ok
|
394
|
-
cache.trace.should.include :miss
|
395
|
-
response.headers.should.include 'Age'
|
396
|
-
end
|
397
|
-
|
398
|
-
[(201..202),(204..206),(303..305),(400..403),(405..409),(411..417),(500..505)].each do |range|
|
399
|
-
range.each do |response_code|
|
400
|
-
it "does not cache #{response_code} responses" do
|
401
|
-
respond_with response_code, 'Expires' => (Time.now + 5).httpdate
|
402
|
-
get '/'
|
403
|
-
|
404
|
-
cache.trace.should.not.include :store
|
405
|
-
response.status.should.equal response_code
|
406
|
-
response.headers.should.not.include 'Age'
|
407
|
-
end
|
408
|
-
end
|
409
|
-
end
|
410
|
-
|
411
|
-
it "does not cache responses with explicit no-store directive" do
|
412
|
-
respond_with 200,
|
413
|
-
'Expires' => (Time.now + 5).httpdate,
|
414
|
-
'Cache-Control' => 'no-store'
|
415
|
-
get '/'
|
416
|
-
|
417
|
-
response.should.be.ok
|
418
|
-
cache.trace.should.not.include :store
|
419
|
-
response.headers.should.not.include 'Age'
|
420
|
-
end
|
421
|
-
|
422
|
-
it 'does not cache responses without freshness information or a validator' do
|
423
|
-
respond_with 200
|
424
|
-
get '/'
|
425
|
-
|
426
|
-
response.should.be.ok
|
427
|
-
cache.trace.should.not.include :store
|
428
|
-
end
|
429
|
-
|
430
|
-
it "caches responses with explicit no-cache directive" do
|
431
|
-
respond_with 200,
|
432
|
-
'Expires' => (Time.now + 5).httpdate,
|
433
|
-
'Cache-Control' => 'no-cache'
|
434
|
-
get '/'
|
435
|
-
|
436
|
-
response.should.be.ok
|
437
|
-
cache.trace.should.include :store
|
438
|
-
response.headers.should.include 'Age'
|
439
|
-
end
|
440
|
-
|
441
|
-
it 'caches responses with an Expiration header' do
|
442
|
-
respond_with 200, 'Expires' => (Time.now + 5).httpdate
|
443
|
-
get '/'
|
444
|
-
|
445
|
-
response.should.be.ok
|
446
|
-
response.body.should.equal 'Hello World'
|
447
|
-
response.headers.should.include 'Date'
|
448
|
-
response['Age'].should.not.be.nil
|
449
|
-
response['X-Content-Digest'].should.not.be.nil
|
450
|
-
cache.trace.should.include :miss
|
451
|
-
cache.trace.should.include :store
|
452
|
-
cache.metastore.to_hash.keys.length.should.equal 1
|
453
|
-
end
|
454
|
-
|
455
|
-
it 'caches responses with a max-age directive' do
|
456
|
-
respond_with 200, 'Cache-Control' => 'max-age=5'
|
457
|
-
get '/'
|
458
|
-
|
459
|
-
response.should.be.ok
|
460
|
-
response.body.should.equal 'Hello World'
|
461
|
-
response.headers.should.include 'Date'
|
462
|
-
response['Age'].should.not.be.nil
|
463
|
-
response['X-Content-Digest'].should.not.be.nil
|
464
|
-
cache.trace.should.include :miss
|
465
|
-
cache.trace.should.include :store
|
466
|
-
cache.metastore.to_hash.keys.length.should.equal 1
|
467
|
-
end
|
468
|
-
|
469
|
-
it 'caches responses with a s-maxage directive' do
|
470
|
-
respond_with 200, 'Cache-Control' => 's-maxage=5'
|
471
|
-
get '/'
|
472
|
-
|
473
|
-
response.should.be.ok
|
474
|
-
response.body.should.equal 'Hello World'
|
475
|
-
response.headers.should.include 'Date'
|
476
|
-
response['Age'].should.not.be.nil
|
477
|
-
response['X-Content-Digest'].should.not.be.nil
|
478
|
-
cache.trace.should.include :miss
|
479
|
-
cache.trace.should.include :store
|
480
|
-
cache.metastore.to_hash.keys.length.should.equal 1
|
481
|
-
end
|
482
|
-
|
483
|
-
it 'caches responses with a Last-Modified validator but no freshness information' do
|
484
|
-
respond_with 200, 'Last-Modified' => Time.now.httpdate
|
485
|
-
get '/'
|
486
|
-
|
487
|
-
response.should.be.ok
|
488
|
-
response.body.should.equal 'Hello World'
|
489
|
-
cache.trace.should.include :miss
|
490
|
-
cache.trace.should.include :store
|
491
|
-
end
|
492
|
-
|
493
|
-
it 'caches responses with an ETag validator but no freshness information' do
|
494
|
-
respond_with 200, 'ETag' => '"123456"'
|
495
|
-
get '/'
|
496
|
-
|
497
|
-
response.should.be.ok
|
498
|
-
response.body.should.equal 'Hello World'
|
499
|
-
cache.trace.should.include :miss
|
500
|
-
cache.trace.should.include :store
|
501
|
-
end
|
502
|
-
|
503
|
-
it 'hits cached response with Expires header' do
|
504
|
-
respond_with 200,
|
505
|
-
'Date' => (Time.now - 5).httpdate,
|
506
|
-
'Expires' => (Time.now + 5).httpdate
|
507
|
-
|
508
|
-
get '/'
|
509
|
-
app.should.be.called
|
510
|
-
response.should.be.ok
|
511
|
-
response.headers.should.include 'Date'
|
512
|
-
cache.trace.should.include :miss
|
513
|
-
cache.trace.should.include :store
|
514
|
-
response.body.should.equal 'Hello World'
|
515
|
-
|
516
|
-
get '/'
|
517
|
-
response.should.be.ok
|
518
|
-
app.should.not.be.called
|
519
|
-
response['Date'].should.equal responses.first['Date']
|
520
|
-
response['Age'].to_i.should.satisfy { |age| age > 0 }
|
521
|
-
response['X-Content-Digest'].should.not.be.nil
|
522
|
-
cache.trace.should.include :fresh
|
523
|
-
cache.trace.should.not.include :store
|
524
|
-
response.body.should.equal 'Hello World'
|
525
|
-
end
|
526
|
-
|
527
|
-
it 'hits cached response with max-age directive' do
|
528
|
-
respond_with 200,
|
529
|
-
'Date' => (Time.now - 5).httpdate,
|
530
|
-
'Cache-Control' => 'max-age=10'
|
531
|
-
|
532
|
-
get '/'
|
533
|
-
app.should.be.called
|
534
|
-
response.should.be.ok
|
535
|
-
response.headers.should.include 'Date'
|
536
|
-
cache.trace.should.include :miss
|
537
|
-
cache.trace.should.include :store
|
538
|
-
response.body.should.equal 'Hello World'
|
539
|
-
|
540
|
-
get '/'
|
541
|
-
response.should.be.ok
|
542
|
-
app.should.not.be.called
|
543
|
-
response['Date'].should.equal responses.first['Date']
|
544
|
-
response['Age'].to_i.should.satisfy { |age| age > 0 }
|
545
|
-
response['X-Content-Digest'].should.not.be.nil
|
546
|
-
cache.trace.should.include :fresh
|
547
|
-
cache.trace.should.not.include :store
|
548
|
-
response.body.should.equal 'Hello World'
|
549
|
-
end
|
550
|
-
|
551
|
-
it 'hits cached response with s-maxage directive' do
|
552
|
-
respond_with 200,
|
553
|
-
'Date' => (Time.now - 5).httpdate,
|
554
|
-
'Cache-Control' => 's-maxage=10, max-age=0'
|
555
|
-
|
556
|
-
get '/'
|
557
|
-
app.should.be.called
|
558
|
-
response.should.be.ok
|
559
|
-
response.headers.should.include 'Date'
|
560
|
-
cache.trace.should.include :miss
|
561
|
-
cache.trace.should.include :store
|
562
|
-
response.body.should.equal 'Hello World'
|
563
|
-
|
564
|
-
get '/'
|
565
|
-
response.should.be.ok
|
566
|
-
app.should.not.be.called
|
567
|
-
response['Date'].should.equal responses.first['Date']
|
568
|
-
response['Age'].to_i.should.satisfy { |age| age > 0 }
|
569
|
-
response['X-Content-Digest'].should.not.be.nil
|
570
|
-
cache.trace.should.include :fresh
|
571
|
-
cache.trace.should.not.include :store
|
572
|
-
response.body.should.equal 'Hello World'
|
573
|
-
end
|
574
|
-
|
575
|
-
it 'assigns default_ttl when response has no freshness information' do
|
576
|
-
respond_with 200
|
577
|
-
|
578
|
-
get '/', 'rack-cache.default_ttl' => 10
|
579
|
-
app.should.be.called
|
580
|
-
response.should.be.ok
|
581
|
-
cache.trace.should.include :miss
|
582
|
-
cache.trace.should.include :store
|
583
|
-
response.body.should.equal 'Hello World'
|
584
|
-
response['Cache-Control'].should.include 's-maxage=10'
|
585
|
-
|
586
|
-
get '/', 'rack-cache.default_ttl' => 10
|
587
|
-
response.should.be.ok
|
588
|
-
app.should.not.be.called
|
589
|
-
cache.trace.should.include :fresh
|
590
|
-
cache.trace.should.not.include :store
|
591
|
-
response.body.should.equal 'Hello World'
|
592
|
-
end
|
593
|
-
|
594
|
-
it 'does not assign default_ttl when response has must-revalidate directive' do
|
595
|
-
respond_with 200,
|
596
|
-
'Cache-Control' => 'must-revalidate'
|
597
|
-
|
598
|
-
get '/', 'rack-cache.default_ttl' => 10
|
599
|
-
app.should.be.called
|
600
|
-
response.should.be.ok
|
601
|
-
cache.trace.should.include :miss
|
602
|
-
cache.trace.should.not.include :store
|
603
|
-
response['Cache-Control'].should.not.include 's-maxage'
|
604
|
-
response.body.should.equal 'Hello World'
|
605
|
-
end
|
606
|
-
|
607
|
-
it 'fetches full response when cache stale and no validators present' do
|
608
|
-
respond_with 200, 'Expires' => (Time.now + 5).httpdate
|
609
|
-
|
610
|
-
# build initial request
|
611
|
-
get '/'
|
612
|
-
app.should.be.called
|
613
|
-
response.should.be.ok
|
614
|
-
response.headers.should.include 'Date'
|
615
|
-
response.headers.should.include 'X-Content-Digest'
|
616
|
-
response.headers.should.include 'Age'
|
617
|
-
cache.trace.should.include :miss
|
618
|
-
cache.trace.should.include :store
|
619
|
-
response.body.should.equal 'Hello World'
|
620
|
-
|
621
|
-
# go in and play around with the cached metadata directly ...
|
622
|
-
# XXX find some other way to do this
|
623
|
-
hash = cache.metastore.to_hash
|
624
|
-
hash.values.length.should.equal 1
|
625
|
-
entries = Marshal.load(hash.values.first)
|
626
|
-
entries.length.should.equal 1
|
627
|
-
req, res = entries.first
|
628
|
-
res['Expires'] = (Time.now - 1).httpdate
|
629
|
-
hash[hash.keys.first] = Marshal.dump([[req, res]])
|
630
|
-
|
631
|
-
# build subsequent request; should be found but miss due to freshness
|
632
|
-
get '/'
|
633
|
-
app.should.be.called
|
634
|
-
response.should.be.ok
|
635
|
-
response['Age'].to_i.should.equal 0
|
636
|
-
response.headers.should.include 'X-Content-Digest'
|
637
|
-
cache.trace.should.include :stale
|
638
|
-
cache.trace.should.not.include :fresh
|
639
|
-
cache.trace.should.not.include :miss
|
640
|
-
cache.trace.should.include :store
|
641
|
-
response.body.should.equal 'Hello World'
|
642
|
-
end
|
643
|
-
|
644
|
-
it 'validates cached responses with Last-Modified and no freshness information' do
|
645
|
-
timestamp = Time.now.httpdate
|
646
|
-
respond_with do |req,res|
|
647
|
-
res['Last-Modified'] = timestamp
|
648
|
-
if req.env['HTTP_IF_MODIFIED_SINCE'] == timestamp
|
649
|
-
res.status = 304
|
650
|
-
res.body = []
|
651
|
-
end
|
652
|
-
end
|
653
|
-
|
654
|
-
# build initial request
|
655
|
-
get '/'
|
656
|
-
app.should.be.called
|
657
|
-
response.should.be.ok
|
658
|
-
response.headers.should.include 'Last-Modified'
|
659
|
-
response.headers.should.include 'X-Content-Digest'
|
660
|
-
response.body.should.equal 'Hello World'
|
661
|
-
cache.trace.should.include :miss
|
662
|
-
cache.trace.should.include :store
|
663
|
-
cache.trace.should.not.include :stale
|
664
|
-
|
665
|
-
# build subsequent request; should be found but miss due to freshness
|
666
|
-
get '/'
|
667
|
-
app.should.be.called
|
668
|
-
response.should.be.ok
|
669
|
-
response.headers.should.include 'Last-Modified'
|
670
|
-
response.headers.should.include 'X-Content-Digest'
|
671
|
-
response['Age'].to_i.should.equal 0
|
672
|
-
response.body.should.equal 'Hello World'
|
673
|
-
cache.trace.should.include :stale
|
674
|
-
cache.trace.should.include :valid
|
675
|
-
cache.trace.should.include :store
|
676
|
-
cache.trace.should.not.include :miss
|
677
|
-
end
|
678
|
-
|
679
|
-
it 'validates cached responses with ETag and no freshness information' do
|
680
|
-
timestamp = Time.now.httpdate
|
681
|
-
respond_with do |req,res|
|
682
|
-
res['ETAG'] = '"12345"'
|
683
|
-
if req.env['HTTP_IF_NONE_MATCH'] == res['Etag']
|
684
|
-
res.status = 304
|
685
|
-
res.body = []
|
686
|
-
end
|
687
|
-
end
|
688
|
-
|
689
|
-
# build initial request
|
690
|
-
get '/'
|
691
|
-
app.should.be.called
|
692
|
-
response.should.be.ok
|
693
|
-
response.headers.should.include 'ETag'
|
694
|
-
response.headers.should.include 'X-Content-Digest'
|
695
|
-
response.body.should.equal 'Hello World'
|
696
|
-
cache.trace.should.include :miss
|
697
|
-
cache.trace.should.include :store
|
698
|
-
|
699
|
-
# build subsequent request; should be found but miss due to freshness
|
700
|
-
get '/'
|
701
|
-
app.should.be.called
|
702
|
-
response.should.be.ok
|
703
|
-
response.headers.should.include 'ETag'
|
704
|
-
response.headers.should.include 'X-Content-Digest'
|
705
|
-
response['Age'].to_i.should.equal 0
|
706
|
-
response.body.should.equal 'Hello World'
|
707
|
-
cache.trace.should.include :stale
|
708
|
-
cache.trace.should.include :valid
|
709
|
-
cache.trace.should.include :store
|
710
|
-
cache.trace.should.not.include :miss
|
711
|
-
end
|
712
|
-
|
713
|
-
it 'replaces cached responses when validation results in non-304 response' do
|
714
|
-
timestamp = Time.now.httpdate
|
715
|
-
count = 0
|
716
|
-
respond_with do |req,res|
|
717
|
-
res['Last-Modified'] = timestamp
|
718
|
-
case (count+=1)
|
719
|
-
when 1 ; res.body = ['first response']
|
720
|
-
when 2 ; res.body = ['second response']
|
721
|
-
when 3
|
722
|
-
res.body = []
|
723
|
-
res.status = 304
|
724
|
-
end
|
725
|
-
end
|
726
|
-
|
727
|
-
# first request should fetch from backend and store in cache
|
728
|
-
get '/'
|
729
|
-
response.status.should.equal 200
|
730
|
-
response.body.should.equal 'first response'
|
731
|
-
|
732
|
-
# second request is validated, is invalid, and replaces cached entry
|
733
|
-
get '/'
|
734
|
-
response.status.should.equal 200
|
735
|
-
response.body.should.equal 'second response'
|
736
|
-
|
737
|
-
# third respone is validated, valid, and returns cached entry
|
738
|
-
get '/'
|
739
|
-
response.status.should.equal 200
|
740
|
-
response.body.should.equal 'second response'
|
741
|
-
|
742
|
-
count.should.equal 3
|
743
|
-
end
|
744
|
-
|
745
|
-
it 'passes HEAD requests through directly on pass' do
|
746
|
-
respond_with do |req,res|
|
747
|
-
res.status = 200
|
748
|
-
res.body = []
|
749
|
-
req.request_method.should.equal 'HEAD'
|
750
|
-
end
|
751
|
-
|
752
|
-
head '/', 'HTTP_EXPECT' => 'something ...'
|
753
|
-
app.should.be.called
|
754
|
-
response.body.should.equal ''
|
755
|
-
end
|
756
|
-
|
757
|
-
it 'uses cache to respond to HEAD requests when fresh' do
|
758
|
-
respond_with do |req,res|
|
759
|
-
res['Cache-Control'] = 'max-age=10'
|
760
|
-
res.body = ['Hello World']
|
761
|
-
req.request_method.should.not.equal 'HEAD'
|
762
|
-
end
|
763
|
-
|
764
|
-
get '/'
|
765
|
-
app.should.be.called
|
766
|
-
response.status.should.equal 200
|
767
|
-
response.body.should.equal 'Hello World'
|
768
|
-
|
769
|
-
head '/'
|
770
|
-
app.should.not.be.called
|
771
|
-
response.status.should.equal 200
|
772
|
-
response.body.should.equal ''
|
773
|
-
response['Content-Length'].should.equal 'Hello World'.length.to_s
|
774
|
-
end
|
775
|
-
|
776
|
-
it 'invalidates cached responses on POST' do
|
777
|
-
respond_with do |req,res|
|
778
|
-
if req.request_method == 'GET'
|
779
|
-
res.status = 200
|
780
|
-
res['Cache-Control'] = 'public, max-age=500'
|
781
|
-
res.body = ['Hello World']
|
782
|
-
elsif req.request_method == 'POST'
|
783
|
-
res.status = 303
|
784
|
-
res['Location'] = '/'
|
785
|
-
res.headers.delete('Cache-Control')
|
786
|
-
res.body = []
|
787
|
-
end
|
788
|
-
end
|
789
|
-
|
790
|
-
# build initial request to enter into the cache
|
791
|
-
get '/'
|
792
|
-
app.should.be.called
|
793
|
-
response.should.be.ok
|
794
|
-
response.body.should.equal 'Hello World'
|
795
|
-
cache.trace.should.include :miss
|
796
|
-
cache.trace.should.include :store
|
797
|
-
|
798
|
-
# make sure it is valid
|
799
|
-
get '/'
|
800
|
-
app.should.not.called
|
801
|
-
response.should.be.ok
|
802
|
-
response.body.should.equal 'Hello World'
|
803
|
-
cache.trace.should.include :fresh
|
804
|
-
|
805
|
-
# now POST to same URL
|
806
|
-
post '/'
|
807
|
-
app.should.be.called
|
808
|
-
response.should.be.redirect
|
809
|
-
response['Location'].should.equal '/'
|
810
|
-
cache.trace.should.include :invalidate
|
811
|
-
cache.trace.should.include :pass
|
812
|
-
response.body.should.equal ''
|
813
|
-
|
814
|
-
# now make sure it was actually invalidated
|
815
|
-
get '/'
|
816
|
-
app.should.be.called
|
817
|
-
response.should.be.ok
|
818
|
-
response.body.should.equal 'Hello World'
|
819
|
-
cache.trace.should.include :stale
|
820
|
-
cache.trace.should.include :invalid
|
821
|
-
cache.trace.should.include :store
|
822
|
-
end
|
823
|
-
|
824
|
-
describe 'with responses that include a Vary header' do
|
825
|
-
before do
|
826
|
-
count = 0
|
827
|
-
respond_with 200 do |req,res|
|
828
|
-
res['Vary'] = 'Accept User-Agent Foo'
|
829
|
-
res['Cache-Control'] = 'max-age=10'
|
830
|
-
res['X-Response-Count'] = (count+=1).to_s
|
831
|
-
res.body = [req.env['HTTP_USER_AGENT']]
|
832
|
-
end
|
833
|
-
end
|
834
|
-
|
835
|
-
it 'serves from cache when headers match' do
|
836
|
-
get '/',
|
837
|
-
'HTTP_ACCEPT' => 'text/html',
|
838
|
-
'HTTP_USER_AGENT' => 'Bob/1.0'
|
839
|
-
response.should.be.ok
|
840
|
-
response.body.should.equal 'Bob/1.0'
|
841
|
-
cache.trace.should.include :miss
|
842
|
-
cache.trace.should.include :store
|
843
|
-
|
844
|
-
get '/',
|
845
|
-
'HTTP_ACCEPT' => 'text/html',
|
846
|
-
'HTTP_USER_AGENT' => 'Bob/1.0'
|
847
|
-
response.should.be.ok
|
848
|
-
response.body.should.equal 'Bob/1.0'
|
849
|
-
cache.trace.should.include :fresh
|
850
|
-
cache.trace.should.not.include :store
|
851
|
-
response.headers.should.include 'X-Content-Digest'
|
852
|
-
end
|
853
|
-
|
854
|
-
it 'stores multiple responses when headers differ' do
|
855
|
-
get '/',
|
856
|
-
'HTTP_ACCEPT' => 'text/html',
|
857
|
-
'HTTP_USER_AGENT' => 'Bob/1.0'
|
858
|
-
response.should.be.ok
|
859
|
-
response.body.should.equal 'Bob/1.0'
|
860
|
-
response['X-Response-Count'].should.equal '1'
|
861
|
-
|
862
|
-
get '/',
|
863
|
-
'HTTP_ACCEPT' => 'text/html',
|
864
|
-
'HTTP_USER_AGENT' => 'Bob/2.0'
|
865
|
-
cache.trace.should.include :miss
|
866
|
-
cache.trace.should.include :store
|
867
|
-
response.body.should.equal 'Bob/2.0'
|
868
|
-
response['X-Response-Count'].should.equal '2'
|
869
|
-
|
870
|
-
get '/',
|
871
|
-
'HTTP_ACCEPT' => 'text/html',
|
872
|
-
'HTTP_USER_AGENT' => 'Bob/1.0'
|
873
|
-
cache.trace.should.include :fresh
|
874
|
-
response.body.should.equal 'Bob/1.0'
|
875
|
-
response['X-Response-Count'].should.equal '1'
|
876
|
-
|
877
|
-
get '/',
|
878
|
-
'HTTP_ACCEPT' => 'text/html',
|
879
|
-
'HTTP_USER_AGENT' => 'Bob/2.0'
|
880
|
-
cache.trace.should.include :fresh
|
881
|
-
response.body.should.equal 'Bob/2.0'
|
882
|
-
response['X-Response-Count'].should.equal '2'
|
883
|
-
|
884
|
-
get '/',
|
885
|
-
'HTTP_USER_AGENT' => 'Bob/2.0'
|
886
|
-
cache.trace.should.include :miss
|
887
|
-
response.body.should.equal 'Bob/2.0'
|
888
|
-
response['X-Response-Count'].should.equal '3'
|
889
|
-
end
|
890
|
-
end
|
891
|
-
|
892
|
-
it 'passes if there was a metastore exception' do
|
893
|
-
respond_with 200, 'Cache-Control' => 'max-age=10000' do |req,res|
|
894
|
-
res.body = ['Hello World']
|
895
|
-
end
|
896
|
-
|
897
|
-
get '/'
|
898
|
-
response.should.be.ok
|
899
|
-
response.body.should.equal 'Hello World'
|
900
|
-
cache.trace.should.include :store
|
901
|
-
|
902
|
-
get '/' do |cache|
|
903
|
-
cache.meta_def(:metastore) { raise Timeout::Error }
|
904
|
-
end
|
905
|
-
response.should.be.ok
|
906
|
-
response.body.should.equal 'Hello World'
|
907
|
-
cache.trace.should.include :pass
|
908
|
-
|
909
|
-
post '/' do |cache|
|
910
|
-
cache.meta_def(:metastore) { raise Timeout::Error }
|
911
|
-
end
|
912
|
-
response.should.be.ok
|
913
|
-
response.body.should.equal 'Hello World'
|
914
|
-
cache.trace.should.include :pass
|
915
|
-
end
|
916
|
-
end
|