rack-cache 0.3.0 → 0.4

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of rack-cache might be problematic. Click here for more details.

Files changed (44) hide show
  1. data/CHANGES +43 -0
  2. data/README +18 -9
  3. data/Rakefile +1 -14
  4. data/TODO +13 -14
  5. data/doc/configuration.markdown +7 -153
  6. data/doc/faq.markdown +8 -0
  7. data/doc/index.markdown +7 -9
  8. data/example/sinatra/app.rb +25 -0
  9. data/example/sinatra/views/index.erb +44 -0
  10. data/lib/rack/cache.rb +5 -11
  11. data/lib/rack/cache/cachecontrol.rb +193 -0
  12. data/lib/rack/cache/context.rb +190 -52
  13. data/lib/rack/cache/entitystore.rb +10 -4
  14. data/lib/rack/cache/key.rb +52 -0
  15. data/lib/rack/cache/metastore.rb +52 -16
  16. data/lib/rack/cache/options.rb +60 -39
  17. data/lib/rack/cache/request.rb +11 -15
  18. data/lib/rack/cache/response.rb +221 -30
  19. data/lib/rack/cache/storage.rb +1 -2
  20. data/rack-cache.gemspec +9 -15
  21. data/test/cache_test.rb +9 -6
  22. data/test/cachecontrol_test.rb +139 -0
  23. data/test/context_test.rb +251 -169
  24. data/test/entitystore_test.rb +12 -11
  25. data/test/key_test.rb +50 -0
  26. data/test/metastore_test.rb +57 -14
  27. data/test/options_test.rb +11 -0
  28. data/test/request_test.rb +19 -0
  29. data/test/response_test.rb +164 -23
  30. data/test/spec_setup.rb +7 -0
  31. metadata +12 -20
  32. data/doc/events.dot +0 -27
  33. data/lib/rack/cache/config.rb +0 -65
  34. data/lib/rack/cache/config/busters.rb +0 -16
  35. data/lib/rack/cache/config/default.rb +0 -133
  36. data/lib/rack/cache/config/no-cache.rb +0 -13
  37. data/lib/rack/cache/core.rb +0 -299
  38. data/lib/rack/cache/headers.rb +0 -325
  39. data/lib/rack/utils/environment_headers.rb +0 -78
  40. data/test/config_test.rb +0 -66
  41. data/test/core_test.rb +0 -84
  42. data/test/environment_headers_test.rb +0 -69
  43. data/test/headers_test.rb +0 -298
  44. data/test/logging_test.rb +0 -45
@@ -3,11 +3,11 @@ require 'rack/cache/metastore'
3
3
  require 'rack/cache/entitystore'
4
4
 
5
5
  module Rack::Cache
6
+
6
7
  # Maintains a collection of MetaStore and EntityStore instances keyed by
7
8
  # URI. A single instance of this class can be used across a single process
8
9
  # to ensure that only a single instance of a backing store is created per
9
10
  # unique storage URI.
10
-
11
11
  class Storage
12
12
  def initialize
13
13
  @metastores = {}
@@ -22,7 +22,6 @@ module Rack::Cache
22
22
  @entitystores[uri.to_s] ||= create_store(EntityStore, uri)
23
23
  end
24
24
 
25
- # Clear store instances.
26
25
  def clear
27
26
  @metastores.clear
28
27
  @entitystores.clear
data/rack-cache.gemspec CHANGED
@@ -3,8 +3,8 @@ Gem::Specification.new do |s|
3
3
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
4
4
 
5
5
  s.name = 'rack-cache'
6
- s.version = '0.3.0'
7
- s.date = '2008-12-28'
6
+ s.version = '0.4'
7
+ s.date = '2009-03-16'
8
8
 
9
9
  s.description = "HTTP Caching for Rack"
10
10
  s.summary = "HTTP Caching for Rack"
@@ -20,7 +20,6 @@ Gem::Specification.new do |s|
20
20
  Rakefile
21
21
  TODO
22
22
  doc/configuration.markdown
23
- doc/events.dot
24
23
  doc/faq.markdown
25
24
  doc/index.markdown
26
25
  doc/layout.html.erb
@@ -28,33 +27,28 @@ Gem::Specification.new do |s|
28
27
  doc/rack-cache.css
