Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Use memcache-client lib unless memcached already required

  • Loading branch information...
commit dc350d870bf9b4dbb9a4eb78b70248e731239693 1 parent 522dc31
Ryan Tomayko rtomayko authored
6 CHANGES
View
@@ -1,3 +1,9 @@
+## 0.5.0 / unreleased
+
+ * Added meta and entity store implementations based on the
+ memcache-client library. These are the default unless the memcached
+ library has already been required.
+
## 0.4.0 / March 2009
* Ruby 1.9.1 / Rack 1.0 compatible.
18 TODO
View
@@ -1,17 +1,19 @@
+## 0.5
+
+ - Work with both memcache and memcached gems (memcached hasn't built on
+ MacOS for some time now).
+ - Support multiple memcache servers.
+ - Explicit expiration/invalidation based on response headers or via an
+ object interface passed in the rack env.
+
## Backlog
- Move breakers.rb configuration file into rack-contrib as a middleware
component.
- Sample apps: Rack, Rails, Sinatra, Merb, etc.
- Use Bacon instead of test/spec
- - Work with both memcache and memcached gems (memcached hasn't built on MacOS
- for some time now).
- Fast path pass processing. We do a lot more than necessary just to determine
that the response should be passed through untouched.
- - Don't purge/remove cache entries when invalidating. The entries should be
- marked as stale and be forced revalidated on the next request instead of
- being removed entirely.
- - Add missing Expires header if we have a max-age.
- Purge/invalidate everything
- Invalidate at the URI of the Location or Content-Location response header
on POST, PUT, or DELETE that results in a redirect.
@@ -19,10 +21,6 @@
- Last-Modified factor: requests that have a Last-Modified header but no Expires
header have a TTL assigned based on the last modified age of the response:
TTL = (Age * Factor), or, 1h = (10h * 0.1)
- - Run under multiple-threads with an option to lock before making requests
- to the backend. The idea is to be able to serve requests from cache in
- separate threads. This should probably be implemented as a separate
- middleware component.
- Consider implementing ESI (http://www.w3.org/TR/esi-lang). This should
probably be implemented as a separate middleware component.
- Sqlite3 (meta store)
120 lib/rack/cache/entitystore.rb
View
@@ -19,7 +19,7 @@ def slurp(body)
yield part
end
body.close if body.respond_to? :close
- [ digest.hexdigest, size ]
+ [digest.hexdigest, size]
end
if ''.respond_to?(:bytesize)
@@ -168,44 +168,97 @@ def self.resolve(uri)
DISK = Disk
FILE = Disk
- # Stores entity bodies in memcached.
- class MemCache < EntityStore
-
+ # Base class for memcached entity stores.
+ class MemCacheBase < EntityStore
# The underlying Memcached instance used to communicate with the
- # memcahced daemon.
+ # memcached daemon.
attr_reader :cache
+ extend Rack::Utils
+
+ def open(key)
+ data = read(key)
+ data && [data]
+ end
+
+ def self.resolve(uri)
+ server = "#{uri.host}:#{uri.port || '11211'}"
+ options = parse_query(uri.query)
+ options.keys.each do |key|
+ value =
+ case value = options.delete(key)
+ when 'true' ; true
+ when 'false' ; false
+ else value.to_sym
+ end
+ options[k.to_sym] = value
+ end
+ options[:namespace] = uri.path.sub(/^\//, '')
+ new server, options
+ end
+ end
+
+ # Uses the memcache-client ruby library. This is the default unless
+ # the memcached library has already been required.
+ class MemCache < MemCacheBase
def initialize(server="localhost:11211", options={})
@cache =
if server.respond_to?(:stats)
server
else
+ require 'memcache'
+ ::MemCache.new(server, options)
+ end
+ end
+
+ def exist?(key)
+ !cache.get(key).nil?
+ end
+
+ def read(key)
+ cache.get(key)
+ end
+
+ def write(body)
+ buf = StringIO.new
+ key, size = slurp(body){|part| buf.write(part) }
+ [key, size] if cache.set(key, buf.string)
+ end
+
+ def purge(key)
+ cache.delete(key)
+ nil
+ end
+ end
+
+ # Uses the memcached client library. The ruby based memcache-client is used
+ # in preference to this store unless the memcached library has already been
+ # required.
+ class MemCached < MemCacheBase
+ def initialize(server="localhost:11211", options={})
+ options[:prefix_key] ||= options.delete(:namespace) if options.key?(:namespace)
+ @cache =
+ if server.respond_to?(:stats)
+ server
+ else
require 'memcached'
- Memcached.new(server, options)
+ ::Memcached.new(server, options)
end
end
def exist?(key)
cache.append(key, '')
true
- rescue Memcached::NotStored
+ rescue ::Memcached::NotStored
false
end
def read(key)
cache.get(key, false)
- rescue Memcached::NotFound
+ rescue ::Memcached::NotFound
nil
end
- def open(key)
- if data = read(key)
- [data]
- else
- nil
- end
- end
-
def write(body)
buf = StringIO.new
key, size = slurp(body){|part| buf.write(part) }
@@ -216,38 +269,19 @@ def write(body)
def purge(key)
cache.delete(key)
nil
- rescue Memcached::NotFound
+ rescue ::Memcached::NotFound
nil
end
+ end
- extend Rack::Utils
-
- # Create MemCache store for the given URI. The URI must specify
- # a host and may specify a port, namespace, and options:
- #
- # memcached://example.com:11211/namespace?opt1=val1&opt2=val2
- #
- # Query parameter names and values are documented with the memcached
- # library: http://tinyurl.com/4upqnd
- def self.resolve(uri)
- server = "#{uri.host}:#{uri.port || '11211'}"
- options = parse_query(uri.query)
- options.keys.each do |key|
- value =
- case value = options.delete(key)
- when 'true' ; true
- when 'false' ; false
- else value.to_sym
- end
- options[k.to_sym] = value
- end
- options[:namespace] = uri.path.sub(/^\//, '')
- new server, options
+ MEMCACHE =
+ if defined?(::Memcached)
+ MemCached
+ else
+ MemCache
end
- end
- MEMCACHE = MemCache
- MEMCACHED = MemCache
+ MEMCACHED = MEMCACHE
end
end
91 lib/rack/cache/metastore.rb
View
@@ -259,8 +259,65 @@ def self.resolve(uri)
# Stores request/response pairs in memcached. Keys are not stored
# directly since memcached has a 250-byte limit on key names. Instead,
# the SHA1 hexdigest of the key is used.
- class MemCache < MetaStore
+ class MemCacheBase < MetaStore
+ extend Rack::Utils
+ # The MemCache object used to communicated with the memcached
+ # daemon.
+ attr_reader :cache
+
+ # Create MemCache store for the given URI. The URI must specify
+ # a host and may specify a port, namespace, and options:
+ #
+ # memcached://example.com:11211/namespace?opt1=val1&opt2=val2
+ #
+ # Query parameter names and values are documented with the memcached
+ # library: http://tinyurl.com/4upqnd
+ def self.resolve(uri)
+ server = "#{uri.host}:#{uri.port || '11211'}"
+ options = parse_query(uri.query)
+ options.keys.each do |key|
+ value =
+ case value = options.delete(key)
+ when 'true' ; true
+ when 'false' ; false
+ else value.to_sym
+ end
+ options[k.to_sym] = value
+ end
+ options[:namespace] = uri.path.sub(/^\//, '')
+ new server, options
+ end
+ end
+
+ class MemCache < MemCacheBase
+ def initialize(server="localhost:11211", options={})
+ @cache =
+ if server.respond_to?(:stats)
+ server
+ else
+ require 'memcache'
+ ::MemCache.new(server, options)
+ end
+ end
+
+ def read(key)
+ key = hexdigest(key)
+ cache.get(key) || []
+ end
+
+ def write(key, entries)
+ key = hexdigest(key)
+ cache.set(key, entries)
+ end
+
+ def purge(key)
+ cache.delete(hexdigest(key))
+ nil
+ end
+ end
+
+ class MemCached < MemCacheBase
# The Memcached instance used to communicated with the memcached
# daemon.
attr_reader :cache
@@ -294,34 +351,14 @@ def purge(key)
rescue Memcached::NotFound
nil
end
-
- extend Rack::Utils
-
- # Create MemCache store for the given URI. The URI must specify
- # a host and may specify a port, namespace, and options:
- #
- # memcached://example.com:11211/namespace?opt1=val1&opt2=val2
- #
- # Query parameter names and values are documented with the memcached
- # library: http://tinyurl.com/4upqnd
- def self.resolve(uri)
- server = "#{uri.host}:#{uri.port || '11211'}"
- options = parse_query(uri.query)
- options.keys.each do |key|
- value =
- case value = options.delete(key)
- when 'true' ; true
- when 'false' ; false
- else value.to_sym
- end
- options[k.to_sym] = value
- end
- options[:namespace] = uri.path.sub(/^\//, '')
- new server, options
- end
end
- MEMCACHE = MemCache
+ MEMCACHE =
+ if defined?(::Memcached)
+ MemCached
+ else
+ MemCache
+ end
MEMCACHED = MemCache
end
16 test/entitystore_test.rb
View
@@ -176,10 +176,24 @@ def sha_like?
end
need_memcached 'entity store tests' do
+ describe 'MemCached' do
+ it_should_behave_like 'A Rack::Cache::EntityStore Implementation'
+ before do
+ @store = Rack::Cache::EntityStore::MemCached.new($memcached)
+ end
+ after do
+ @store = nil
+ end
+ end
+ end
+
+
+ need_memcache 'entity store tests' do
describe 'MemCache' do
it_should_behave_like 'A Rack::Cache::EntityStore Implementation'
before do
- @store = Rack::Cache::EntityStore::MemCache.new($memcached)
+ $memcache.flush_all
+ @store = Rack::Cache::EntityStore::MemCache.new($memcache)
end
after do
@store = nil
16 test/metastore_test.rb
View
@@ -250,12 +250,24 @@ def self.call(request); request.path_info.reverse end
end
need_memcached 'metastore tests' do
- describe 'MemCache' do
+ describe 'MemCached' do
it_should_behave_like 'A Rack::Cache::MetaStore Implementation'
before :each do
@temp_dir = create_temp_directory
$memcached.flush
- @store = Rack::Cache::MetaStore::MemCache.new($memcached)
+ @store = Rack::Cache::MetaStore::MemCached.new($memcached)
+ @entity_store = Rack::Cache::EntityStore::Heap.new
+ end
+ end
+ end
+
+ need_memcache 'metastore tests' do
+ describe 'MemCache' do
+ it_should_behave_like 'A Rack::Cache::MetaStore Implementation'
+ before :each do
+ @temp_dir = create_temp_directory
+ $memcache.flush_all
+ @store = Rack::Cache::MetaStore::MemCache.new($memcache)
@entity_store = Rack::Cache::EntityStore::Heap.new
end
end
28 test/spec_setup.rb
View
@@ -15,6 +15,7 @@
# of the MemCached meta and entity stores.
ENV['MEMCACHED'] ||= 'localhost:11215'
$memcached = nil
+$memcache = nil
def have_memcached?(server=ENV['MEMCACHED'])
return $memcached unless $memcached.nil?
@@ -35,11 +36,36 @@ def have_memcached?(server=ENV['MEMCACHED'])
have_memcached?
+def have_memcache?(server=ENV['MEMCACHED'])
+ return $memcache unless $memcache.nil?
+ require 'memcache'
+ $memcache = MemCache.new(server)
+ $memcache.set('ping', '')
+ true
+rescue LoadError => boom
+ $memcache = false
+ false
+rescue => boom
+ STDERR.puts "memcache not working. related tests will be skipped."
+ $memcache = false
+ false
+end
+
+have_memcache?
+
def need_memcached(forwhat)
if have_memcached?
yield
else
- STDERR.puts "skipping memcached #{forwhat} (MEMCACHED environment variable not set)"
+ STDERR.puts "skipping memcached #{forwhat}"
+ end
+end
+
+def need_memcache(forwhat)
+ if have_memcache?
+ yield
+ else
+ STDERR.puts "skipping memcache #{forwhat}"
end
end
Please sign in to comment.
Something went wrong with that request. Please try again.