rack-cache 1.0.3 → 1.1

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.

data/CHANGES CHANGED
@@ -1,3 +1,25 @@
1
+ ## 1.1 / September 2011
2
+
3
+ * Allow (INM/IMS) validation requests through to backend on miss. Makes it
4
+ possible to use validation for private / uncacheable responses. A number of
5
+ people using Rails's stale?() helper reported that their validation logic was
6
+ never kicking in.
7
+
8
+ * Add rack env rack-cache.force-pass option to bypass rack-cache on
9
+ per request basis
10
+
11
+ * Fix an issue with memcache namespace not being set when using the
12
+ :namespace option instead of :prefix_key.
13
+
14
+ * Fix test failures due to MockResponse changes in recent Rack
15
+ version (issue #34)
16
+
17
+ ## 1.0.3 / August 2011
18
+
19
+ * Fix bug passing options to memcached and dalli
20
+
21
+ * Document cache_key
22
+
1
23
  ## 1.0.1 / April 2011
2
24
 
3
25
  * Added lib/rack-cache.rb to match package name for auto-requiring machinery.
data/Gemfile CHANGED
@@ -1,7 +1,2 @@
1
1
  source :rubygems
2
-
3
- gem 'bacon'
4
- gem 'memcached'
5
- gem 'dalli'
6
- gem 'rack', '>= 0.4'
7
- gem 'rdiscount'
2
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,22 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ rack-cache (1.0.3)
5
+ rack (>= 0.4)
6
+
7
+ GEM
8
+ remote: http://rubygems.org/
9
+ specs:
10
+ bacon (1.1.0)
11
+ dalli (1.0.5)
12
+ memcached (1.3)
13
+ rack (1.3.2)
14
+
15
+ PLATFORMS
16
+ ruby
17
+
18
+ DEPENDENCIES
19
+ bacon
20
+ dalli
21
+ memcached
22
+ rack-cache!
data/Rakefile CHANGED
@@ -1,10 +1,15 @@
1
1
  require 'rake/clean'
2
2
 
3
- task :default => :test
3
+ task :default => [:setup, :test]
4
4
 
5
5
  CLEAN.include %w[coverage/ doc/api tags]
6
6
  CLOBBER.include %w[dist]
7
7
 
8
+ desc "Install gem dependencies"
9
+ task :setup do
10
+ sh "bundle check >/dev/null || bundle install", :verbose => false
11
+ end
12
+
8
13
  # SPECS =====================================================================
9
14
 
10
15
  desc 'Run specs with unit test style output'
data/doc/faq.markdown CHANGED
@@ -110,6 +110,13 @@ frameworks are based entirely on manual purge and do not support validation at
110
110
  the cache level.
111
111
 
112
112
 
113
+ <a class='hash' id='force-pass' href='#force-pass'>#</a>
114
+
115
+ ### Q: How do I bypass rack-cache on a per-request basis?
116
+
117
+ Set the `rack-cache.force-pass` variable in the rack environment to `true`.
118
+
119
+
113
120
  <a class='hash' id='efficient-validation' href='#efficient-validation'>#</a>
114
121
 
115
122
  ### Q: What does "Efficient Validation" mean?
@@ -61,7 +61,7 @@ module Rack::Cache
61
61
 
62
62
  response =
63
63
  if @request.get? || @request.head?
64
- if !@env['HTTP_EXPECT']
64
+ if !@env['HTTP_EXPECT'] && !@env['rack-cache.force-pass']
65
65
  lookup
66
66
  else
67
67
  pass
@@ -231,15 +231,13 @@ module Rack::Cache
231
231
  end
232
232
 
233
233
  # The cache missed or a reload is required. Forward the request to the
234
- # backend and determine whether the response should be stored.
234
+ # backend and determine whether the response should be stored. This allows
235
+ # conditional / validation requests through to the backend but performs no
236
+ # caching of the response when the backend returns a 304.
235
237
  def fetch
236
238
  # send no head requests because we want content
237
239
  @env['REQUEST_METHOD'] = 'GET'
238
240
 
239
- # avoid that the backend sends no content
240
- @env.delete('HTTP_IF_MODIFIED_SINCE')
241
- @env.delete('HTTP_IF_NONE_MATCH')
242
-
243
241
  response = forward
244
242
 
245
243
  # Mark the response as explicitly private if any of the private
@@ -58,7 +58,7 @@ module Rack::Cache
58
58
  end
59
59
 
60
60
  # Write the Rack response body immediately and return the SHA1 key.
61
- def write(body)
61
+ def write(body, ttl=nil)
62
62
  buf = []
63
63
  key, size = slurp(body) { |part| buf << part }
64
64
  @hash[key] = buf
@@ -119,7 +119,7 @@ module Rack::Cache
119
119
  nil
120
120
  end
121
121
 
122
- def write(body)
122
+ def write(body, ttl=nil)
123
123
  filename = ['buf', $$, Thread.current.object_id].join('-')
124
124
  temp_file = storage_path(filename)
125
125
  key, size =
@@ -227,10 +227,10 @@ module Rack::Cache
227
227
  data
228
228
  end
229
229
 
230
- def write(body)
230
+ def write(body, ttl=nil)
231
231
  buf = StringIO.new
232
232
  key, size = slurp(body){|part| buf.write(part) }
233
- [key, size] if cache.set(key, buf.string)
233
+ [key, size] if cache.set(key, buf.string, ttl)
234
234
  end
235
235
 
236
236
  def purge(key)
@@ -267,10 +267,10 @@ module Rack::Cache
267
267
  nil
268
268
  end
269
269
 
270
- def write(body)
270
+ def write(body, ttl=0)
271
271
  buf = StringIO.new
272
272
  key, size = slurp(body){|part| buf.write(part) }
273
- cache.set(key, buf.string, 0, false)
273
+ cache.set(key, buf.string, ttl, false)
274
274
  [key, size]
275
275
  end
276
276
 
@@ -315,10 +315,10 @@ module Rack::Cache
315
315
  end
316
316
  end
317
317
 
318
- def write(body)
318
+ def write(body, ttl=nil)
319
319
  buf = StringIO.new
320
320
  key, size = slurp(body){|part| buf.write(part) }
321
- cache.put(key, buf.string)
321
+ cache.put(key, buf.string, ttl)
322
322
  [key, size]
323
323
  end
324
324
 
@@ -57,7 +57,11 @@ module Rack::Cache
57
57
  # write the response body to the entity store if this is the
58
58
  # original response.
59
59
  if response.headers['X-Content-Digest'].nil?
60
- digest, size = entity_store.write(response.body)
60
+ if request.env['rack-cache.use_native_ttl'] && response.fresh?
61
+ digest, size = entity_store.write(response.body, response.ttl)
62
+ else
63
+ digest, size = entity_store.write(response.body)
64
+ end
61
65
  response.headers['X-Content-Digest'] = digest
62
66
  response.headers['Content-Length'] = size.to_s unless response.headers['Transfer-Encoding']
63
67
  response.body = entity_store.open(digest)
@@ -337,6 +341,7 @@ module Rack::Cache
337
341
  attr_reader :cache
338
342
 
339
343
  def initialize(server="localhost:11211", options={})
344
+ options[:prefix_key] ||= options.delete(:namespace) if options.key?(:namespace)
340
345
  @cache =
341
346
  if server.respond_to?(:stats)
342
347
  server
@@ -8,6 +8,8 @@ module Rack::Cache
8
8
  # below are stored in the Rack Environment as "rack-cache.<option>", where
9
9
  # <option> is the option name.
10
10
  module Options
11
+ extend self
12
+
11
13
  def self.option_accessor(key)
12
14
  name = option_name(key)
13
15
  define_method(key) { || options[name] }
@@ -95,6 +97,10 @@ module Rack::Cache
95
97
  # default for compliance with RFC 2616.
96
98
  option_accessor :allow_revalidate
97
99
 
100
+ # Specifies whether the underlying entity store's native expiration should
101
+ # be used.
102
+ option_accessor :use_native_ttl
103
+
98
104
  # The underlying options Hash. During initialization (or outside of a
99
105
  # request), this is a default values Hash. During a request, this is the
100
106
  # Rack environment Hash. The default values Hash is merged in underneath
@@ -134,7 +140,8 @@ module Rack::Cache
134
140
  'rack-cache.default_ttl' => 0,
135
141
  'rack-cache.private_headers' => ['Authorization', 'Cookie'],
136
142
  'rack-cache.allow_reload' => false,
137
- 'rack-cache.allow_revalidate' => false
143
+ 'rack-cache.allow_revalidate' => false,
144
+ 'rack-cache.use_native_ttl' => false,
138
145
  }