29
28
  doc/server.ru
30
29
  doc/storage.markdown
30
+ example/sinatra/app.rb
31
+ example/sinatra/views/index.erb
31
32
  lib/rack/cache.rb
32
- lib/rack/cache/config.rb
33
- lib/rack/cache/config/busters.rb
34
- lib/rack/cache/config/default.rb
35
- lib/rack/cache/config/no-cache.rb
33
+ lib/rack/cache/cachecontrol.rb
36
34
  lib/rack/cache/context.rb
37
- lib/rack/cache/core.rb
38
35
  lib/rack/cache/entitystore.rb
39
- lib/rack/cache/headers.rb
36
+ lib/rack/cache/key.rb
40
37
  lib/rack/cache/metastore.rb
41
38
  lib/rack/cache/options.rb
42
39
  lib/rack/cache/request.rb
43
40
  lib/rack/cache/response.rb
44
41
  lib/rack/cache/storage.rb
45
- lib/rack/utils/environment_headers.rb
46
42
  rack-cache.gemspec
47
43
  test/cache_test.rb
48
- test/config_test.rb
44
+ test/cachecontrol_test.rb
49
45
  test/context_test.rb
50
- test/core_test.rb
51
46
  test/entitystore_test.rb
52
- test/environment_headers_test.rb
53
- test/headers_test.rb
54
- test/logging_test.rb
47
+ test/key_test.rb
55
48
  test/metastore_test.rb
56
49
  test/options_test.rb
57
50
  test/pony.jpg
51
+ test/request_test.rb
58
52
  test/response_test.rb
59
53
  test/spec_setup.rb
60
54
  test/storage_test.rb
data/test/cache_test.rb CHANGED
@@ -12,24 +12,27 @@ 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
- state, block_scope = 'not invoked', nil
26
- object =
27
- Rack::Cache.new @app do
28
- block_scope = self
28
+ state, object = 'not invoked', nil
29
+ instance =
30
+ Rack::Cache.new @app do |cache|
31
+ object = cache
29
32
  state = 'invoked'
30
- should.respond_to :on
33
+ cache.should.respond_to :set
31
34
  end
32
35
  state.should.equal 'invoked'
33
- object.should.be block_scope
36
+ object.should.be instance
34
37
  end
35
38
  end
@@ -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.a.performed :pass
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, 'Etag' => '"FOO"'
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.a.performed :miss
26
- cache.should.a.performed :fetch
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.a.performed :miss
39
- cache.should.a.performed :fetch
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, 'Etag' => '"FOO"'
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.a.performed :miss
53
- cache.should.a.performed :fetch
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.a.performed :miss
66
- cache.should.a.performed :fetch
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.a.performed :miss
89
- cache.should.a.performed :store
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['Etag'] = '12345'
101
+ res['ETag'] = '12345'
96
102
  res['Content-Type'] = 'text/plain'
97
103
  res.body = ['Hello World']
98
104
  end
@@ -103,28 +109,131 @@ 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 'Etag'
112
+ response.headers.should.include 'ETag'
107
113
  response.body.should.empty
108
- cache.should.a.performed :miss
109
- cache.should.a.performed :store
114
+ cache.trace.should.include :miss
115
+ cache.trace.should.include :store
110
116
  end
111
117
 
