rack-cache 0.3.0 → 0.4

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.

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