139
146
  self.options = options
140
147
  end
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 = '1.0.3'
7
- s.date = '2011-08-27'
6
+ s.version = '1.1'
7
+ s.date = '2011-09-18'
8
8
 
9
9
  s.description = "HTTP Caching for Rack"
10
10
  s.summary = "HTTP Caching for Rack"
@@ -17,6 +17,7 @@ Gem::Specification.new do |s|
17
17
  CHANGES
18
18
  COPYING
19
19
  Gemfile
20
+ Gemfile.lock
20
21
  README
21
22
  Rakefile
22
23
  TODO
data/test/context_test.rb CHANGED
@@ -15,6 +15,16 @@ describe 'Rack::Cache::Context' do
15
15
  response.headers.should.not.include 'Age'
16
16
  end
17
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
+
18
28
  %w[post put delete].each do |request_method|
19
29
  it "invalidates on #{request_method} requests" do
20
30
  respond_with 200
@@ -88,8 +98,8 @@ describe 'Rack::Cache::Context' do
88
98
  'HTTP_IF_MODIFIED_SINCE' => timestamp
89
99
  app.should.be.called
90
100
  response.status.should.equal 304
91
- response.headers.should.not.include 'Content-Length'
92
- response.headers.should.not.include 'Content-Type'
101
+ response.original_headers.should.not.include 'Content-Length'
102
+ response.original_headers.should.not.include 'Content-Type'
93
103
  response.body.should.empty
