rack-cache 1.5.1 → 1.6.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,377 @@
1
+ require 'digest/sha1'
2
+
3
+ module Rack::Cache
4
+
5
+ # Entity stores are used to cache response bodies across requests. All
6
+ # Implementations are required to calculate a SHA checksum of the data written
7
+ # which becomes the response body's key.
8
+ class EntityStore
9
+
10
+ # Read body calculating the SHA1 checksum and size while
11
+ # yielding each chunk to the block. If the body responds to close,
12
+ # call it after iteration is complete. Return a two-tuple of the form:
13
+ # [ hexdigest, size ].
14
+ def slurp(body)
15
+ digest, size = Digest::SHA1.new, 0
16
+ body.each do |part|
17
+ size += bytesize(part)
18
+ digest << part
19
+ yield part
20
+ end
21
+ body.close if body.respond_to? :close
22
+ [digest.hexdigest, size]
23
+ end
24
+
25
+ if ''.respond_to?(:bytesize)
26
+ def bytesize(string); string.bytesize; end
27
+ else
28
+ def bytesize(string); string.size; end
29
+ end
30
+
31
+ private :slurp, :bytesize
32
+
33
+
34
+ # Stores entity bodies on the heap using a Hash object.
35
+ class Heap < EntityStore
36
+
37
+ # Create the store with the specified backing Hash.
38
+ def initialize(hash={})
39
+ @hash = hash
40
+ end
41
+
42
+ # Determine whether the response body with the specified key (SHA1)
43
+ # exists in the store.
44
+ def exist?(key)
45
+ @hash.include?(key)
46
+ end
47
+
48
+ # Return an object suitable for use as a Rack response body for the
49
+ # specified key.
50
+ def open(key)
51
+ (body = @hash[key]) && body.dup
52
+ end
53
+
54
+ # Read all data associated with the given key and return as a single
55
+ # String.
56
+ def read(key)
57
+ (body = @hash[key]) && body.join
58
+ end
59
+
60
+ # Write the Rack response body immediately and return the SHA1 key.
61
+ def write(body, ttl=nil)
62
+ buf = []
63
+ key, size = slurp(body) { |part| buf << part }
64
+ @hash[key] = buf
65
+ [key, size]
66
+ end
67
+
68
+ # Remove the body corresponding to key; return nil.
69
+ def purge(key)
70
+ @hash.delete(key)
71
+ nil
72
+ end
73
+
74
+ def self.resolve(uri)
75
+ new
76
+ end
77
+ end
78
+
79
+ HEAP = Heap
80
+ MEM = Heap
81
+
82
+ # Stores entity bodies on disk at the specified path.
83
+ class Disk < EntityStore
84
+
85
+ # Path where entities should be stored. This directory is
86
+ # created the first time the store is instansiated if it does not
87
+ # already exist.
88
+ attr_reader :root
89
+
90
+ def initialize(root)
91
+ @root = root
92
+ FileUtils.mkdir_p root, :mode => 0755
93
+ end
94
+
95
+ def exist?(key)
96
+ File.exist?(body_path(key))
97
+ end
98
+
99
+ def read(key)
100
+ File.open(body_path(key), 'rb') { |f| f.read }
101
+ rescue Errno::ENOENT
102
+ nil
103
+ end
104
+
105
+ class Body < ::File #:nodoc:
106
+ def each
107
+ while part = read(8192)
108
+ yield part
109
+ end
110
+ end
111
+ alias_method :to_path, :path
112
+ end
113
+
114
+ # Open the entity body and return an IO object. The IO object's
115
+ # each method is overridden to read 8K chunks instead of lines.
116
+ def open(key)
117
+ Body.open(body_path(key), 'rb')
118
+ rescue Errno::ENOENT
119
+ nil
120
+ end
121
+
122
+ def write(body, ttl=nil)
123
+ filename = ['buf', $$, Thread.current.object_id].join('-')
124
+ temp_file = storage_path(filename)
125
+ key, size =
126
+ File.open(temp_file, 'wb') { |dest|
127
+ slurp(body) { |part| dest.write(part) }
128
+ }
129
+
130
+ path = body_path(key)
131
+ if File.exist?(path)
132
+ File.unlink temp_file
133
+ else
134
+ FileUtils.mkdir_p File.dirname(path), :mode => 0755
135
+ FileUtils.mv temp_file, path
136
+ end
137
+ [key, size]
138
+ end
139
+
140
+ def purge(key)
141
+ File.unlink body_path(key)
142
+ nil
143
+ rescue Errno::ENOENT
144
+ nil
145
+ end
146
+
147
+ protected
148
+ def storage_path(stem)
149
+ File.join root, stem
150
+ end
151
+
152
+ def spread(key)
153
+ key = key.dup
154
+ key[2,0] = '/'
155
+ key
156
+ end
157
+
158
+ def body_path(key)
159
+ storage_path spread(key)
160
+ end
161
+
162
+ def self.resolve(uri)
163
+ path = File.expand_path(uri.opaque || uri.path)
164
+ new path
165
+ end
166
+ end
167
+
168
+ DISK = Disk
169
+ FILE = Disk
170
+
171
+ # Base class for memcached entity stores.
172
+ class MemCacheBase < EntityStore
173
+ # The underlying Memcached instance used to communicate with the
174
+ # memcached daemon.
175
+ attr_reader :cache
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
+ if uri.respond_to?(:scheme)
186
+ server = "#{uri.host}:#{uri.port || '11211'}"
187
+ options = parse_query(uri.query)
188
+ options.keys.each do |key|
189
+ value =
190
+ case value = options.delete(key)
191
+ when 'true' ; true
192
+ when 'false' ; false
193
+ else value.to_sym
194
+ end
195
+ options[key.to_sym] = value
196
+ end
197
+ options[:namespace] = uri.path.sub(/^\//, '')
198
+ new server, options
199
+ else
200
+ # if the object provided is not a URI, pass it straight through
201
+ # to the underlying implementation.
202
+ new uri
203
+ end
204
+ end
205
+ end
206
+
207
+ # Uses the Dalli ruby library. This is the default unless
208
+ # the memcached library has already been required.
209
+ class Dalli < MemCacheBase
210
+ def initialize(server="localhost:11211", options={})
211
+ @cache =
212
+ if server.respond_to?(:stats)
213
+ server
214
+ else
215
+ require 'dalli'
216
+ ::Dalli::Client.new(server, options)
217
+ end
218
+ end
219
+
220
+ def exist?(key)
221
+ !cache.get(key).nil?
222
+ end
223
+
224
+ def read(key)
225
+ data = cache.get(key)
226
+ data.force_encoding('BINARY') if data.respond_to?(:force_encoding)
227
+ data
228
+ end
229
+
230
+ def write(body, ttl=nil)
231
+ buf = StringIO.new
232
+ key, size = slurp(body){|part| buf.write(part) }
233
+ [key, size] if cache.set(key, buf.string, ttl)
234
+ end
235
+
236
+ def purge(key)
237
+ cache.delete(key)
238
+ nil
239
+ end
240
+ end
241
+
242
+ # Uses the memcached client library. The ruby based memcache-client is used
243
+ # in preference to this store unless the memcached library has already been
244
+ # required.
245
+ class MemCached < MemCacheBase
246
+ def initialize(server="localhost:11211", options={})
247
+ options[:prefix_key] ||= options.delete(:namespace) if options.key?(:namespace)
248
+ @cache =
249
+ if server.respond_to?(:stats)
250
+ server
251
+ else
252
+ require 'memcached'
253
+ ::Memcached.new(server, options)
254
+ end
255
+ end
256
+
257
+ def exist?(key)
258
+ cache.append(key, '')
259
+ true
260
+ rescue ::Memcached::NotStored
261
+ false
262
+ end
263
+
264
+ def read(key)
265
+ cache.get(key, false)
266
+ rescue ::Memcached::NotFound
267
+ nil
268
+ end
269
+
270
+ def write(body, ttl=0)
271
+ buf = StringIO.new
272
+ key, size = slurp(body){|part| buf.write(part) }
273
+ cache.set(key, buf.string, ttl, false)
274
+ [key, size]
275
+ end
276
+
277
+ def purge(key)
278
+ cache.delete(key)
279
+ nil
280
+ rescue ::Memcached::NotFound
281
+ nil
282
+ end
283
+ end
284
+
285
+ MEMCACHE =
286
+ if defined?(::Memcached)
287
+ MemCached
288
+ else
289
+ Dalli
290
+ end
291
+
292
+ MEMCACHED = MEMCACHE
293
+
294
+ class GAEStore < EntityStore
295
+ attr_reader :cache
296
+
297
+ def initialize(options = {})
298
+ require 'rack/cache/app_engine'
299
+ @cache = Rack::Cache::AppEngine::MemCache.new(options)
300
+ end
301
+
302
+ def exist?(key)
303
+ cache.contains?(key)
304
+ end
305
+
306
+ def read(key)
307
+ cache.get(key)
308
+ end
309
+
310
+ def open(key)
311
+ if data = read(key)
312
+ [data]
313
+ else
314
+ nil
315
+ end
316
+ end
317
+
318
+ def write(body, ttl=nil)
319
+ buf = StringIO.new
320
+ key, size = slurp(body){|part| buf.write(part) }
321
+ cache.put(key, buf.string, ttl)
322
+ [key, size]
323
+ end
324
+
325
+ def purge(key)
326
+ cache.delete(key)
327
+ nil
328
+ end
329
+
330
+ def self.resolve(uri)
331
+ self.new(:namespace => uri.host)
332
+ end
333
+
334
+ end
335
+
336
+ GAECACHE = GAEStore
337
+ GAE = GAEStore
338
+
339
+ # Noop Entity Store backend.
340
+ #
341
+ # Set `entitystore` to 'noop:/'.
342
+ # Does not persist response bodies (no disk/memory used).
343
+ # Responses from the cache will have an empty body.
344
+ # Clients must ignore these empty cached response (check for X-Rack-Cache response header).
345
+ # Atm cannot handle streamed responses, patch needed.
346
+ #
347
+ class Noop < EntityStore
348
+ def exist?(key)
349
+ true
350
+ end
351
+
352
+ def read(key)
353
+ ''
354
+ end
355
+
356
+ def open(key)
357
+ []
358
+ end
359
+
360
+ def write(body, ttl=nil)
361
+ key, size = slurp(body) { |part| part }
362
+ [key, size]
363
+ end
364
+
365
+ def purge(key)
366
+ nil
367
+ end
368
+
369
+ def self.resolve(uri)
370
+ new
371
+ end
372
+ end
373
+
374
+ NOOP = Noop
375
+ end
376
+
377
+ end
@@ -1,341 +1,2 @@
1
- require 'digest/sha1'
2
-
3
- module Rack::Cache
4
-
5
- # Entity stores are used to cache response bodies across requests. All
6
- # Implementations are required to calculate a SHA checksum of the data written
7
- # which becomes the response body's key.
8
- class EntityStore
9
-
10
- # Read body calculating the SHA1 checksum and size while
11
- # yielding each chunk to the block. If the body responds to close,
12
- # call it after iteration is complete. Return a two-tuple of the form:
13
- # [ hexdigest, size ].
14
- def slurp(body)
15
- digest, size = Digest::SHA1.new, 0
16
- body.each do |part|
17
- size += bytesize(part)
18
- digest << part
19
- yield part
20
- end
21
- body.close if body.respond_to? :close
22
- [digest.hexdigest, size]
23
- end
24
-
25
- if ''.respond_to?(:bytesize)
26
- def bytesize(string); string.bytesize; end
27
- else
28
- def bytesize(string); string.size; end
29
- end
30
-
31
- private :slurp, :bytesize
32
-
33
-
34
- # Stores entity bodies on the heap using a Hash object.
35
- class Heap < EntityStore
36
-
37
- # Create the store with the specified backing Hash.
38
- def initialize(hash={})
39
- @hash = hash
40
- end
41
-
42
- # Determine whether the response body with the specified key (SHA1)
43
- # exists in the store.
44
- def exist?(key)
45
- @hash.include?(key)
46
- end
47
-
48
- # Return an object suitable for use as a Rack response body for the
49
- # specified key.
50
- def open(key)
51
- (body = @hash[key]) && body.dup
52
- end
53
-
54
- # Read all data associated with the given key and return as a single
55
- # String.
56
- def read(key)
57
- (body = @hash[key]) && body.join
58
- end
59
-
60
- # Write the Rack response body immediately and return the SHA1 key.
61
- def write(body, ttl=nil)
62
- buf = []
63
- key, size = slurp(body) { |part| buf << part }
64
- @hash[key] = buf
65
- [key, size]
66
- end
67
-
68
- # Remove the body corresponding to key; return nil.
69
- def purge(key)
70
- @hash.delete(key)
71
- nil
72
- end
73
-
74
- def self.resolve(uri)
75
- new
76
- end
77
- end
78
-
79
- HEAP = Heap
80
- MEM = Heap
81
-
82
- # Stores entity bodies on disk at the specified path.
83
- class Disk < EntityStore
84
-
85
- # Path where entities should be stored. This directory is
86
- # created the first time the store is instansiated if it does not
87
- # already exist.
88
- attr_reader :root
89
-
90
- def initialize(root)
91
- @root = root
92
- FileUtils.mkdir_p root, :mode => 0755
93
- end
94
-
95
- def exist?(key)
96
- File.exist?(body_path(key))
97
- end
98
-
99
- def read(key)
100
- File.open(body_path(key), 'rb') { |f| f.read }
101
- rescue Errno::ENOENT
102
- nil
103
- end
104
-
105
- class Body < ::File #:nodoc:
106
- def each
107
- while part = read(8192)
108
- yield part
109
- end
110
- end
111
- alias_method :to_path, :path
112
- end
113
-
114
- # Open the entity body and return an IO object. The IO object's
115
- # each method is overridden to read 8K chunks instead of lines.
116
- def open(key)
117
- Body.open(body_path(key), 'rb')
118
- rescue Errno::ENOENT
119
- nil
120
- end
121
-
122
- def write(body, ttl=nil)
123
- filename = ['buf', $$, Thread.current.object_id].join('-')
124
- temp_file = storage_path(filename)
125
- key, size =
126
- File.open(temp_file, 'wb') { |dest|
127
- slurp(body) { |part| dest.write(part) }
128
- }
129
-
130
- path = body_path(key)
131
- if File.exist?(path)
132
- File.unlink temp_file
133
- else
134
- FileUtils.mkdir_p File.dirname(path), :mode => 0755
135
- FileUtils.mv temp_file, path
136
- end
137
- [key, size]
138
- end
139
-
140
- def purge(key)
141
- File.unlink body_path(key)
142
- nil
143
- rescue Errno::ENOENT
144
- nil
145
- end
146
-
147
- protected
148
- def storage_path(stem)
149
- File.join root, stem
150
- end
151
-
152
- def spread(key)
153
- key = key.dup
154
- key[2,0] = '/'
155
- key
156
- end
157
-
158
- def body_path(key)
159
- storage_path spread(key)
160
- end
161
-
162
- def self.resolve(uri)
163
- path = File.expand_path(uri.opaque || uri.path)
164
- new path
165
- end
166
- end
167
-
168
- DISK = Disk
169
- FILE = Disk
170
-
171
- # Base class for memcached entity stores.
172
- class MemCacheBase < EntityStore
173
- # The underlying Memcached instance used to communicate with the
174
- # memcached daemon.
175
- attr_reader :cache
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
- if uri.respond_to?(:scheme)
186
- server = "#{uri.host}:#{uri.port || '11211'}"
187
- options = parse_query(uri.query)
188
- options.keys.each do |key|
189
- value =
190
- case value = options.delete(key)
191
- when 'true' ; true
192
- when 'false' ; false
193
- else value.to_sym
194
- end
195
- options[key.to_sym] = value
196
- end
197
- options[:namespace] = uri.path.sub(/^\//, '')
198
- new server, options
199
- else
200
- # if the object provided is not a URI, pass it straight through
201
- # to the underlying implementation.
202
- new uri
203
- end
204
- end
205
- end
206
-
207
- # Uses the Dalli ruby library. This is the default unless
208
- # the memcached library has already been required.
209
- class Dalli < MemCacheBase
210
- def initialize(server="localhost:11211", options={})
211
- @cache =
212
- if server.respond_to?(:stats)
213
- server
214
- else
215
- require 'dalli'
216
- ::Dalli::Client.new(server, options)
217
- end
218
- end
219
-
220
- def exist?(key)
221
- !cache.get(key).nil?
222
- end
223
-
224
- def read(key)
225
- data = cache.get(key)
226
- data.force_encoding('BINARY') if data.respond_to?(:force_encoding)
227
- data
228
- end
229
-
230
- def write(body, ttl=nil)
231
- buf = StringIO.new
232
- key, size = slurp(body){|part| buf.write(part) }
233
- [key, size] if cache.set(key, buf.string, ttl)
234
- end
235
-
236
- def purge(key)
237
- cache.delete(key)
238
- nil
239
- end
240
- end
241
-
242
- # Uses the memcached client library. The ruby based memcache-client is used
243
- # in preference to this store unless the memcached library has already been
244
- # required.
245
- class MemCached < MemCacheBase
246
- def initialize(server="localhost:11211", options={})
247
- options[:prefix_key] ||= options.delete(:namespace) if options.key?(:namespace)
248
- @cache =
249
- if server.respond_to?(:stats)
250
- server
251
- else
252
- require 'memcached'
253
- ::Memcached.new(server, options)
254
- end
255
- end
256
-
257
- def exist?(key)
258
- cache.append(key, '')
259
- true
260
- rescue ::Memcached::NotStored
261
- false
262
- end
263
-
264
- def read(key)
265
- cache.get(key, false)
266
- rescue ::Memcached::NotFound
267
- nil
268
- end
269
-
270
- def write(body, ttl=0)
271
- buf = StringIO.new
272
- key, size = slurp(body){|part| buf.write(part) }
273
- cache.set(key, buf.string, ttl, false)
274
- [key, size]
275
- end
276
-
277
- def purge(key)
278
- cache.delete(key)
279
- nil
280
- rescue ::Memcached::NotFound
281
- nil
282
- end
283
- end
284
-
285
- MEMCACHE =
286
- if defined?(::Memcached)
287
- MemCached
288
- else
289
- Dalli
290
- end
291
-
292
- MEMCACHED = MEMCACHE
293
-
294
- class GAEStore < EntityStore
295
- attr_reader :cache
296
-
297
- def initialize(options = {})
298
- require 'rack/cache/appengine'
299
- @cache = Rack::Cache::AppEngine::MemCache.new(options)
300
- end
301
-
302
- def exist?(key)
303
- cache.contains?(key)
304
- end
305
-
306
- def read(key)
307
- cache.get(key)
308
- end
309
-
310
- def open(key)
311
- if data = read(key)
312
- [data]
313
- else
314
- nil
315
- end
316
- end
317
-
318
- def write(body, ttl=nil)
319
- buf = StringIO.new
320
- key, size = slurp(body){|part| buf.write(part) }
321
- cache.put(key, buf.string, ttl)
322
- [key, size]
323
- end
324
-
325
- def purge(key)
326
- cache.delete(key)
327
- nil
328
- end
329
-
330
- def self.resolve(uri)
331
- self.new(:namespace => uri.host)
332
- end
333
-
334
- end
335
-
336
- GAECACHE = GAEStore
337
- GAE = GAEStore
338
-
339
- end
340
-
341
- end
1
+ warn "use require 'rack/cache/entity_store'"
2
+ require 'rack/cache/entity_store'