Skip to content

Commit

Permalink
Google AppEngine memcache entity store and metastore implementations
Browse files Browse the repository at this point in the history
To use GAE's memcache with rack-cache, set the :metastore and
:entitystore options as follows:

    use Rack::Cache,
      :metastore   => 'gae://cache-meta',
      :entitystore => 'gae://cache-body'

The 'cache-meta' and 'cache-body' parts are memcache namespace
prefixes and should be set to different values.
  • Loading branch information
Alexander A. Portnov authored and rtomayko committed May 11, 2009
1 parent 42f7266 commit 9afdb94
Show file tree
Hide file tree
Showing 7 changed files with 198 additions and 1 deletion.
52 changes: 52 additions & 0 deletions lib/rack/cache/appengine.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
require 'base64'

module Rack::Cache::AppEngine

module MC
require 'java'

import com.google.appengine.api.memcache.Expiration;
import com.google.appengine.api.memcache.MemcacheService;
import com.google.appengine.api.memcache.MemcacheServiceFactory;
import com.google.appengine.api.memcache.Stats;

Service = MemcacheServiceFactory.getMemcacheService
end unless defined?(Rack::Cache::AppEngine::MC)

class MemCache

def initialize(options = {})
@cache = MC::Service
@cache.namespace = options[:namespace] if options[:namespace]
end

def contains?(key)
MC::Service.contains(key)
end

def get(key)
value = MC::Service.get(key)
Marshal.load(Base64.decode64(value)) if value
end

def put(key, value, ttl = nil)
expiration = ttl ? MC::Expiration.byDeltaSeconds(ttl) : nil
value = Base64.encode64(Marshal.dump(value)).gsub(/\n/, '')
MC::Service.put(key, value, expiration)
end

def namespace
MC::Service.getNamespace
end

def namespace=(value)
MC::Service.setNamespace(value.to_s)
end

def delete(key)
MC::Service.delete(key)
end

end

end
46 changes: 46 additions & 0 deletions lib/rack/cache/entitystore.rb
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,52 @@ def purge(key)
end

MEMCACHED = MEMCACHE

class GAEStore < EntityStore
attr_reader :cache

def initialize(options = {})
require 'rack/cache/appengine'
@cache = Rack::Cache::AppEngine::MemCache.new(options)
end

def exist?(key)
cache.contains?(key)
end

def read(key)
cache.get(key)
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) }
cache.put(key, buf.string)
[key, size]
end

def purge(key)
cache.delete(key)
nil
end

def self.resolve(uri)
self.new(:namespace => uri.host)
end

end

GAECACHE = GAEStore
GAE = GAEStore

end

end
34 changes: 34 additions & 0 deletions lib/rack/cache/metastore.rb
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,40 @@ def purge(key)
MemCache
end
MEMCACHED = MemCache

class GAEStore < MetaStore
attr_reader :cache

def initialize(options = {})
require 'rack/cache/appengine'
@cache = Rack::Cache::AppEngine::MemCache.new(options)
end

def read(key)
key = hexdigest(key)
cache.get(key) || []
end

def write(key, entries)
key = hexdigest(key)
cache.put(key, entries)
end

def purge(key)
key = hexdigest(key)
cache.delete(key)
nil
end

def self.resolve(uri)
self.new(:namespace => uri.host)
end

end

GAECACHE = GAEStore
GAE = GAEStore

end

end
3 changes: 2 additions & 1 deletion rack-cache.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Gem::Specification.new do |s|

s.name = 'rack-cache'
s.version = '0.4'
s.date = '2009-03-16'
s.date = '2009-04-28'

s.description = "HTTP Caching for Rack"
s.summary = "HTTP Caching for Rack"
Expand All @@ -30,6 +30,7 @@ Gem::Specification.new do |s|
example/sinatra/app.rb
example/sinatra/views/index.erb
lib/rack/cache.rb
lib/rack/cache/appengine.rb
lib/rack/cache/cachecontrol.rb
lib/rack/cache/context.rb
lib/rack/cache/entitystore.rb
Expand Down
27 changes: 27 additions & 0 deletions test/entitystore_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -200,4 +200,31 @@ def sha_like?
end
end
end

need_java 'entity store testing' do
module Rack::Cache::AppEngine
module MC
class << (Service = {})

def contains(key); include?(key); end
def get(key); self[key]; end;
def put(key, value, ttl = nil)
self[key] = value
end
end

end
end

describe 'GAEStore' do
it_should_behave_like 'A Rack::Cache::EntityStore Implementation'
before do
puts Rack::Cache::AppEngine::MC::Service.inspect
@store = Rack::Cache::EntityStore::GAEStore.new
end
after do
@store = nil
end
end
end
end
27 changes: 27 additions & 0 deletions test/metastore_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -272,4 +272,31 @@ def self.call(request); request.path_info.reverse end
end
end
end

need_java 'entity store testing' do
module Rack::Cache::AppEngine
module MC
class << (Service = {})

def contains(key); include?(key); end
def get(key); self[key]; end;
def put(key, value, ttl = nil)
self[key] = value
end

end
end
end

describe 'GAEStore' do
it_should_behave_like 'A Rack::Cache::MetaStore Implementation'
before :each do
Rack::Cache::AppEngine::MC::Service.clear
@store = Rack::Cache::MetaStore::GAEStore.new
@entity_store = Rack::Cache::EntityStore::Heap.new
end
end

end

end
10 changes: 10 additions & 0 deletions test/spec_setup.rb
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,16 @@ def need_memcache(forwhat)
end
end

def need_java(forwhat)

if RUBY_PLATFORM =~ /java/
yield
else
STDERR.puts "skipping app engine #{forwhat}"
end
end


# Setup the load path ..
$LOAD_PATH.unshift File.dirname(File.dirname(__FILE__)) + '/lib'
$LOAD_PATH.unshift File.dirname(__FILE__)
Expand Down

0 comments on commit 9afdb94

Please sign in to comment.