112
- it 'caches requests when Cache-Control request header set to no-cache' do
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.a.performed :store
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 'does not reload responses when allow_reload is set false' do
152
+ count = 0
153
+ respond_with 200, 'Cache-Control' => 'max-age=10000' do |req,res|
154
+ count+= 1
155
+ res.body = (count == 1) ? ['Hello World'] : ['Goodbye World']
156
+ end
157
+
158
+ get '/'
159
+ response.should.be.ok
160
+ response.body.should.equal 'Hello World'
161
+ cache.trace.should.include :store
162
+
163
+ get '/'
164
+ response.should.be.ok
165
+ response.body.should.equal 'Hello World'
166
+ cache.trace.should.include :fresh
167
+
168
+ get '/',
169
+ 'rack-cache.allow_reload' => false,
170
+ 'HTTP_CACHE_CONTROL' => 'no-cache'
171
+ response.should.be.ok
172
+ response.body.should.equal 'Hello World'
173
+ cache.trace.should.not.include :reload
174
+ end
175
+
176
+ it 'revalidates fresh cache entry when max-age request directive is exceeded' do
177
+ count = 0
178
+ respond_with do |req,res|
179
+ count+= 1
180
+ res['Cache-Control'] = 'max-age=10000'
181
+ res['ETag'] = count.to_s
182
+ res.body = (count == 1) ? ['Hello World'] : ['Goodbye World']
183
+ end
184
+
185
+ get '/'
186
+ response.should.be.ok
187
+ response.body.should.equal 'Hello World'
188
+ cache.trace.should.include :store
189
+
190
+ get '/'
191
+ response.should.be.ok
192
+ response.body.should.equal 'Hello World'
193
+ cache.trace.should.include :fresh
194
+
195
+ get '/', 'HTTP_CACHE_CONTROL' => 'max-age=0'
196
+ response.should.be.ok
197
+ response.body.should.equal 'Goodbye World'
198
+ cache.trace.should.include :stale
199
+ cache.trace.should.include :invalid
200
+ cache.trace.should.include :store
201
+ end
202
+
203
+ it 'does not revalidate fresh cache entry when enable_revalidate option is set false' do
204
+ count = 0
205
+ respond_with do |req,res|
206
+ count+= 1
207
+ res['Cache-Control'] = 'max-age=10000'
208
+ res['ETag'] = count.to_s
209
+ res.body = (count == 1) ? ['Hello World'] : ['Goodbye World']
210
+ end
211
+
212
+ get '/'
213
+ response.should.be.ok
214
+ response.body.should.equal 'Hello World'
215
+ cache.trace.should.include :store
216
+
217
+ get '/'
218
+ response.should.be.ok
219
+ response.body.should.equal 'Hello World'
220
+ cache.trace.should.include :fresh
221
+
222
+ get '/',
223
+ 'rack-cache.allow_revalidate' => false,
224
+ 'HTTP_CACHE_CONTROL' => 'max-age=0'
225
+ response.should.be.ok
226
+ response.body.should.equal 'Hello World'
227
+ cache.trace.should.not.include :stale
228
+ cache.trace.should.not.include :invalid
229
+ cache.trace.should.include :fresh
230
+ end
121
231
  it 'fetches response from backend when cache misses' do
122
232
  respond_with 200, 'Expires' => (Time.now + 5).httpdate
123
233
  get '/'
124
234
 
125
235
  response.should.be.ok
126
- cache.should.a.performed :miss
127
- cache.should.a.performed :fetch
236
+ cache.trace.should.include :miss
128
237
  response.headers.should.include 'Age'
129
238
  end
130
239
 
@@ -134,7 +243,7 @@ describe 'Rack::Cache::Context' do
134
243
  respond_with response_code, 'Expires' => (Time.now + 5).httpdate
135
244
  get '/'
136
245
 
137
- cache.should.a.not.performed :store
246
+ cache.trace.should.not.include :store
138
247
  response.status.should.equal response_code
139
248
  response.headers.should.not.include 'Age'
140
249
  end
@@ -148,7 +257,7 @@ describe 'Rack::Cache::Context' do
148
257
  get '/'
149
258
 
150
259
  response.should.be.ok
151
- cache.should.a.not.performed :store
260
+ cache.trace.should.not.include :store
152
261
  response.headers.should.not.include 'Age'
153
262
  end
154
263
 
@@ -157,7 +266,7 @@ describe 'Rack::Cache::Context' do
157
266
  get '/'
158
267
 
159
268
  response.should.be.ok
160
- cache.should.a.not.performed :store
269
+ cache.trace.should.not.include :store
161
270
  end
162
271
 
163
272
  it "caches responses with explicit no-cache directive" do
@@ -167,7 +276,7 @@ describe 'Rack::Cache::Context' do
167
276
  get '/'
168
277
 
169
278
  response.should.be.ok
170
- cache.should.a.performed :store
279
+ cache.trace.should.include :store
171
280
  response.headers.should.include 'Age'
172
281
  end
173
282
 
@@ -180,8 +289,8 @@ describe 'Rack::Cache::Context' do
180
289
  response.headers.should.include 'Date'
