rtomayko-rack-cache 0.4 → 0.5
Sign up to get free protection for your applications and to get access to all the features.
- 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: rtomayko-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
|