94
104
  cache.trace.should.include :miss
95
105
  cache.trace.should.include :store
@@ -107,8 +117,8 @@ describe 'Rack::Cache::Context' do
107
117
  'HTTP_IF_NONE_MATCH' => '12345'
108
118
  app.should.be.called
109
119
  response.status.should.equal 304
110
- response.headers.should.not.include 'Content-Length'
111
- response.headers.should.not.include 'Content-Type'
120
+ response.original_headers.should.not.include 'Content-Length'
121
+ response.original_headers.should.not.include 'Content-Type'
112
122
  response.headers.should.include 'ETag'
113
123
  response.body.should.empty
114
124
  cache.trace.should.include :miss
@@ -24,6 +24,15 @@ shared 'A Rack::Cache::EntityStore Implementation' do
24
24
  data.should.equal 'My wild love went riding,'
25
25
  end
26
26
 
27
+ it 'takes a ttl parameter for #write' do
28
+ key, size = @store.write(['My wild love went riding,'], 0)
29
+ key.should.not.be.nil
30
+ key.should.be.sha_like
31
+
32
+ data = @store.read(key)
33
+ data.should.equal 'My wild love went riding,'
34
+ end
35
+
27
36
  it 'correctly determines whether cached body exists for key with #exist?' do
28
37
  key, size = @store.write(['She rode to the devil,'])
29
38
  @store.should.exist key
@@ -185,10 +194,20 @@ describe 'Rack::Cache::EntityStore' do
185
194
  end
186
195
  behaves_like 'A Rack::Cache::EntityStore Implementation'
187
196
  end
188
-
189
- it 'passes options from uri' do
190
- memcached = Rack::Cache::EntityStore::Dalli.resolve URI.parse("memcached://#{ENV['MEMCACHED']}?show_backtraces=true")
191
- memcached.cache.instance_variable_get(:@options)[:show_backtraces].should.equal true
197
+
198
+ describe 'options parsing' do
199
+ before do
200
+ uri = URI.parse("memcached://#{ENV['MEMCACHED']}/obj_ns1?show_backtraces=true")
201
+ @memcached_metastore = Rack::Cache::MetaStore::MemCached.resolve uri
202
+ end
203
+
204
+ it 'passes options from uri' do
205
+ @memcached_metastore.cache.instance_variable_get(:@options)[:show_backtraces].should.equal true
206
+ end
207
+
208
+ it 'takes namespace into account' do
209
+ @memcached_metastore.cache.instance_variable_get(:@options)[:prefix_key].should.equal 'obj_ns1'
210
+ end
192
211
  end
193
212
  end
194
213
 
@@ -203,10 +222,20 @@ describe 'Rack::Cache::EntityStore' do
203
222
  @store = nil
204
223
  end
205
224
  behaves_like 'A Rack::Cache::EntityStore Implementation'
206
-
225
+ end
226
+
227
+ describe 'options parsing' do
228
+ before do
229
+ uri = URI.parse("memcached://#{ENV['MEMCACHED']}/obj_ns1?show_backtraces=true")
230
+ @dalli_metastore = Rack::Cache::MetaStore::Dalli.resolve uri
231
+ end
232
+
207
233
  it 'passes options from uri' do
