rack-cache 0.4 → 0.5
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 +24 -0
- data/TODO +11 -15
- data/doc/configuration.markdown +28 -2
- data/doc/index.markdown +3 -0
- data/lib/rack/cache/appengine.rb +52 -0
- data/lib/rack/cache/entitystore.rb +115 -35
- data/lib/rack/cache/metastore.rb +95 -24
- data/lib/rack/cache/options.rb +2 -2
- data/rack-cache.gemspec +4 -3
- data/test/context_test.rb +28 -6
- data/test/entitystore_test.rb +42 -1
- data/test/metastore_test.rb +41 -2
- data/test/spec_setup.rb +37 -1
- metadata +4 -3
data/CHANGES
CHANGED
@@ -1,3 +1,27 @@
|
|
1
|
+
## 0.5.0 / May 2009
|
2
|
+
|
3
|
+
* Added meta and entity store implementations based on the
|
4
|
+
memcache-client library. These are the default unless the memcached
|
5
|
+
library has already been required.
|
6
|
+
|
7
|
+
* The "allow_reload" and "allow_revalidate" options now default to
|
8
|
+
false instead of true. This means we break with RFC 2616 out of
|
9
|
+
the box but this is the expected configuration in a huge majority
|
10
|
+
of gateway cache scenarios. See the docs on configuration
|
11
|
+
options for more information on these options:
|
12
|
+
http://tomayko.com/src/rack-cache/configuration
|
13
|
+
|
14
|
+
* Added Google AppEngine memcache entity store and metastore
|
15
|
+
implementations. To use GAE's memcache with rack-cache, set the
|
16
|
+
"metastore" and "entitystore" options as follows:
|
17
|
+
|
18
|
+
use Rack::Cache,
|
19
|
+
:metastore => 'gae://cache-meta',
|
20
|
+
:entitystore => 'gae://cache-body'
|
21
|
+
|
22
|
+
The 'cache-meta' and 'cache-body' parts are memcache namespace
|
23
|
+
prefixes and should be set to different values.
|
24
|
+
|
1
25
|
## 0.4.0 / March 2009
|
2
26
|
|
3
27
|
* Ruby 1.9.1 / Rack 1.0 compatible.
|
data/TODO
CHANGED
@@ -1,31 +1,27 @@
|
|
1
|
-
##
|
1
|
+
## 0.5
|
2
2
|
|
3
|
-
-
|
4
|
-
|
3
|
+
- Document allow_revalidate and allow_reload options.
|
4
|
+
- Support multiple memcache servers.
|
5
|
+
- Purge/invalidate everything
|
6
|
+
- Explicit expiration/invalidation based on response headers or via an
|
7
|
+
object interface passed in the rack env.
|
5
8
|
- Sample apps: Rack, Rails, Sinatra, Merb, etc.
|
9
|
+
- Move old breakers.rb configuration file into rack-contrib as a
|
10
|
+
middleware component.
|
11
|
+
|
12
|
+
## Backlog
|
13
|
+
|
6
14
|
- Use Bacon instead of test/spec
|
7
|
-
- Work with both memcache and memcached gems (memcached hasn't built on MacOS
|
8
|
-
for some time now).
|
9
15
|
- Fast path pass processing. We do a lot more than necessary just to determine
|
10
16
|
that the response should be passed through untouched.
|
11
|
-
- Don't purge/remove cache entries when invalidating. The entries should be
|
12
|
-
marked as stale and be forced revalidated on the next request instead of
|
13
|
-
being removed entirely.
|
14
|
-
- Add missing Expires header if we have a max-age.
|
15
|
-
- Purge/invalidate everything
|
16
17
|
- Invalidate at the URI of the Location or Content-Location response header
|
17
18
|
on POST, PUT, or DELETE that results in a redirect.
|
18
19
|
- Maximum size of cached entity
|
19
20
|
- Last-Modified factor: requests that have a Last-Modified header but no Expires
|
20
21
|
header have a TTL assigned based on the last modified age of the response:
|
21
22
|
TTL = (Age * Factor), or, 1h = (10h * 0.1)
|
22
|
-
- Run under multiple-threads with an option to lock before making requests
|
23
|
-
to the backend. The idea is to be able to serve requests from cache in
|
24
|
-
separate threads. This should probably be implemented as a separate
|
25
|
-
middleware component.
|
26
23
|
- Consider implementing ESI (http://www.w3.org/TR/esi-lang). This should
|
27
24
|
probably be implemented as a separate middleware component.
|
28
|
-
- Sqlite3 (meta store)
|
29
25
|
- stale-while-revalidate
|
30
26
|
- Serve cached copies when down (see: stale-if-error) - e.g., database
|
31
27
|
connection drops and the cache takes over what it can.
|
data/doc/configuration.markdown
CHANGED
@@ -16,8 +16,8 @@ __Environment__.
|
|
16
16
|
When the __Rack::Cache__ object is instantiated:
|
17
17
|
|
18
18
|
use Rack::Cache,
|
19
|
-
:verbose
|
20
|
-
:metastore
|
19
|
+
:verbose => true,
|
20
|
+
:metastore => 'memcached://localhost:11211/',
|
21
21
|
:entitystore => 'file:/var/cache/rack'
|
22
22
|
|
23
23
|
Using __Rack__'s __Environment__:
|
@@ -81,6 +81,32 @@ If any of these headers are present in the request, the response is considered
|
|
81
81
|
private and will not be cached _unless_ the response is explicitly marked public
|
82
82
|
(e.g., `Cache-Control: public`).
|
83
83
|
|
84
|
+
### `allow_reload`
|
85
|
+
|
86
|
+
A boolean specifying whether reload requests sent by the client should be
|
87
|
+
honored by the cache. When this option is enabled (`rack-cache.allow_reload`
|
88
|
+
is `true`), requests that include a `Cache-Control: no-cache` header cause
|
89
|
+
the cache to discard anything it has stored for the request and ask that the
|
90
|
+
response be fully generated.
|
91
|
+
|
92
|
+
Most browsers include a `Cache-Control: no-cache` header when the user performs
|
93
|
+
a "hard refresh" (e.g., holding `Shift` while clicking the "Refresh" button).
|
94
|
+
|
95
|
+
*IMPORTANT: Enabling this option globally allows all clients to break your cache.*
|
96
|
+
|
97
|
+
### `allow_revalidate`
|
98
|
+
|
99
|
+
A boolean specifying whether revalidate requests sent by the client should be
|
100
|
+
honored by the cache. When this option is enabled (`rack-cache.allow_revalidate`
|
101
|
+
is `true`), requests that include a `Cache-Control: max-age=0` header cause the
|
102
|
+
cache to assume its copy of the response is stale, resulting in a conditional
|
103
|
+
GET / validation request to be sent to the server.
|
104
|
+
|
105
|
+
Most browsers include a `Cache-Control: max-age=0` header when the user performs
|
106
|
+
a refresh (e.g., clicking the "Refresh" button).
|
107
|
+
|
108
|
+
*IMPORTANT: Enabling this option globally allows all clients to break your cache.*
|
109
|
+
|
84
110
|
### `cache_key`
|
85
111
|
|
86
112
|
TODO: Document custom cache keys
|
data/doc/index.markdown
CHANGED
@@ -12,6 +12,9 @@ for [Rack][]-based applications that produce freshness (`Expires`,
|
|
12
12
|
News
|
13
13
|
----
|
14
14
|
|
15
|
+
* Rack::Cache 0.5 was released on May 25, 2009. See the
|
16
|
+
[`CHANGES`](http://github.com/rtomayko/rack-cache/blob/0.5.0/CHANGES) file
|
17
|
+
for details.
|
15
18
|
* [How to use Rack::Cache with Rails 2.3](http://snippets.aktagon.com/snippets/302-How-to-setup-and-use-Rack-Cache-with-Rails-2-3-0-RC-1) - it's really easy.
|
16
19
|
* [RailsLab's Advanced HTTP Caching Screencast](http://railslab.newrelic.com/2009/02/26/episode-11-advanced-http-caching)
|
17
20
|
is a really great review of HTTP caching concepts and shows how to
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'base64'
|
2
|
+
|
3
|
+
module Rack::Cache::AppEngine
|
4
|
+
|
5
|
+
module MC
|
6
|
+
require 'java'
|
7
|
+
|
8
|
+
import com.google.appengine.api.memcache.Expiration;
|
9
|
+
import com.google.appengine.api.memcache.MemcacheService;
|
10
|
+
import com.google.appengine.api.memcache.MemcacheServiceFactory;
|
11
|
+
import com.google.appengine.api.memcache.Stats;
|
12
|
+
|
13
|
+
Service = MemcacheServiceFactory.getMemcacheService
|
14
|
+
end unless defined?(Rack::Cache::AppEngine::MC)
|
15
|
+
|
16
|
+
class MemCache
|
17
|
+
|
18
|
+
def initialize(options = {})
|
19
|
+
@cache = MC::Service
|
20
|
+
@cache.namespace = options[:namespace] if options[:namespace]
|
21
|
+
end
|
22
|
+
|
23
|
+
def contains?(key)
|
24
|
+
MC::Service.contains(key)
|
25
|
+
end
|
26
|
+
|
27
|
+
def get(key)
|
28
|
+
value = MC::Service.get(key)
|
29
|
+
Marshal.load(Base64.decode64(value)) if value
|
30
|
+
end
|
31
|
+
|
32
|
+
def put(key, value, ttl = nil)
|
33
|
+
expiration = ttl ? MC::Expiration.byDeltaSeconds(ttl) : nil
|
34
|
+
value = Base64.encode64(Marshal.dump(value)).gsub(/\n/, '')
|
35
|
+
MC::Service.put(key, value, expiration)
|
36
|
+
end
|
37
|
+
|
38
|
+
def namespace
|
39
|
+
MC::Service.getNamespace
|
40
|
+
end
|
41
|
+
|
42
|
+
def namespace=(value)
|
43
|
+
MC::Service.setNamespace(value.to_s)
|
44
|
+
end
|
45
|
+
|
46
|
+
def delete(key)
|
47
|
+
MC::Service.delete(key)
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
@@ -19,7 +19,7 @@ module Rack::Cache
|
|
19
19
|
yield part
|
20
20
|
end
|
21
21
|
body.close if body.respond_to? :close
|
22
|
-
[
|
22
|
+
[digest.hexdigest, size]
|
23
23
|
end
|
24
24
|
|
25
25
|
if ''.respond_to?(:bytesize)
|
@@ -168,35 +168,136 @@ module Rack::Cache
|
|
168
168
|
DISK = Disk
|
169
169
|
FILE = Disk
|
170
170
|
|
171
|
-
#
|
172
|
-
class
|
173
|
-
|
171
|
+
# Base class for memcached entity stores.
|
172
|
+
class MemCacheBase < EntityStore
|
174
173
|
# The underlying Memcached instance used to communicate with the
|
175
|
-
#
|
174
|
+
# memcached daemon.
|
176
175
|
attr_reader :cache
|
177
176
|
|
177
|
+
extend Rack::Utils
|
178
|
+
|
179
|
+
def open(key)
|
180
|
+
data = read(key)
|
181
|
+
data && [data]
|
182
|
+
end
|
183
|
+
|
184
|
+
def self.resolve(uri)
|
185
|
+
server = "#{uri.host}:#{uri.port || '11211'}"
|
186
|
+
options = parse_query(uri.query)
|
187
|
+
options.keys.each do |key|
|
188
|
+
value =
|
189
|
+
case value = options.delete(key)
|
190
|
+
when 'true' ; true
|
191
|
+
when 'false' ; false
|
192
|
+
else value.to_sym
|
193
|
+
end
|
194
|
+
options[k.to_sym] = value
|
195
|
+
end
|
196
|
+
options[:namespace] = uri.path.sub(/^\//, '')
|
197
|
+
new server, options
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
# Uses the memcache-client ruby library. This is the default unless
|
202
|
+
# the memcached library has already been required.
|
203
|
+
class MemCache < MemCacheBase
|
204
|
+
def initialize(server="localhost:11211", options={})
|
205
|
+
@cache =
|
206
|
+
if server.respond_to?(:stats)
|
207
|
+
server
|
208
|
+
else
|
209
|
+
require 'memcache'
|
210
|
+
::MemCache.new(server, options)
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
def exist?(key)
|
215
|
+
!cache.get(key).nil?
|
216
|
+
end
|
217
|
+
|
218
|
+
def read(key)
|
219
|
+
cache.get(key)
|
220
|
+
end
|
221
|
+
|
222
|
+
def write(body)
|
223
|
+
buf = StringIO.new
|
224
|
+
key, size = slurp(body){|part| buf.write(part) }
|
225
|
+
[key, size] if cache.set(key, buf.string)
|
226
|
+
end
|
227
|
+
|
228
|
+
def purge(key)
|
229
|
+
cache.delete(key)
|
230
|
+
nil
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
# Uses the memcached client library. The ruby based memcache-client is used
|
235
|
+
# in preference to this store unless the memcached library has already been
|
236
|
+
# required.
|
237
|
+
class MemCached < MemCacheBase
|
178
238
|
def initialize(server="localhost:11211", options={})
|
239
|
+
options[:prefix_key] ||= options.delete(:namespace) if options.key?(:namespace)
|
179
240
|
@cache =
|
180
241
|
if server.respond_to?(:stats)
|
181
242
|
server
|
182
243
|
else
|
183
244
|
require 'memcached'
|
184
|
-
Memcached.new(server, options)
|
245
|
+
::Memcached.new(server, options)
|
185
246
|
end
|
186
247
|
end
|
187
248
|
|
188
249
|
def exist?(key)
|
189
250
|
cache.append(key, '')
|
190
251
|
true
|
191
|
-
rescue Memcached::NotStored
|
252
|
+
rescue ::Memcached::NotStored
|
192
253
|
false
|
193
254
|
end
|
194
255
|
|
195
256
|
def read(key)
|
196
257
|
cache.get(key, false)
|
197
|
-
rescue Memcached::NotFound
|
258
|
+
rescue ::Memcached::NotFound
|
259
|
+
nil
|
260
|
+
end
|
261
|
+
|
262
|
+
def write(body)
|
263
|
+
buf = StringIO.new
|
264
|
+
key, size = slurp(body){|part| buf.write(part) }
|
265
|
+
cache.set(key, buf.string, 0, false)
|
266
|
+
[key, size]
|
267
|
+
end
|
268
|
+
|
269
|
+
def purge(key)
|
270
|
+
cache.delete(key)
|
271
|
+
nil
|
272
|
+
rescue ::Memcached::NotFound
|
198
273
|
nil
|
199
274
|
end
|
275
|
+
end
|
276
|
+
|
277
|
+
MEMCACHE =
|
278
|
+
if defined?(::Memcached)
|
279
|
+
MemCached
|
280
|
+
else
|
281
|
+
MemCache
|
282
|
+
end
|
283
|
+
|
284
|
+
MEMCACHED = MEMCACHE
|
285
|
+
|
286
|
+
class GAEStore < EntityStore
|
287
|
+
attr_reader :cache
|
288
|
+
|
289
|
+
def initialize(options = {})
|
290
|
+
require 'rack/cache/appengine'
|
291
|
+
@cache = Rack::Cache::AppEngine::MemCache.new(options)
|
292
|
+
end
|
293
|
+
|
294
|
+
def exist?(key)
|
295
|
+
cache.contains?(key)
|
296
|
+
end
|
297
|
+
|
298
|
+
def read(key)
|
299
|
+
cache.get(key)
|
300
|
+
end
|
200
301
|
|
201
302
|
def open(key)
|
202
303
|
if data = read(key)
|
@@ -209,45 +310,24 @@ module Rack::Cache
|
|
209
310
|
def write(body)
|
210
311
|
buf = StringIO.new
|
211
312
|
key, size = slurp(body){|part| buf.write(part) }
|
212
|
-
cache.
|
313
|
+
cache.put(key, buf.string)
|
213
314
|
[key, size]
|
214
315
|
end
|
215
316
|
|
216
317
|
def purge(key)
|
217
318
|
cache.delete(key)
|
218
319
|
nil
|
219
|
-
rescue Memcached::NotFound
|
220
|
-
nil
|
221
320
|
end
|
222
321
|
|
223
|
-
extend Rack::Utils
|
224
|
-
|
225
|
-
# Create MemCache store for the given URI. The URI must specify
|
226
|
-
# a host and may specify a port, namespace, and options:
|
227
|
-
#
|
228
|
-
# memcached://example.com:11211/namespace?opt1=val1&opt2=val2
|
229
|
-
#
|
230
|
-
# Query parameter names and values are documented with the memcached
|
231
|
-
# library: http://tinyurl.com/4upqnd
|
232
322
|
def self.resolve(uri)
|
233
|
-
|
234
|
-
options = parse_query(uri.query)
|
235
|
-
options.keys.each do |key|
|
236
|
-
value =
|
237
|
-
case value = options.delete(key)
|
238
|
-
when 'true' ; true
|
239
|
-
when 'false' ; false
|
240
|
-
else value.to_sym
|
241
|
-
end
|
242
|
-
options[k.to_sym] = value
|
243
|
-
end
|
244
|
-
options[:namespace] = uri.path.sub(/^\//, '')
|
245
|
-
new server, options
|
323
|
+
self.new(:namespace => uri.host)
|
246
324
|
end
|
325
|
+
|
247
326
|
end
|
248
327
|
|
249
|
-
|
250
|
-
|
328
|
+
GAECACHE = GAEStore
|
329
|
+
GAE = GAEStore
|
330
|
+
|
251
331
|
end
|
252
332
|
|
253
333
|
end
|
data/lib/rack/cache/metastore.rb
CHANGED
@@ -259,8 +259,65 @@ module Rack::Cache
|
|
259
259
|
# Stores request/response pairs in memcached. Keys are not stored
|
260
260
|
# directly since memcached has a 250-byte limit on key names. Instead,
|
261
261
|
# the SHA1 hexdigest of the key is used.
|
262
|
-
class
|
262
|
+
class MemCacheBase < MetaStore
|
263
|
+
extend Rack::Utils
|
264
|
+
|
265
|
+
# The MemCache object used to communicated with the memcached
|
266
|
+
# daemon.
|
267
|
+
attr_reader :cache
|
268
|
+
|
269
|
+
# Create MemCache store for the given URI. The URI must specify
|
270
|
+
# a host and may specify a port, namespace, and options:
|
271
|
+
#
|
272
|
+
# memcached://example.com:11211/namespace?opt1=val1&opt2=val2
|
273
|
+
#
|
274
|
+
# Query parameter names and values are documented with the memcached
|
275
|
+
# library: http://tinyurl.com/4upqnd
|
276
|
+
def self.resolve(uri)
|
277
|
+
server = "#{uri.host}:#{uri.port || '11211'}"
|
278
|
+
options = parse_query(uri.query)
|
279
|
+
options.keys.each do |key|
|
280
|
+
value =
|
281
|
+
case value = options.delete(key)
|
282
|
+
when 'true' ; true
|
283
|
+
when 'false' ; false
|
284
|
+
else value.to_sym
|
285
|
+
end
|
286
|
+
options[k.to_sym] = value
|
287
|
+
end
|
288
|
+
options[:namespace] = uri.path.sub(/^\//, '')
|
289
|
+
new server, options
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
class MemCache < MemCacheBase
|
294
|
+
def initialize(server="localhost:11211", options={})
|
295
|
+
@cache =
|
296
|
+
if server.respond_to?(:stats)
|
297
|
+
server
|
298
|
+
else
|
299
|
+
require 'memcache'
|
300
|
+
::MemCache.new(server, options)
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
def read(key)
|
305
|
+
key = hexdigest(key)
|
306
|
+
cache.get(key) || []
|
307
|
+
end
|
308
|
+
|
309
|
+
def write(key, entries)
|
310
|
+
key = hexdigest(key)
|
311
|
+
cache.set(key, entries)
|
312
|
+
end
|
313
|
+
|
314
|
+
def purge(key)
|
315
|
+
cache.delete(hexdigest(key))
|
316
|
+
nil
|
317
|
+
end
|
318
|
+
end
|
263
319
|
|
320
|
+
class MemCached < MemCacheBase
|
264
321
|
# The Memcached instance used to communicated with the memcached
|
265
322
|
# daemon.
|
266
323
|
attr_reader :cache
|
@@ -294,35 +351,49 @@ module Rack::Cache
|
|
294
351
|
rescue Memcached::NotFound
|
295
352
|
nil
|
296
353
|
end
|
354
|
+
end
|
297
355
|
|
298
|
-
|
356
|
+
MEMCACHE =
|
357
|
+
if defined?(::Memcached)
|
358
|
+
MemCached
|
359
|
+
else
|
360
|
+
MemCache
|
361
|
+
end
|
362
|
+
MEMCACHED = MemCache
|
363
|
+
|
364
|
+
class GAEStore < MetaStore
|
365
|
+
attr_reader :cache
|
366
|
+
|
367
|
+
def initialize(options = {})
|
368
|
+
require 'rack/cache/appengine'
|
369
|
+
@cache = Rack::Cache::AppEngine::MemCache.new(options)
|
370
|
+
end
|
371
|
+
|
372
|
+
def read(key)
|
373
|
+
key = hexdigest(key)
|
374
|
+
cache.get(key) || []
|
375
|
+
end
|
376
|
+
|
377
|
+
def write(key, entries)
|
378
|
+
key = hexdigest(key)
|
379
|
+
cache.put(key, entries)
|
380
|
+
end
|
381
|
+
|
382
|
+
def purge(key)
|
383
|
+
key = hexdigest(key)
|
384
|
+
cache.delete(key)
|
385
|
+
nil
|
386
|
+
end
|
299
387
|
|
300
|
-
# Create MemCache store for the given URI. The URI must specify
|
301
|
-
# a host and may specify a port, namespace, and options:
|
302
|
-
#
|
303
|
-
# memcached://example.com:11211/namespace?opt1=val1&opt2=val2
|
304
|
-
#
|
305
|
-
# Query parameter names and values are documented with the memcached
|
306
|
-
# library: http://tinyurl.com/4upqnd
|
307
388
|
def self.resolve(uri)
|
308
|
-
|
309
|
-
options = parse_query(uri.query)
|
310
|
-
options.keys.each do |key|
|
311
|
-
value =
|
312
|
-
case value = options.delete(key)
|
313
|
-
when 'true' ; true
|
314
|
-
when 'false' ; false
|
315
|
-
else value.to_sym
|
316
|
-
end
|
317
|
-
options[k.to_sym] = value
|
318
|
-
end
|
319
|
-
options[:namespace] = uri.path.sub(/^\//, '')
|
320
|
-
new server, options
|
389
|
+
self.new(:namespace => uri.host)
|
321
390
|
end
|
391
|
+
|
322
392
|
end
|
323
393
|
|
324
|
-
|
325
|
-
|
394
|
+
GAECACHE = GAEStore
|
395
|
+
GAE = GAEStore
|
396
|
+
|
326
397
|
end
|
327
398
|
|
328
399
|
end
|
data/lib/rack/cache/options.rb
CHANGED
@@ -133,8 +133,8 @@ module Rack::Cache
|
|
133
133
|
'rack-cache.entitystore' => 'heap:/',
|
134
134
|
'rack-cache.default_ttl' => 0,
|
135
135
|
'rack-cache.private_headers' => ['Authorization', 'Cookie'],
|
136
|
-
'rack-cache.allow_reload' =>
|
137
|
-
'rack-cache.allow_revalidate' =>
|
136
|
+
'rack-cache.allow_reload' => false,
|
137
|
+
'rack-cache.allow_revalidate' => false
|
138
138
|
}
|
139
139
|
self.options = options
|
140
140
|
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 = '0.
|
7
|
-
s.date = '2009-
|
6
|
+
s.version = '0.5'
|
7
|
+
s.date = '2009-05-25'
|
8
8
|
|
9
9
|
s.description = "HTTP Caching for Rack"
|
10
10
|
s.summary = "HTTP Caching for Rack"
|
@@ -30,6 +30,7 @@ Gem::Specification.new do |s|
|
|
30
30
|
example/sinatra/app.rb
|
31
31
|
example/sinatra/views/index.erb
|
32
32
|
lib/rack/cache.rb
|
33
|
+
lib/rack/cache/appengine.rb
|
33
34
|
lib/rack/cache/cachecontrol.rb
|
34
35
|
lib/rack/cache/context.rb
|
35
36
|
lib/rack/cache/entitystore.rb
|
@@ -58,7 +59,7 @@ Gem::Specification.new do |s|
|
|
58
59
|
s.test_files = s.files.select {|path| path =~ /^test\/.*_test.rb/}
|
59
60
|
|
60
61
|
s.extra_rdoc_files = %w[README COPYING TODO CHANGES]
|
61
|
-
s.add_dependency 'rack', '
|
62
|
+
s.add_dependency 'rack', '>= 0.4'
|
62
63
|
|
63
64
|
s.has_rdoc = true
|
64
65
|
s.homepage = "http://tomayko.com/src/rack-cache/"
|
data/test/context_test.rb
CHANGED
@@ -124,7 +124,8 @@ describe 'Rack::Cache::Context' do
|
|
124
124
|
response.headers.should.include 'Age'
|
125
125
|
end
|
126
126
|
|
127
|
-
it 'reloads responses when cache hits but no-cache request directive present'
|
127
|
+
it 'reloads responses when cache hits but no-cache request directive present ' +
|
128
|
+
'when allow_reload is set true' do
|
128
129
|
count = 0
|
129
130
|
respond_with 200, 'Cache-Control' => 'max-age=10000' do |req,res|
|
130
131
|
count+= 1
|
@@ -141,14 +142,16 @@ describe 'Rack::Cache::Context' do
|
|
141
142
|
response.body.should.equal 'Hello World'
|
142
143
|
cache.trace.should.include :fresh
|
143
144
|
|
144
|
-
get '/',
|
145
|
+
get '/',
|
146
|
+
'rack-cache.allow_reload' => true,
|
147
|
+
'HTTP_CACHE_CONTROL' => 'no-cache'
|
145
148
|
response.should.be.ok
|
146
149
|
response.body.should.equal 'Goodbye World'
|
147
150
|
cache.trace.should.include :reload
|
148
151
|
cache.trace.should.include :store
|
149
152
|
end
|
150
153
|
|
151
|
-
it 'does not reload responses when allow_reload is set false' do
|
154
|
+
it 'does not reload responses when allow_reload is set false (default)' do
|
152
155
|
count = 0
|
153
156
|
respond_with 200, 'Cache-Control' => 'max-age=10000' do |req,res|
|
154
157
|
count+= 1
|
@@ -171,9 +174,17 @@ describe 'Rack::Cache::Context' do
|
|
171
174
|
response.should.be.ok
|
172
175
|
response.body.should.equal 'Hello World'
|
173
176
|
cache.trace.should.not.include :reload
|
177
|
+
|
178
|
+
# test again without explicitly setting the allow_reload option to false
|
179
|
+
get '/',
|
180
|
+
'HTTP_CACHE_CONTROL' => 'no-cache'
|
181
|
+
response.should.be.ok
|
182
|
+
response.body.should.equal 'Hello World'
|
183
|
+
cache.trace.should.not.include :reload
|
174
184
|
end
|
175
185
|
|
176
|
-
it 'revalidates fresh cache entry when max-age request directive is exceeded'
|
186
|
+
it 'revalidates fresh cache entry when max-age request directive is exceeded ' +
|
187
|
+
'when allow_revalidate option is set true' do
|
177
188
|
count = 0
|
178
189
|
respond_with do |req,res|
|
179
190
|
count+= 1
|
@@ -192,7 +203,9 @@ describe 'Rack::Cache::Context' do
|
|
192
203
|
response.body.should.equal 'Hello World'
|
193
204
|
cache.trace.should.include :fresh
|
194
205
|
|
195
|
-
get '/',
|
206
|
+
get '/',
|
207
|
+
'rack-cache.allow_revalidate' => true,
|
208
|
+
'HTTP_CACHE_CONTROL' => 'max-age=0'
|
196
209
|
response.should.be.ok
|
197
210
|
response.body.should.equal 'Goodbye World'
|
198
211
|
cache.trace.should.include :stale
|
@@ -200,7 +213,7 @@ describe 'Rack::Cache::Context' do
|
|
200
213
|
cache.trace.should.include :store
|
201
214
|
end
|
202
215
|
|
203
|
-
it 'does not revalidate fresh cache entry when enable_revalidate option is set false' do
|
216
|
+
it 'does not revalidate fresh cache entry when enable_revalidate option is set false (default)' do
|
204
217
|
count = 0
|
205
218
|
respond_with do |req,res|
|
206
219
|
count+= 1
|
@@ -227,6 +240,15 @@ describe 'Rack::Cache::Context' do
|
|
227
240
|
cache.trace.should.not.include :stale
|
228
241
|
cache.trace.should.not.include :invalid
|
229
242
|
cache.trace.should.include :fresh
|
243
|
+
|
244
|
+
# test again without explicitly setting the allow_revalidate option to false
|
245
|
+
get '/',
|
246
|
+
'HTTP_CACHE_CONTROL' => 'max-age=0'
|
247
|
+
response.should.be.ok
|
248
|
+
response.body.should.equal 'Hello World'
|
249
|
+
cache.trace.should.not.include :stale
|
250
|
+
cache.trace.should.not.include :invalid
|
251
|
+
cache.trace.should.include :fresh
|
230
252
|
end
|
231
253
|
it 'fetches response from backend when cache misses' do
|
232
254
|
respond_with 200, 'Expires' => (Time.now + 5).httpdate
|
data/test/entitystore_test.rb
CHANGED
@@ -176,10 +176,51 @@ describe 'Rack::Cache::EntityStore' do
|
|
176
176
|
end
|
177
177
|
|
178
178
|
need_memcached 'entity store tests' do
|
179
|
+
describe 'MemCached' do
|
180
|
+
it_should_behave_like 'A Rack::Cache::EntityStore Implementation'
|
181
|
+
before do
|
182
|
+
@store = Rack::Cache::EntityStore::MemCached.new($memcached)
|
183
|
+
end
|
184
|
+
after do
|
185
|
+
@store = nil
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
|
191
|
+
need_memcache 'entity store tests' do
|
179
192
|
describe 'MemCache' do
|
180
193
|
it_should_behave_like 'A Rack::Cache::EntityStore Implementation'
|
181
194
|
before do
|
182
|
-
|
195
|
+
$memcache.flush_all
|
196
|
+
@store = Rack::Cache::EntityStore::MemCache.new($memcache)
|
197
|
+
end
|
198
|
+
after do
|
199
|
+
@store = nil
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
need_java 'entity store testing' do
|
205
|
+
module Rack::Cache::AppEngine
|
206
|
+
module MC
|
207
|
+
class << (Service = {})
|
208
|
+
|
209
|
+
def contains(key); include?(key); end
|
210
|
+
def get(key); self[key]; end;
|
211
|
+
def put(key, value, ttl = nil)
|
212
|
+
self[key] = value
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
describe 'GAEStore' do
|
220
|
+
it_should_behave_like 'A Rack::Cache::EntityStore Implementation'
|
221
|
+
before do
|
222
|
+
puts Rack::Cache::AppEngine::MC::Service.inspect
|
223
|
+
@store = Rack::Cache::EntityStore::GAEStore.new
|
183
224
|
end
|
184
225
|
after do
|
185
226
|
@store = nil
|
data/test/metastore_test.rb
CHANGED
@@ -250,14 +250,53 @@ describe 'Rack::Cache::MetaStore' do
|
|
250
250
|
end
|
251
251
|
|
252
252
|
need_memcached 'metastore tests' do
|
253
|
-
describe '
|
253
|
+
describe 'MemCached' do
|
254
254
|
it_should_behave_like 'A Rack::Cache::MetaStore Implementation'
|
255
255
|
before :each do
|
256
256
|
@temp_dir = create_temp_directory
|
257
257
|
$memcached.flush
|
258
|
-
@store = Rack::Cache::MetaStore::
|
258
|
+
@store = Rack::Cache::MetaStore::MemCached.new($memcached)
|
259
259
|
@entity_store = Rack::Cache::EntityStore::Heap.new
|
260
260
|
end
|
261
261
|
end
|
262
262
|
end
|
263
|
+
|
264
|
+
need_memcache 'metastore tests' do
|
265
|
+
describe 'MemCache' do
|
266
|
+
it_should_behave_like 'A Rack::Cache::MetaStore Implementation'
|
267
|
+
before :each do
|
268
|
+
@temp_dir = create_temp_directory
|
269
|
+
$memcache.flush_all
|
270
|
+
@store = Rack::Cache::MetaStore::MemCache.new($memcache)
|
271
|
+
@entity_store = Rack::Cache::EntityStore::Heap.new
|
272
|
+
end
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
need_java 'entity store testing' do
|
277
|
+
module Rack::Cache::AppEngine
|
278
|
+
module MC
|
279
|
+
class << (Service = {})
|
280
|
+
|
281
|
+
def contains(key); include?(key); end
|
282
|
+
def get(key); self[key]; end;
|
283
|
+
def put(key, value, ttl = nil)
|
284
|
+
self[key] = value
|
285
|
+
end
|
286
|
+
|
287
|
+
end
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
describe 'GAEStore' do
|
292
|
+
it_should_behave_like 'A Rack::Cache::MetaStore Implementation'
|
293
|
+
before :each do
|
294
|
+
Rack::Cache::AppEngine::MC::Service.clear
|
295
|
+
@store = Rack::Cache::MetaStore::GAEStore.new
|
296
|
+
@entity_store = Rack::Cache::EntityStore::Heap.new
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
end
|
301
|
+
|
263
302
|
end
|
data/test/spec_setup.rb
CHANGED
@@ -15,6 +15,7 @@ end
|
|
15
15
|
# of the MemCached meta and entity stores.
|
16
16
|
ENV['MEMCACHED'] ||= 'localhost:11215'
|
17
17
|
$memcached = nil
|
18
|
+
$memcache = nil
|
18
19
|
|
19
20
|
def have_memcached?(server=ENV['MEMCACHED'])
|
20
21
|
return $memcached unless $memcached.nil?
|
@@ -35,14 +36,49 @@ end
|
|
35
36
|
|
36
37
|
have_memcached?
|
37
38
|
|
39
|
+
def have_memcache?(server=ENV['MEMCACHED'])
|
40
|
+
return $memcache unless $memcache.nil?
|
41
|
+
require 'memcache'
|
42
|
+
$memcache = MemCache.new(server)
|
43
|
+
$memcache.set('ping', '')
|
44
|
+
true
|
45
|
+
rescue LoadError => boom
|
46
|
+
$memcache = false
|
47
|
+
false
|
48
|
+
rescue => boom
|
49
|
+
STDERR.puts "memcache not working. related tests will be skipped."
|
50
|
+
$memcache = false
|
51
|
+
false
|
52
|
+
end
|
53
|
+
|
54
|
+
have_memcache?
|
55
|
+
|
38
56
|
def need_memcached(forwhat)
|
39
57
|
if have_memcached?
|
40
58
|
yield
|
41
59
|
else
|
42
|
-
STDERR.puts "skipping memcached #{forwhat}
|
60
|
+
STDERR.puts "skipping memcached #{forwhat}"
|
43
61
|
end
|
44
62
|
end
|
45
63
|
|
64
|
+
def need_memcache(forwhat)
|
65
|
+
if have_memcache?
|
66
|
+
yield
|
67
|
+
else
|
68
|
+
STDERR.puts "skipping memcache #{forwhat}"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def need_java(forwhat)
|
73
|
+
|
74
|
+
if RUBY_PLATFORM =~ /java/
|
75
|
+
yield
|
76
|
+
else
|
77
|
+
STDERR.puts "skipping app engine #{forwhat}"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
|
46
82
|
# Setup the load path ..
|
47
83
|
$LOAD_PATH.unshift File.dirname(File.dirname(__FILE__)) + '/lib'
|
48
84
|
$LOAD_PATH.unshift File.dirname(__FILE__)
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rack-cache
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: "0.
|
4
|
+
version: "0.5"
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ryan Tomayko
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-
|
12
|
+
date: 2009-05-25 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -18,7 +18,7 @@ dependencies:
|
|
18
18
|
version_requirement:
|
19
19
|
version_requirements: !ruby/object:Gem::Requirement
|
20
20
|
requirements:
|
21
|
-
- -
|
21
|
+
- - ">="
|
22
22
|
- !ruby/object:Gem::Version
|
23
23
|
version: "0.4"
|
24
24
|
version:
|
@@ -50,6 +50,7 @@ files:
|
|
50
50
|
- example/sinatra/app.rb
|
51
51
|
- example/sinatra/views/index.erb
|
52
52
|
- lib/rack/cache.rb
|
53
|
+
- lib/rack/cache/appengine.rb
|
53
54
|
- lib/rack/cache/cachecontrol.rb
|
54
55
|
- lib/rack/cache/context.rb
|
55
56
|
- lib/rack/cache/entitystore.rb
|