181
290
  response['Age'].should.not.be.nil
182
291
  response['X-Content-Digest'].should.not.be.nil
183
- cache.should.a.performed :miss
184
- cache.should.a.performed :store
292
+ cache.trace.should.include :miss
293
+ cache.trace.should.include :store
185
294
  cache.metastore.to_hash.keys.length.should.equal 1
186
295
  end
187
296
 
@@ -194,8 +303,8 @@ describe 'Rack::Cache::Context' do
194
303
  response.headers.should.include 'Date'
195
304
  response['Age'].should.not.be.nil
196
305
  response['X-Content-Digest'].should.not.be.nil
197
- cache.should.a.performed :miss
198
- cache.should.a.performed :store
306
+ cache.trace.should.include :miss
307
+ cache.trace.should.include :store
199
308
  cache.metastore.to_hash.keys.length.should.equal 1
200
309
  end
201
310
 
@@ -208,8 +317,8 @@ describe 'Rack::Cache::Context' do
208
317
  response.headers.should.include 'Date'
209
318
  response['Age'].should.not.be.nil
210
319
  response['X-Content-Digest'].should.not.be.nil
211
- cache.should.a.performed :miss
212
- cache.should.a.performed :store
320
+ cache.trace.should.include :miss
321
+ cache.trace.should.include :store
213
322
  cache.metastore.to_hash.keys.length.should.equal 1
214
323
  end
215
324
 
@@ -219,18 +328,18 @@ describe 'Rack::Cache::Context' do
219
328
 
220
329
  response.should.be.ok
221
330
  response.body.should.equal 'Hello World'
222
- cache.should.a.performed :miss
223
- cache.should.a.performed :store
331
+ cache.trace.should.include :miss
332
+ cache.trace.should.include :store
224
333
  end
225
334
 
226
335
  it 'caches responses with an ETag validator but no freshness information' do
227
- respond_with 200, 'Etag' => '"123456"'
336
+ respond_with 200, 'ETag' => '"123456"'
228
337
  get '/'
229
338
 
230
339
  response.should.be.ok
231
340
  response.body.should.equal 'Hello World'
232
- cache.should.a.performed :miss
233
- cache.should.a.performed :store
341
+ cache.trace.should.include :miss
342
+ cache.trace.should.include :store
234
343
  end
235
344
 
236
345
  it 'hits cached response with Expires header' do
@@ -242,8 +351,8 @@ describe 'Rack::Cache::Context' do
242
351
  app.should.be.called
243
352
  response.should.be.ok
244
353
  response.headers.should.include 'Date'
245
- cache.should.a.performed :miss
246
- cache.should.a.performed :store
354
+ cache.trace.should.include :miss
355
+ cache.trace.should.include :store
247
356
  response.body.should.equal 'Hello World'
248
357
 
249
358
  get '/'
@@ -252,8 +361,8 @@ describe 'Rack::Cache::Context' do
252
361
  response['Date'].should.equal responses.first['Date']
253
362
  response['Age'].to_i.should.satisfy { |age| age > 0 }
254
363
  response['X-Content-Digest'].should.not.be.nil
255
- cache.should.a.performed :hit
256
- cache.should.a.not.performed :fetch
364
+ cache.trace.should.include :fresh
365
+ cache.trace.should.not.include :store
257
366
  response.body.should.equal 'Hello World'
258
367
  end
259
368
 
@@ -266,8 +375,8 @@ describe 'Rack::Cache::Context' do
266
375
  app.should.be.called
267
376
  response.should.be.ok
268
377
  response.headers.should.include 'Date'
269
- cache.should.a.performed :miss
270
- cache.should.a.performed :store
378
+ cache.trace.should.include :miss
379
+ cache.trace.should.include :store
271
380
  response.body.should.equal 'Hello World'
272
381
 
273
382
  get '/'
@@ -276,8 +385,8 @@ describe 'Rack::Cache::Context' do
276
385
  response['Date'].should.equal responses.first['Date']
277
386
  response['Age'].to_i.should.satisfy { |age| age > 0 }
278
387
  response['X-Content-Digest'].should.not.be.nil
