rack-cache 0.2.0
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 +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
|