rack-cache 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of rack-cache might be problematic. Click here for more details.
- data/CHANGES +27 -0
- data/COPYING +18 -0
- data/README +96 -0
- data/Rakefile +144 -0
- data/TODO +40 -0
- data/doc/configuration.markdown +224 -0
- data/doc/events.dot +27 -0
- data/doc/faq.markdown +133 -0
- data/doc/index.markdown +113 -0
- data/doc/layout.html.erb +33 -0
- data/doc/license.markdown +24 -0
- data/doc/rack-cache.css +362 -0
- data/doc/storage.markdown +162 -0
- data/lib/rack/cache.rb +51 -0
- data/lib/rack/cache/config.rb +65 -0
- data/lib/rack/cache/config/busters.rb +16 -0
- data/lib/rack/cache/config/default.rb +134 -0
- data/lib/rack/cache/config/no-cache.rb +13 -0
- data/lib/rack/cache/context.rb +95 -0
- data/lib/rack/cache/core.rb +271 -0
- data/lib/rack/cache/entitystore.rb +224 -0
- data/lib/rack/cache/headers.rb +237 -0
- data/lib/rack/cache/metastore.rb +309 -0
- data/lib/rack/cache/options.rb +119 -0
- data/lib/rack/cache/request.rb +37 -0
- data/lib/rack/cache/response.rb +76 -0
- data/lib/rack/cache/storage.rb +50 -0
- data/lib/rack/utils/environment_headers.rb +78 -0
- data/rack-cache.gemspec +74 -0
- data/test/cache_test.rb +35 -0
- data/test/config_test.rb +66 -0
- data/test/context_test.rb +465 -0
- data/test/core_test.rb +84 -0
- data/test/entitystore_test.rb +176 -0
- data/test/environment_headers_test.rb +71 -0
- data/test/headers_test.rb +215 -0
- data/test/logging_test.rb +45 -0
- data/test/metastore_test.rb +210 -0
- data/test/options_test.rb +64 -0
- data/test/pony.jpg +0 -0
- data/test/response_test.rb +37 -0
- data/test/spec_setup.rb +189 -0
- data/test/storage_test.rb +94 -0
- metadata +120 -0
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'rack/cache/config'
|
2
|
+
require 'rack/cache/options'
|
3
|
+
require 'rack/cache/core'
|
4
|
+
require 'rack/cache/request'
|
5
|
+
require 'rack/cache/response'
|
6
|
+
require 'rack/cache/storage'
|
7
|
+
|
8
|
+
module Rack::Cache
|
9
|
+
# Implements Rack's middleware interface and provides the context for all
|
10
|
+
# cache logic. This class includes the Options, Config, and Core modules
|
11
|
+
# to provide much of its core functionality.
|
12
|
+
|
13
|
+
class Context
|
14
|
+
include Rack::Cache::Options
|
15
|
+
include Rack::Cache::Config
|
16
|
+
include Rack::Cache::Core
|
17
|
+
|
18
|
+
# The Rack application object immediately downstream.
|
19
|
+
attr_reader :backend
|
20
|
+
|
21
|
+
def initialize(backend, options={}, &block)
|
22
|
+
@errors = nil
|
23
|
+
@env = nil
|
24
|
+
@backend = backend
|
25
|
+
initialize_options options
|
26
|
+
initialize_core
|
27
|
+
initialize_config(&block)
|
28
|
+
end
|
29
|
+
|
30
|
+
# The call! method is invoked on the duplicate context instance.
|
31
|
+
# process_request is defined in Core.
|
32
|
+
alias_method :call!, :process_request
|
33
|
+
protected :call!
|
34
|
+
|
35
|
+
# The Rack call interface. The receiver acts as a prototype and runs each
|
36
|
+
# request in a duplicate object, unless the +rack.run_once+ variable is set
|
37
|
+
# in the environment.
|
38
|
+
def call(env)
|
39
|
+
if env['rack.run_once']
|
40
|
+
call! env
|
41
|
+
else
|
42
|
+
clone.call! env
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
public
|
47
|
+
# IO-like object that receives log, warning, and error messages;
|
48
|
+
# defaults to the rack.errors environment variable.
|
49
|
+
def errors
|
50
|
+
@errors || (@env && (@errors = @env['rack.errors'])) || STDERR
|
51
|
+
end
|
52
|
+
|
53
|
+
# Set the output stream for log messages, warnings, and errors.
|
54
|
+
def errors=(ioish)
|
55
|
+
fail "stream must respond to :write" if ! ioish.respond_to?(:write)
|
56
|
+
@errors = ioish
|
57
|
+
end
|
58
|
+
|
59
|
+
# The configured MetaStore instance. Changing the rack-cache.metastore
|
60
|
+
# environment variable effects the result of this method immediately.
|
61
|
+
def metastore
|
62
|
+
uri = options['rack-cache.metastore']
|
63
|
+
storage.resolve_metastore_uri(uri)
|
64
|
+
end
|
65
|
+
|
66
|
+
# The configured EntityStore instance. Changing the rack-cache.entitystore
|
67
|
+
# environment variable effects the result of this method immediately.
|
68
|
+
def entitystore
|
69
|
+
uri = options['rack-cache.entitystore']
|
70
|
+
storage.resolve_entitystore_uri(uri)
|
71
|
+
end
|
72
|
+
|
73
|
+
protected
|
74
|
+
# Write a log message to the errors stream. +level+ is a symbol
|
75
|
+
# such as :error, :warn, :info, or :trace.
|
76
|
+
def log(level, message=nil, *params)
|
77
|
+
errors.write("[cache] #{level}: #{message}\n" % params)
|
78
|
+
errors.flush
|
79
|
+
end
|
80
|
+
|
81
|
+
def info(*message, &bk)
|
82
|
+
log :info, *message, &bk
|
83
|
+
end
|
84
|
+
|
85
|
+
def warn(*message, &bk)
|
86
|
+
log :warn, *message, &bk
|
87
|
+
end
|
88
|
+
|
89
|
+
def trace(*message, &bk)
|
90
|
+
return unless verbose?
|
91
|
+
log :trace, *message, &bk
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
@@ -0,0 +1,271 @@
|
|
1
|
+
require 'rack/cache/request'
|
2
|
+
require 'rack/cache/response'
|
3
|
+
|
4
|
+
module Rack::Cache
|
5
|
+
# Raised when an attempt is made to transition to an event that can
|
6
|
+
# not be transitioned from the current event.
|
7
|
+
class IllegalTransition < Exception
|
8
|
+
end
|
9
|
+
|
10
|
+
# The core logic engine and state machine. When a request is received,
|
11
|
+
# the engine begins transitioning from state to state based on the
|
12
|
+
# advice given by events. Each transition performs some piece of core
|
13
|
+
# logic, calls out to an event handler, and then kicks off the next
|
14
|
+
# transition.
|
15
|
+
#
|
16
|
+
# Five objects of interest are made available during execution:
|
17
|
+
#
|
18
|
+
# * +original_request+ - The request as originally received. This object
|
19
|
+
# is never modified.
|
20
|
+
# * +request+ - The request that may eventually be sent downstream in
|
21
|
+
# case of pass or miss. This object defaults to the +original_request+
|
22
|
+
# but may be modified or replaced entirely.
|
23
|
+
# * +original_response+ - The response exactly as specified by the
|
24
|
+
# downstream application; +nil+ on cache hit.
|
25
|
+
# * +entry+ - The response loaded from cache or stored to cache. This
|
26
|
+
# object becomes +response+ if the cached response is valid.
|
27
|
+
# * +response+ - The response that will be delivered upstream after
|
28
|
+
# processing is complete. This object may be modified as necessary.
|
29
|
+
#
|
30
|
+
# These objects can be accessed and modified from within event handlers
|
31
|
+
# to perform various types of request/response manipulation.
|
32
|
+
module Core
|
33
|
+
|
34
|
+
# The request exactly as received. The object is an instance of the
|
35
|
+
# Rack::Cache::Request class, which includes many utility methods for
|
36
|
+
# inspecting the state of the request.
|
37
|
+
#
|
38
|
+
# This object cannot be modified. If the request requires modification
|
39
|
+
# before being delivered to the downstream application, use the
|
40
|
+
# #request object.
|
41
|
+
attr_reader :original_request
|
42
|
+
|
43
|
+
# The response exactly as received from the downstream application. The
|
44
|
+
# object is an instance of the Rack::Cache::Response class, which includes
|
45
|
+
# utility methods for inspecting the state of the response.
|
46
|
+
#
|
47
|
+
# The original response should not be modified. Use the #response object to
|
48
|
+
# access the response to be sent back upstream.
|
49
|
+
attr_reader :original_response
|
50
|
+
|
51
|
+
# A response object retrieved from cache, or the response that is to be
|
52
|
+
# saved to cache, or nil if no cached response was found. The object is
|
53
|
+
# an instance of the Rack::Cache::Response class.
|
54
|
+
attr_reader :entry
|
55
|
+
|
56
|
+
# The request that will be made downstream on the application. This
|
57
|
+
# defaults to the request exactly as received (#original_request). The
|
58
|
+
# object is an instance of the Rack::Cache::Request class, which includes
|
59
|
+
# utility methods for inspecting and modifying various aspects of the
|
60
|
+
# HTTP request.
|
61
|
+
attr_reader :request
|
62
|
+
|
63
|
+
# The response that will be sent upstream. Defaults to the response
|
64
|
+
# received from the downstream application (#original_response) but
|
65
|
+
# is set to the cached #entry when valid. In any case, the object
|
66
|
+
# is an instance of the Rack::Cache::Response class, which includes a
|
67
|
+
# variety of utility methods for inspecting and modifying the HTTP
|
68
|
+
# response.
|
69
|
+
attr_reader :response
|
70
|
+
|
71
|
+
# Has the given event been performed at any time during the
|
72
|
+
# request life-cycle? Useful for testing.
|
73
|
+
def performed?(event)
|
74
|
+
@triggered.include?(event)
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
# Event handlers.
|
79
|
+
attr_reader :events
|
80
|
+
|
81
|
+
public
|
82
|
+
# Attach custom logic to one or more events.
|
83
|
+
def on(*events, &block)
|
84
|
+
events.each { |event| @events[event].unshift(block) }
|
85
|
+
nil
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
# Transitioning statements
|
90
|
+
|
91
|
+
def pass! ; throw(:transition, [:pass]) ; end
|
92
|
+
def lookup! ; throw(:transition, [:lookup]) ; end
|
93
|
+
def store! ; throw(:transition, [:store]) ; end
|
94
|
+
def fetch! ; throw(:transition, [:fetch]) ; end
|
95
|
+
def persist! ; throw(:transition, [:persist]) ; end
|
96
|
+
def deliver! ; throw(:transition, [:deliver]) ; end
|
97
|
+
def finish! ; throw(:transition, [:finish]) ; end
|
98
|
+
|
99
|
+
def error!(code=500, headers={}, body=nil)
|
100
|
+
throw(:transition, [:error, code, headers, body])
|
101
|
+
end
|
102
|
+
|
103
|
+
private
|
104
|
+
# Determine if the response's Last-Modified date matches the
|
105
|
+
# If-Modified-Since value provided in the original request.
|
106
|
+
def not_modified?
|
107
|
+
response.last_modified_at?(original_request.if_modified_since)
|
108
|
+
end
|
109
|
+
|
110
|
+
# Delegate the request to the backend and create the response.
|
111
|
+
def fetch_from_backend
|
112
|
+
status, headers, body = backend.call(request.env)
|
113
|
+
response = Response.new(status, headers, body)
|
114
|
+
@response = response.dup
|
115
|
+
@original_response = response.freeze
|
116
|
+
end
|
117
|
+
|
118
|
+
private
|
119
|
+
def perform_receive
|
120
|
+
@original_request = Request.new(@env.dup.freeze)
|
121
|
+
@request = Request.new(@env)
|
122
|
+
info "%s %s", @original_request.request_method, @original_request.fullpath
|
123
|
+
transition(from=:receive, to=[:pass, :lookup, :error])
|
124
|
+
end
|
125
|
+
|
126
|
+
def perform_pass
|
127
|
+
trace 'passing'
|
128
|
+
fetch_from_backend
|
129
|
+
transition(from=:pass, to=[:pass, :finish, :error]) do |event|
|
130
|
+
if event == :pass
|
131
|
+
:finish
|
132
|
+
else
|
133
|
+
event
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def perform_error(code=500, headers={}, body=nil)
|
139
|
+
body, headers = headers, {} unless headers.is_a?(Hash)
|
140
|
+
headers = {} if headers.nil?
|
141
|
+
body = [] if body.nil? || body == ''
|
142
|
+
@response = Rack::Cache::Response.new(code, headers, body)
|
143
|
+
transition(from=:error, to=[:finish])
|
144
|
+
end
|
145
|
+
|
146
|
+
def perform_lookup
|
147
|
+
if @entry = metastore.lookup(original_request, entitystore)
|
148
|
+
if @entry.fresh?
|
149
|
+
trace 'cache hit (ttl: %ds)', @entry.ttl
|
150
|
+
transition(from=:hit, to=[:deliver, :pass, :error]) do |event|
|
151
|
+
@response = @entry if event == :deliver
|
152
|
+
event
|
153
|
+
end
|
154
|
+
else
|
155
|
+
trace 'cache stale (ttl: %ds), validating...', @entry.ttl
|
156
|
+
perform_validate
|
157
|
+
end
|
158
|
+
else
|
159
|
+
trace 'cache miss'
|
160
|
+
transition(from=:miss, to=[:fetch, :pass, :error])
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def perform_validate
|
165
|
+
# add our cached validators to the backend request
|
166
|
+
request.headers['If-Modified-Since'] = entry.last_modified
|
167
|
+
request.headers['If-None-Match'] = entry.etag
|
168
|
+
fetch_from_backend
|
169
|
+
|
170
|
+
if original_response.status == 304
|
171
|
+
trace "cache entry valid"
|
172
|
+
@response = entry.dup
|
173
|
+
@response.headers.delete('Age')
|
174
|
+
@response.headers['X-Origin-Status'] = '304'
|
175
|
+
%w[Date Expires Cache-Control Etag Last-Modified].each do |name|
|
176
|
+
next unless value = original_response.headers[name]
|
177
|
+
@response[name] = value
|
178
|
+
end
|
179
|
+
@response.activate!
|
180
|
+
else
|
181
|
+
trace "cache entry invalid"
|
182
|
+
@entry = nil
|
183
|
+
end
|
184
|
+
transition(from=:fetch, to=[:store, :deliver, :error])
|
185
|
+
end
|
186
|
+
|
187
|
+
def perform_fetch
|
188
|
+
trace "fetching response from backend"
|
189
|
+
request.env.delete('HTTP_IF_MODIFIED_SINCE')
|
190
|
+
request.env.delete('HTTP_IF_NONE_MATCH')
|
191
|
+
fetch_from_backend
|
192
|
+
transition(from=:fetch, to=[:store, :deliver, :error])
|
193
|
+
end
|
194
|
+
|
195
|
+
def perform_store
|
196
|
+
@entry = @response
|
197
|
+
transition(from=:store, to=[:persist, :deliver, :error]) do |event|
|
198
|
+
if event == :persist
|
199
|
+
trace "writing response to cache"
|
200
|
+
metastore.store(original_request, @entry, entitystore)
|
201
|
+
@response = @entry
|
202
|
+
:deliver
|
203
|
+
else
|
204
|
+
event
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
def perform_deliver
|
210
|
+
trace "delivering response ..."
|
211
|
+
if not_modified?
|
212
|
+
response.status = 304
|
213
|
+
response.body = []
|
214
|
+
end
|
215
|
+
transition(from=:deliver, to=[:finish, :error])
|
216
|
+
end
|
217
|
+
|
218
|
+
def perform_finish
|
219
|
+
response.to_a
|
220
|
+
end
|
221
|
+
|
222
|
+
private
|
223
|
+
# Transition from the currently processing event to another event
|
224
|
+
# after triggering event handlers.
|
225
|
+
def transition(from, to)
|
226
|
+
ev, *args = trigger(from)
|
227
|
+
raise IllegalTransition, "No transition to :#{ev}" unless to.include?(ev)
|
228
|
+
ev = yield ev if block_given?
|
229
|
+
send "perform_#{ev}", *args
|
230
|
+
end
|
231
|
+
|
232
|
+
# Trigger processing of the event specified and return an array containing
|
233
|
+
# the name of the next transition and any arguments provided to the
|
234
|
+
# transitioning statement.
|
235
|
+
def trigger(event)
|
236
|
+
if @events.include? event
|
237
|
+
@triggered << event
|
238
|
+
catch(:transition) do
|
239
|
+
@events[event].each { |block| instance_eval(&block) }
|
240
|
+
nil
|
241
|
+
end
|
242
|
+
else
|
243
|
+
raise NameError, "No such event: #{event}"
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
private
|
248
|
+
# Setup the core prototype. The object's state after execution
|
249
|
+
# of this method will be duped and used for individual request.
|
250
|
+
def initialize_core
|
251
|
+
@triggered = []
|
252
|
+
@events = Hash.new { |h,k| h[k.to_sym] = [] }
|
253
|
+
|
254
|
+
# initialize some instance variables; we won't use them until we dup to
|
255
|
+
# process a request.
|
256
|
+
@request = nil
|
257
|
+
@response = nil
|
258
|
+
@original_request = nil
|
259
|
+
@original_response = nil
|
260
|
+
@entry = nil
|
261
|
+
end
|
262
|
+
|
263
|
+
# Process a request. This method is compatible with Rack's #call
|
264
|
+
# interface.
|
265
|
+
def process_request(env)
|
266
|
+
@triggered = []
|
267
|
+
@env = @default_options.merge(env)
|
268
|
+
perform_receive
|
269
|
+
end
|
270
|
+
end
|
271
|
+
end
|
@@ -0,0 +1,224 @@
|
|
1
|
+
require 'digest/sha1'
|
2
|
+
|
3
|
+
module Rack::Cache
|
4
|
+
# Entity stores are used to cache response bodies across requests. All
|
5
|
+
# Implementations are required to calculate a SHA checksum of the data written
|
6
|
+
# which becomes the response body's key.
|
7
|
+
class EntityStore
|
8
|
+
|
9
|
+
# Read body calculating the SHA1 checksum and size while
|
10
|
+
# yielding each chunk to the block. If the body responds to close,
|
11
|
+
# call it after iteration is complete. Return a two-tuple of the form:
|
12
|
+
# [ hexdigest, size ].
|
13
|
+
def slurp(body)
|
14
|
+
digest, size = Digest::SHA1.new, 0
|
15
|
+
body.each do |part|
|
16
|
+
size += part.length
|
17
|
+
digest << part
|
18
|
+
yield part
|
19
|
+
end
|
20
|
+
body.close if body.respond_to? :close
|
21
|
+
[ digest.hexdigest, size ]
|
22
|
+
end
|
23
|
+
|
24
|
+
private :slurp
|
25
|
+
|
26
|
+
|
27
|
+
# Stores entity bodies on the heap using a Hash object.
|
28
|
+
class Heap < EntityStore
|
29
|
+
|
30
|
+
# Create the store with the specified backing Hash.
|
31
|
+
def initialize(hash={})
|
32
|
+
@hash = hash
|
33
|
+
end
|
34
|
+
|
35
|
+
# Determine whether the response body with the specified key (SHA1)
|
36
|
+
# exists in the store.
|
37
|
+
def exist?(key)
|
38
|
+
@hash.include?(key)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Return an object suitable for use as a Rack response body for the
|
42
|
+
# specified key.
|
43
|
+
def open(key)
|
44
|
+
(body = @hash[key]) && body.dup
|
45
|
+
end
|
46
|
+
|
47
|
+
# Read all data associated with the given key and return as a single
|
48
|
+
# String.
|
49
|
+
def read(key)
|
50
|
+
(body = @hash[key]) && body.join
|
51
|
+
end
|
52
|
+
|
53
|
+
# Write the Rack response body immediately and return the SHA1 key.
|
54
|
+
def write(body)
|
55
|
+
buf = []
|
56
|
+
key, size = slurp(body) { |part| buf << part }
|
57
|
+
@hash[key] = buf
|
58
|
+
[key, size]
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.resolve(uri)
|
62
|
+
new
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
HEAP = Heap
|
67
|
+
MEM = Heap
|
68
|
+
|
69
|
+
# Stores entity bodies on disk at the specified path.
|
70
|
+
class Disk < EntityStore
|
71
|
+
|
72
|
+
# Path where entities should be stored. This directory is
|
73
|
+
# created the first time the store is instansiated if it does not
|
74
|
+
# already exist.
|
75
|
+
attr_reader :root
|
76
|
+
|
77
|
+
def initialize(root)
|
78
|
+
@root = root
|
79
|
+
FileUtils.mkdir_p root, :mode => 0755
|
80
|
+
end
|
81
|
+
|
82
|
+
def exist?(key)
|
83
|
+
File.exist?(body_path(key))
|
84
|
+
end
|
85
|
+
|
86
|
+
def read(key)
|
87
|
+
File.read(body_path(key))
|
88
|
+
rescue Errno::ENOENT
|
89
|
+
nil
|
90
|
+
end
|
91
|
+
|
92
|
+
# Open the entity body and return an IO object. The IO object's
|
93
|
+
# each method is overridden to read 8K chunks instead of lines.
|
94
|
+
def open(key)
|
95
|
+
io = File.open(body_path(key), 'rb')
|
96
|
+
def io.each
|
97
|
+
while part = read(8192)
|
98
|
+
yield part
|
99
|
+
end
|
100
|
+
end
|
101
|
+
io
|
102
|
+
rescue Errno::ENOENT
|
103
|
+
nil
|
104
|
+
end
|
105
|
+
|
106
|
+
def write(body)
|
107
|
+
filename = ['buf', $$, Thread.current.object_id].join('-')
|
108
|
+
temp_file = storage_path(filename)
|
109
|
+
key, size =
|
110
|
+
File.open(temp_file, 'wb') { |dest|
|
111
|
+
slurp(body) { |part| dest.write(part) }
|
112
|
+
}
|
113
|
+
|
114
|
+
path = body_path(key)
|
115
|
+
if File.exist?(path)
|
116
|
+
File.unlink temp_file
|
117
|
+
else
|
118
|
+
FileUtils.mkdir_p File.dirname(path), :mode => 0755
|
119
|
+
FileUtils.mv temp_file, path
|
120
|
+
end
|
121
|
+
[key, size]
|
122
|
+
end
|
123
|
+
|
124
|
+
protected
|
125
|
+
def storage_path(stem)
|
126
|
+
File.join root, stem
|
127
|
+
end
|
128
|
+
|
129
|
+
def spread(key)
|
130
|
+
key = key.dup
|
131
|
+
key[2,0] = '/'
|
132
|
+
key
|
133
|
+
end
|
134
|
+
|
135
|
+
def body_path(key)
|
136
|
+
storage_path spread(key)
|
137
|
+
end
|
138
|
+
|
139
|
+
def self.resolve(uri)
|
140
|
+
path = File.expand_path(uri.opaque || uri.path)
|
141
|
+
new path
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
DISK = Disk
|
146
|
+
FILE = Disk
|
147
|
+
|
148
|
+
# Stores entity bodies in memcached.
|
149
|
+
class MemCache < EntityStore
|
150
|
+
|
151
|
+
# The underlying Memcached instance used to communicate with the
|
152
|
+
# memcahced daemon.
|
153
|
+
attr_reader :cache
|
154
|
+
|
155
|
+
def initialize(server="localhost:11211", options={})
|
156
|
+
@cache =
|
157
|
+
if server.respond_to?(:stats)
|
158
|
+
server
|
159
|
+
else
|
160
|
+
require 'memcached'
|
161
|
+
Memcached.new(server, options)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def exist?(key)
|
166
|
+
cache.append(key, '')
|
167
|
+
true
|
168
|
+
rescue Memcached::NotStored
|
169
|
+
false
|
170
|
+
end
|
171
|
+
|
172
|
+
def read(key)
|
173
|
+
cache.get(key, false)
|
174
|
+
rescue Memcached::NotFound
|
175
|
+
nil
|
176
|
+
end
|
177
|
+
|
178
|
+
def open(key)
|
179
|
+
if data = read(key)
|
180
|
+
[data]
|
181
|
+
else
|
182
|
+
nil
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
def write(body)
|
187
|
+
buf = StringIO.new
|
188
|
+
key, size = slurp(body){|part| buf.write(part) }
|
189
|
+
cache.set(key, buf.string, 0, false)
|
190
|
+
[key, size]
|
191
|
+
end
|
192
|
+
|
193
|
+
extend Rack::Utils
|
194
|
+
|
195
|
+
# Create MemCache store for the given URI. The URI must specify
|
196
|
+
# a host and may specify a port, namespace, and options:
|
197
|
+
#
|
198
|
+
# memcached://example.com:11211/namespace?opt1=val1&opt2=val2
|
199
|
+
#
|
200
|
+
# Query parameter names and values are documented with the memcached
|
201
|
+
# library: http://tinyurl.com/4upqnd
|
202
|
+
def self.resolve(uri)
|
203
|
+
server = "#{uri.host}:#{uri.port || '11211'}"
|
204
|
+
options = parse_query(uri.query)
|
205
|
+
options.keys.each do |key|
|
206
|
+
value =
|
207
|
+
case value = options.delete(key)
|
208
|
+
when 'true' ; true
|
209
|
+
when 'false' ; false
|
210
|
+
else value.to_sym
|
211
|
+
end
|
212
|
+
options[k.to_sym] = value
|
213
|
+
end
|
214
|
+
options[:namespace] = uri.path.sub(/^\//, '')
|
215
|
+
new server, options
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
MEMCACHE = MemCache
|
220
|
+
MEMCACHED = MemCache
|
221
|
+
|
222
|
+
end
|
223
|
+
|
224
|
+
end
|