279
- cache.should.a.performed :hit
280
- cache.should.a.not.performed :fetch
388
+ cache.trace.should.include :fresh
389
+ cache.trace.should.not.include :store
281
390
  response.body.should.equal 'Hello World'
282
391
  end
283
392
 
@@ -290,8 +399,8 @@ describe 'Rack::Cache::Context' do
290
399
  app.should.be.called
291
400
  response.should.be.ok
292
401
  response.headers.should.include 'Date'
293
- cache.should.a.performed :miss
294
- cache.should.a.performed :store
402
+ cache.trace.should.include :miss
403
+ cache.trace.should.include :store
295
404
  response.body.should.equal 'Hello World'
296
405
 
297
406
  get '/'
@@ -300,8 +409,8 @@ describe 'Rack::Cache::Context' do
300
409
  response['Date'].should.equal responses.first['Date']
301
410
  response['Age'].to_i.should.satisfy { |age| age > 0 }
302
411
  response['X-Content-Digest'].should.not.be.nil
303
- cache.should.a.performed :hit
304
- cache.should.a.not.performed :fetch
412
+ cache.trace.should.include :fresh
413
+ cache.trace.should.not.include :store
305
414
  response.body.should.equal 'Hello World'
306
415
  end
307
416
 
@@ -311,16 +420,16 @@ describe 'Rack::Cache::Context' do
311
420
  get '/', 'rack-cache.default_ttl' => 10
312
421
  app.should.be.called
313
422
  response.should.be.ok
314
- cache.should.a.performed :miss
315
- cache.should.a.performed :store
423
+ cache.trace.should.include :miss
424
+ cache.trace.should.include :store
316
425
  response.body.should.equal 'Hello World'
317
426
  response['Cache-Control'].should.include 's-maxage=10'
318
427
 
319
428
  get '/', 'rack-cache.default_ttl' => 10
320
429
  response.should.be.ok
321
430
  app.should.not.be.called
322
- cache.should.a.performed :hit
323
- cache.should.a.not.performed :fetch
431
+ cache.trace.should.include :fresh
432
+ cache.trace.should.not.include :store
324
433
  response.body.should.equal 'Hello World'
325
434
  end
326
435
 
@@ -331,8 +440,8 @@ describe 'Rack::Cache::Context' do
331
440
  get '/', 'rack-cache.default_ttl' => 10
332
441
  app.should.be.called
333
442
  response.should.be.ok
334
- cache.should.a.performed :miss
335
- cache.should.a.not.performed :store
443
+ cache.trace.should.include :miss
444
+ cache.trace.should.not.include :store
336
445
  response['Cache-Control'].should.not.include 's-maxage'
337
446
  response.body.should.equal 'Hello World'
338
447
  end
@@ -347,8 +456,8 @@ describe 'Rack::Cache::Context' do
347
456
  response.headers.should.include 'Date'
348
457
  response.headers.should.include 'X-Content-Digest'
349
458
  response.headers.should.include 'Age'
350
- cache.should.a.performed :miss
351
- cache.should.a.performed :store
459
+ cache.trace.should.include :miss
460
+ cache.trace.should.include :store
352
461
  response.body.should.equal 'Hello World'
353
462
 
354
463
  # go in and play around with the cached metadata directly ...
@@ -361,10 +470,10 @@ describe 'Rack::Cache::Context' do
361
470
  response.should.be.ok
362
471
  response['Age'].to_i.should.equal 0
363
472
  response.headers.should.include 'X-Content-Digest'
364
- cache.should.a.not.performed :hit
365
- cache.should.a.not.performed :miss
366
- cache.should.a.performed :fetch
367
- cache.should.a.performed :store
473
+ cache.trace.should.include :stale
474
+ cache.trace.should.not.include :fresh
475
+ cache.trace.should.not.include :miss
476
+ cache.trace.should.include :store
368
477
  response.body.should.equal 'Hello World'
369
478
  end
370
479
 
@@ -385,8 +494,9 @@ describe 'Rack::Cache::Context' do
385
494
  response.headers.should.include 'Last-Modified'
386
495
  response.headers.should.include 'X-Content-Digest'
387
496
  response.body.should.equal 'Hello World'