208
- dalli = Rack::Cache::EntityStore::Dalli.resolve URI.parse("memcached://#{ENV['MEMCACHED']}?compression=true")
209
- dalli.cache.instance_variable_get(:@options)[:compression].should.equal true
234
+ @dalli_metastore.cache.instance_variable_get(:@options)[:show_backtraces].should.equal true
235
+ end
236
+
237
+ it 'takes namespace into account' do
238
+ @dalli_metastore.cache.instance_variable_get(:@options)[:namespace].should.equal 'obj_ns1'
210
239
  end
211
240
  end
212
241
  end
@@ -265,10 +265,20 @@ describe 'Rack::Cache::MetaStore' do
265
265
  end
266
266
  behaves_like 'A Rack::Cache::MetaStore Implementation'
267
267
  end
268
-
269
- it 'passes options from uri' do
270
- memcached = Rack::Cache::EntityStore::Dalli.resolve URI.parse("memcached://#{ENV['MEMCACHED']}?show_backtraces=true")
271
- memcached.cache.instance_variable_get(:@options)[:show_backtraces].should.equal true
268
+
269
+ describe 'options parsing' do
270
+ before do
271
+ uri = URI.parse("memcached://#{ENV['MEMCACHED']}/meta_ns1?show_backtraces=true")
272
+ @memcached_metastore = Rack::Cache::MetaStore::MemCached.resolve uri
273
+ end
274
+
275
+ it 'passes options from uri' do
276
+ @memcached_metastore.cache.instance_variable_get(:@options)[:show_backtraces].should.equal true
277
+ end
278
+
279
+ it 'takes namespace into account' do
280
+ @memcached_metastore.cache.instance_variable_get(:@options)[:prefix_key].should.equal 'meta_ns1'
281
+ end
272
282
  end
273
283
  end
274
284
 
@@ -282,10 +292,20 @@ describe 'Rack::Cache::MetaStore' do
282
292
  end
283
293
  behaves_like 'A Rack::Cache::MetaStore Implementation'
284
294
  end
285
-
286
- it 'passes options from uri' do
287
- dalli = Rack::Cache::EntityStore::Dalli.resolve URI.parse("memcached://#{ENV['MEMCACHED']}?compression=true")
288
- dalli.cache.instance_variable_get(:@options)[:compression].should.equal true
295
+
296
+ describe 'options parsing' do
297
+ before do
298
+ uri = URI.parse("memcached://#{ENV['MEMCACHED']}/meta_ns1?show_backtraces=true")
299
+ @dalli_metastore = Rack::Cache::MetaStore::Dalli.resolve uri
300
+ end
301
+
302
+ it 'passes options from uri' do
303
+ @dalli_metastore.cache.instance_variable_get(:@options)[:show_backtraces].should.equal true
304
+ end
305
+
306
+ it 'takes namespace into account' do
307
+ @dalli_metastore.cache.instance_variable_get(:@options)[:namespace].should.equal 'meta_ns1'
308
+ end
289
309
  end
290
310
  end
291
311
 
metadata CHANGED
@@ -1,73 +1,94 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: rack-cache
3
- version: !ruby/object:Gem::Version
4
- version: 1.0.3
3
+ version: !ruby/object:Gem::Version
4
+ hash: 13
5
5
  prerelease:
6
+ segments:
7
+ - 1
8
+ - 1
9
+ version: "1.1"
6
10
  platform: ruby
7
- authors:
11
+ authors:
8
12
  - Ryan Tomayko
9
13
  autorequire:
10
14
  bindir: bin
11
15
  cert_chain: []
12
- date: 2011-08-27 00:00:00.000000000Z
13
- dependencies:
14
- - !ruby/object:Gem::Dependency
16
+
17
+ date: 2011-09-18 00:00:00 Z
18
+ dependencies:
19
+ - !ruby/object:Gem::Dependency
15
20
  name: rack
16
- requirement: &70303071454760 !ruby/object:Gem::Requirement
21
+ prerelease: false
22
+ requirement: &id001 !ruby/object:Gem::Requirement
17
23
  none: false
18
- requirements:
19
- - - ! '>='
20
- - !ruby/object:Gem::Version
21
- version: '0.4'
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ hash: 3
28
+ segments:
29
+ - 0
30
+ - 4
31
+ version: "0.4"
22
32
  type: :runtime
