rack-cache 1.5.1 → 1.6.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.
@@ -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'