388
- cache.should.a.performed :miss
389
- cache.should.a.performed :store
497
+ cache.trace.should.include :miss
498
+ cache.trace.should.include :store
499
+ cache.trace.should.not.include :stale
390
500
 
391
501
  # build subsequent request; should be found but miss due to freshness
392
502
  get '/'
@@ -395,11 +505,11 @@ describe 'Rack::Cache::Context' do
395
505
  response.headers.should.include 'Last-Modified'
396
506
  response.headers.should.include 'X-Content-Digest'
397
507
  response['Age'].to_i.should.equal 0
398
- response['X-Origin-Status'].should.equal '304'
399
508
  response.body.should.equal 'Hello World'
400
- cache.should.a.not.performed :miss
401
- cache.should.a.performed :fetch
402
- cache.should.a.performed :store
509
+ cache.trace.should.include :stale
510
+ cache.trace.should.include :valid
511
+ cache.trace.should.include :store
512
+ cache.trace.should.not.include :miss
403
513
  end
404
514
 
405
515
  it 'validates cached responses with ETag and no freshness information' do
@@ -416,24 +526,24 @@ describe 'Rack::Cache::Context' do
416
526
  get '/'
417
527
  app.should.be.called
418
528
  response.should.be.ok
419
- response.headers.should.include 'Etag'
529
+ response.headers.should.include 'ETag'
420
530
  response.headers.should.include 'X-Content-Digest'
421
531
  response.body.should.equal 'Hello World'
422
- cache.should.a.performed :miss
423
- cache.should.a.performed :store
532
+ cache.trace.should.include :miss
533
+ cache.trace.should.include :store
424
534
 
425
535
  # build subsequent request; should be found but miss due to freshness
426
536
  get '/'
427
537
  app.should.be.called
428
538
  response.should.be.ok
429
- response.headers.should.include 'Etag'
539
+ response.headers.should.include 'ETag'
430
540
  response.headers.should.include 'X-Content-Digest'
431
541
  response['Age'].to_i.should.equal 0
432
- response['X-Origin-Status'].should.equal '304'
433
542
  response.body.should.equal 'Hello World'
434
- cache.should.a.not.performed :miss
435
- cache.should.a.performed :fetch
436
- cache.should.a.performed :store
543
+ cache.trace.should.include :stale
544
+ cache.trace.should.include :valid
545
+ cache.trace.should.include :store
546
+ cache.trace.should.not.include :miss
437
547
  end
438
548
 
439
549
  it 'replaces cached responses when validation results in non-304 response' do
@@ -442,8 +552,8 @@ describe 'Rack::Cache::Context' do
442
552
  respond_with do |req,res|
443
553
  res['Last-Modified'] = timestamp
444
554
  case (count+=1)
445
- when 1 ; res.body = 'first response'
446
- when 2 ; res.body = 'second response'
555
+ when 1 ; res.body = ['first response']
556
+ when 2 ; res.body = ['second response']
447
557
  when 3
448
558
  res.body = []
449
559
  res.status = 304
@@ -475,11 +585,7 @@ describe 'Rack::Cache::Context' do
475
585
  req.request_method.should.equal 'HEAD'
476
586
  end
477
587
 
478
- cache_config do
479
- on(:receive) { pass! }
480
- end
481
-
482
- head '/'
588
+ head '/', 'HTTP_EXPECT' => 'something ...'
483
589
  app.should.be.called
484
590
  response.body.should.equal ''
485
591
  end
@@ -503,6 +609,53 @@ describe 'Rack::Cache::Context' do
503
609
  response['Content-Length'].should.equal 'Hello World'.length.to_s
504
610
  end
505
611
 