23
- prerelease: false
24
- version_requirements: *70303071454760
25
- - !ruby/object:Gem::Dependency
33
+ version_requirements: *id001
34
+ - !ruby/object:Gem::Dependency
26
35
  name: bacon
27
- requirement: &70303071454380 !ruby/object:Gem::Requirement
36
+ prerelease: false
37
+ requirement: &id002 !ruby/object:Gem::Requirement
28
38
  none: false
29
- requirements:
30
- - - ! '>='
31
- - !ruby/object:Gem::Version
32
- version: '0'
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ hash: 3
43
+ segments:
44
+ - 0
45
+ version: "0"
33
46
  type: :development
34
- prerelease: false
35
- version_requirements: *70303071454380
36
- - !ruby/object:Gem::Dependency
47
+ version_requirements: *id002
48
+ - !ruby/object:Gem::Dependency
37
49
  name: memcached
38
- requirement: &70303071453920 !ruby/object:Gem::Requirement
50
+ prerelease: false
51
+ requirement: &id003 !ruby/object:Gem::Requirement
39
52
  none: false
40
- requirements:
41
- - - ! '>='
42
- - !ruby/object:Gem::Version
43
- version: '0'
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ hash: 3
57
+ segments:
58
+ - 0
59
+ version: "0"
44
60
  type: :development
45
- prerelease: false
46
- version_requirements: *70303071453920
47
- - !ruby/object:Gem::Dependency
61
+ version_requirements: *id003
62
+ - !ruby/object:Gem::Dependency
48
63
  name: dalli
49
- requirement: &70303071453500 !ruby/object:Gem::Requirement
64
+ prerelease: false
65
+ requirement: &id004 !ruby/object:Gem::Requirement
50
66
  none: false
51
- requirements:
52
- - - ! '>='
53
- - !ruby/object:Gem::Version
54
- version: '0'
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ hash: 3
71
+ segments:
72
+ - 0
73
+ version: "0"
55
74
  type: :development
56
- prerelease: false
57
- version_requirements: *70303071453500
75
+ version_requirements: *id004
58
76
  description: HTTP Caching for Rack
59
77
  email: r@tomayko.com
60
78
  executables: []
79
+
61
80
  extensions: []
62
- extra_rdoc_files:
81
+
82
+ extra_rdoc_files:
63
83
  - README
64
84
  - COPYING
65
85
  - TODO
66
86
  - CHANGES
67
- files:
87
+ files:
68
88
  - CHANGES
69
89
  - COPYING
70
90
  - Gemfile
91
+ - Gemfile.lock
71
92
  - README
72
93
  - Rakefile
73
94
  - TODO
@@ -108,35 +129,43 @@ files:
108
129
  - test/storage_test.rb
109
130
  homepage: http://tomayko.com/src/rack-cache/
110
131
  licenses: []
132
+
111
133
  post_install_message:
112
- rdoc_options:
134
+ rdoc_options:
113
135
  - --line-numbers
114
136
  - --inline-source
115
137
  - --title
116
138
  - Rack::Cache
117
139
  - --main
118
140
  - Rack::Cache
119
- require_paths:
141
+ require_paths:
120
142
  - lib
121
- required_ruby_version: !ruby/object:Gem::Requirement
143
+ required_ruby_version: !ruby/object:Gem::Requirement
122
144
  none: false
123
- requirements:
124
- - - ! '>='
125
- - !ruby/object:Gem::Version
126
- version: '0'
127
- required_rubygems_version: !ruby/object:Gem::Requirement
145
+ requirements:
146
+ - - ">="
147
+ - !ruby/object:Gem::Version
148
+ hash: 3
149
+ segments:
150
+ - 0
151
+ version: "0"
152
+ required_rubygems_version: !ruby/object:Gem::Requirement
128
153
  none: false
129
- requirements:
130
- - - ! '>='
131
- - !ruby/object:Gem::Version
132
- version: '0'
154
+ requirements:
155
+ - - ">="
156
+ - !ruby/object:Gem::Version
157
+ hash: 3
158
+ segments:
159
+ - 0
160
+ version: "0"
133
161
  requirements: []
162
+
134
163
  rubyforge_project:
135
164
  rubygems_version: 1.8.6
136
165
  signing_key:
137
166
  specification_version: 2
138
167
  summary: HTTP Caching for Rack
139
- test_files:
168
+ test_files:
140
169
  - test/cache_test.rb
141
170
  - test/cachecontrol_test.rb
142
171
  - test/context_test.rb