612
+ it 'invalidates cached responses on POST' do
613
+ respond_with do |req,res|
614
+ if req.request_method == 'GET'
615
+ res.status = 200
616
+ res['Cache-Control'] = 'public, max-age=500'
617
+ res.body = ['Hello World']
618
+ elsif req.request_method == 'POST'
619
+ res.status = 303
620
+ res['Location'] = '/'
621
+ res.headers.delete('Cache-Control')
622
+ res.body = []
623
+ end
624
+ end
625
+
626
+ # build initial request to enter into the cache
627
+ get '/'
628
+ app.should.be.called
629
+ response.should.be.ok
630
+ response.body.should.equal 'Hello World'
631
+ cache.trace.should.include :miss
632
+ cache.trace.should.include :store
633
+
634
+ # make sure it is valid
635
+ get '/'
636
+ app.should.not.called
637
+ response.should.be.ok
638
+ response.body.should.equal 'Hello World'
639
+ cache.trace.should.include :fresh
640
+
641
+ # now POST to same URL
642
+ post '/'
643
+ app.should.be.called
644
+ response.should.be.redirect
645
+ response['Location'].should.equal '/'
646
+ cache.trace.should.include :invalidate
647
+ cache.trace.should.include :pass
648
+ response.body.should.equal ''
649
+
650
+ # now make sure it was actually invalidated
651
+ get '/'
652
+ app.should.be.called
653
+ response.should.be.ok
654
+ response.body.should.equal 'Hello World'
655
+ cache.trace.should.include :stale
656
+ cache.trace.should.include :invalid
657
+ cache.trace.should.include :store
658
+ end
506
659
 
507
660
  describe 'with responses that include a Vary header' do
508
661
  before(:each) do
@@ -511,7 +664,7 @@ describe 'Rack::Cache::Context' do
511
664
  res['Vary'] = 'Accept User-Agent Foo'
512
665
  res['Cache-Control'] = 'max-age=10'
513
666
  res['X-Response-Count'] = (count+=1).to_s
514
- res.body = req.env['HTTP_USER_AGENT']
667
+ res.body = [req.env['HTTP_USER_AGENT']]
515
668
  end
516
669
  end
517
670
 
@@ -521,16 +674,16 @@ describe 'Rack::Cache::Context' do
521
674
  'HTTP_USER_AGENT' => 'Bob/1.0'
522
675
  response.should.be.ok
523
676
  response.body.should.equal 'Bob/1.0'
524
- cache.should.a.performed :miss
525
- cache.should.a.performed :store
677
+ cache.trace.should.include :miss
678
+ cache.trace.should.include :store
526
679
 
527
680
  get '/',
528
681
  'HTTP_ACCEPT' => 'text/html',
529
682
  'HTTP_USER_AGENT' => 'Bob/1.0'
530
683
  response.should.be.ok
531
684
  response.body.should.equal 'Bob/1.0'
532
- cache.should.a.performed :hit
533
- cache.should.a.not.performed :fetch
685
+ cache.trace.should.include :fresh
686
+ cache.trace.should.not.include :store
534
687
  response.headers.should.include 'X-Content-Digest'
535
688
  end
536
689
 
@@ -545,101 +698,30 @@ describe 'Rack::Cache::Context' do
545
698
  get '/',
546
699
  'HTTP_ACCEPT' => 'text/html',
547
700
  'HTTP_USER_AGENT' => 'Bob/2.0'
548
- cache.should.a.performed :miss
549
- cache.should.a.performed :store
701
+ cache.trace.should.include :miss
702
+ cache.trace.should.include :store
550
703
  response.body.should.equal 'Bob/2.0'
551
704
  response['X-Response-Count'].should.equal '2'
552
705
 
553
706
  get '/',
554
707
  'HTTP_ACCEPT' => 'text/html',
555
708
  'HTTP_USER_AGENT' => 'Bob/1.0'
556
- cache.should.a.performed :hit
709
+ cache.trace.should.include :fresh
557
710
  response.body.should.equal 'Bob/1.0'
558
711
  response['X-Response-Count'].should.equal '1'
559
712
 
560
713
  get '/',
561
714
  'HTTP_ACCEPT' => 'text/html',
562
715
  'HTTP_USER_AGENT' => 'Bob/2.0'
563
- cache.should.a.performed :hit
716
+ cache.trace.should.include :fresh
564
717
  response.body.should.equal 'Bob/2.0'
565
718
  response['X-Response-Count'].should.equal '2'
566
719
 
567
720
  get '/',
568
721
  'HTTP_USER_AGENT' => 'Bob/2.0'
569
- cache.should.a.performed :miss
722
+ cache.trace.should.include :miss
570
723
  response.body.should.equal 'Bob/2.0'
571
724
  response['X-Response-Count'].should.equal '3'
572
725
  end
573
726
  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
727
  end