honkster-redis-store 0.3.10

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,32 @@
1
+ module Merb
2
+ # HACK for cyclic dependency: redis-store is required before Merb session stores
3
+ class Mash < Hash; end
4
+ class SessionContainer < Mash; class_inheritable_accessor :session_store_type end
5
+ class SessionStoreContainer < SessionContainer; end
6
+
7
+ class RedisSession < SessionStoreContainer
8
+ self.session_store_type = :redis
9
+ end
10
+
11
+ module RedisStore
12
+ def retrieve_session(session_id)
13
+ get("session:#{session_id}")
14
+ end
15
+
16
+ def store_session(session_id, data)
17
+ set("session:#{session_id}", data)
18
+ end
19
+
20
+ def delete_session(session_id)
21
+ delete("session:#{session_id}")
22
+ end
23
+ end
24
+ end
25
+
26
+ module Rack
27
+ module Session
28
+ class Redis
29
+ include Merb::RedisStore
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,81 @@
1
+ module Rack
2
+ module Session
3
+ class Redis < Abstract::ID
4
+ attr_reader :mutex, :pool
5
+ DEFAULT_OPTIONS = Abstract::ID::DEFAULT_OPTIONS.merge :redis_server => "localhost:6379"
6
+
7
+ def initialize(app, options = {})
8
+ super
9
+ @mutex = Mutex.new
10
+ @pool = RedisFactory.create options[:redis_server] || @default_options[:redis_server]
11
+ end
12
+
13
+ def generate_sid
14
+ loop do
15
+ sid = super
16
+ break sid unless @pool.marshalled_get(sid)
17
+ end
18
+ end
19
+
20
+ def get_session(env, sid)
21
+ session = @pool.marshalled_get(sid) if sid
22
+ @mutex.lock if env['rack.multithread']
23
+ unless sid and session
24
+ env['rack.errors'].puts("Session '#{sid.inspect}' not found, initializing...") if $VERBOSE and not sid.nil?
25
+ session = {}
26
+ sid = generate_sid
27
+ ret = @pool.marshalled_set sid, session
28
+ raise "Session collision on '#{sid.inspect}'" unless ret
29
+ end
30
+ session.instance_variable_set('@old', {}.merge(session))
31
+ return [sid, session]
32
+ rescue Errno::ECONNREFUSED
33
+ warn "#{self} is unable to find server."
34
+ warn $!.inspect
35
+ return [ nil, {} ]
36
+ ensure
37
+ @mutex.unlock if env['rack.multithread']
38
+ end
39
+
40
+ def set_session(env, session_id, new_session, options)
41
+ @mutex.lock if env['rack.multithread']
42
+ session = @pool.marshalled_get(session_id) rescue {}
43
+ if options[:renew] or options[:drop]
44
+ @pool.del session_id
45
+ return false if options[:drop]
46
+ session_id = generate_sid
47
+ @pool.marshalled_set session_id, 0
48
+ end
49
+ old_session = new_session.instance_variable_get('@old') || {}
50
+ session = merge_sessions session_id, old_session, new_session, session
51
+ @pool.marshalled_set session_id, session, options
52
+ return session_id
53
+ rescue Errno::ECONNREFUSED
54
+ warn "#{self} is unable to find server."
55
+ warn $!.inspect
56
+ return false
57
+ ensure
58
+ @mutex.unlock if env['rack.multithread']
59
+ end
60
+
61
+ private
62
+ def merge_sessions(sid, old, new, cur=nil)
63
+ cur ||= {}
64
+ unless Hash === old and Hash === new
65
+ warn 'Bad old or new sessions provided.'
66
+ return cur
67
+ end
68
+
69
+ delete = old.keys - new.keys
70
+ warn "//@#{sid}: dropping #{delete*','}" if $DEBUG and not delete.empty?
71
+ delete.each{|k| cur.del k }
72
+
73
+ update = new.keys.select{|k| new[k] != old[k] }
74
+ warn "//@#{sid}: updating #{update*','}" if $DEBUG and not update.empty?
75
+ update.each{|k| cur[k] = new[k] }
76
+
77
+ cur
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,16 @@
1
+ class DistributedMarshaledRedis < DistRedis
2
+ attr_reader :ring
3
+
4
+ def initialize(addresses)
5
+ nodes = addresses.map do |address|
6
+ MarshaledRedis.new address
7
+ end
8
+ @ring = Redis::HashRing.new nodes
9
+ end
10
+
11
+ def nodes
12
+ ring.nodes
13
+ end
14
+
15
+ alias_method :flushdb, :delete_cloud!
16
+ end
@@ -0,0 +1,52 @@
1
+ class MarshaledRedis < Redis::Client
2
+ def marshalled_set(key, val, options = nil)
3
+ val = marshal_value(val, options)
4
+ if expires_in = expires_in(options)
5
+ set_with_expire key, val, expires_in
6
+ else
7
+ set key, val
8
+ end
9
+ end
10
+
11
+ def marshalled_setnx(key, val, options = nil)
12
+ val = marshal_value(val, options)
13
+ if expires_in = expires_in(options)
14
+ setnx_with_expire key, val, expires_in
15
+ else
16
+ setnx key, val
17
+ end
18
+ end
19
+
20
+ def setnx_with_expire(key, value, ttl)
21
+ multi do
22
+ setnx(key, val)
23
+ expire(key, expires_in)
24
+ end
25
+ end
26
+
27
+ def marshalled_get(key, options = nil)
28
+ result = call_command([:get, key])
29
+ result = Marshal.load result if unmarshal?(result, options)
30
+ result
31
+ end
32
+
33
+ private
34
+ def marshal_value(val, options)
35
+ raw?(options) ? val : Marshal.dump(val)
36
+ end
37
+
38
+ def unmarshal?(result, options)
39
+ result && result.size > 0 && !raw?(options)
40
+ end
41
+
42
+ def raw?(options)
43
+ options && options[:raw]
44
+ end
45
+
46
+ def expires_in(options)
47
+ if options
48
+ # Rack::Session Merb Rails/Sinatra
49
+ options[:expire_after] || options[:expires_in] || options[:expire_in]
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,26 @@
1
+ class RedisFactory
2
+ class << self
3
+ def create(*redis_client_options)
4
+ redis_client_options = redis_client_options.flatten.compact.inject([]) do |result, address|
5
+ result << convert_to_redis_client_options(address)
6
+ result
7
+ end
8
+ if redis_client_options.size > 1
9
+ DistributedMarshaledRedis.new redis_client_options
10
+ else
11
+ MarshaledRedis.new redis_client_options.first || {}
12
+ end
13
+ end
14
+
15
+ def convert_to_redis_client_options(address_or_options)
16
+ return address_or_options if address_or_options.is_a?(Hash)
17
+ host, port = address_or_options.split /\:/
18
+ port, db = port.split /\// if port
19
+ options = {}
20
+ options[:host] = host if host
21
+ options[:port] = port if port
22
+ options[:db] = db.to_i if db
23
+ options
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,33 @@
1
+ require "redis"
2
+ require "redis/dist_redis"
3
+ require "redis/redis_factory"
4
+ require "redis/marshaled_redis"
5
+ require "redis/distributed_marshaled_redis"
6
+
7
+ # Cache store
8
+ if defined?(Sinatra)
9
+ require "cache/sinatra/redis_store"
10
+ elsif defined?(Merb)
11
+ # HACK for cyclic dependency: redis-store is required before merb-cache
12
+ module Merb; module Cache; class AbstractStore; end end end
13
+ require "cache/merb/redis_store"
14
+ elsif defined?(Rails)
15
+ require "cache/rails/redis_store"
16
+ require "cache/rails/redis_session_store"
17
+ end
18
+
19
+ # Rack::Session
20
+ if defined?(Rack::Session)
21
+ require "rack/session/abstract/id"
22
+ require "rack/session/redis"
23
+ if defined?(Merb)
24
+ require "rack/session/merb"
25
+ end
26
+ end
27
+
28
+ # Rack::Cache
29
+ if defined?(Rack::Cache)
30
+ require "rack/cache/key"
31
+ require "rack/cache/redis_metastore"
32
+ require "rack/cache/redis_entitystore"
33
+ end
@@ -0,0 +1,80 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{redis-store}
8
+ s.version = "0.3.7"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Luca Guidi"]
12
+ s.date = %q{2009-11-15}
13
+ s.description = %q{Rack::Session, Rack::Cache and cache Redis stores for Ruby web frameworks.}
14
+ s.email = %q{guidi.luca@gmail.com}
15
+ s.extra_rdoc_files = [
16
+ "README.md"
17
+ ]
18
+ s.files = [
19
+ ".gitignore",
20
+ "Gemfile",
21
+ "MIT-LICENSE",
22
+ "README.md",
23
+ "Rakefile",
24
+ "VERSION",
25
+ "lib/cache/merb/redis_store.rb",
26
+ "lib/cache/rails/redis_store.rb",
27
+ "lib/cache/sinatra/redis_store.rb",
28
+ "lib/rack/cache/redis_entitystore.rb",
29
+ "lib/rack/cache/redis_metastore.rb",
30
+ "lib/rack/session/merb.rb",
31
+ "lib/rack/session/redis.rb",
32
+ "lib/redis-store.rb",
33
+ "lib/redis/distributed_marshaled_redis.rb",
34
+ "lib/redis/marshaled_redis.rb",
35
+ "lib/redis/redis_factory.rb",
36
+ "redis-store.gemspec",
37
+ "spec/cache/merb/redis_store_spec.rb",
38
+ "spec/cache/rails/redis_store_spec.rb",
39
+ "spec/cache/sinatra/redis_store_spec.rb",
40
+ "spec/config/master.conf",
41
+ "spec/config/single.conf",
42
+ "spec/config/slave.conf",
43
+ "spec/rack/cache/entitystore/pony.jpg",
44
+ "spec/rack/cache/entitystore/redis_spec.rb",
45
+ "spec/rack/cache/metastore/redis_spec.rb",
46
+ "spec/rack/session/redis_spec.rb",
47
+ "spec/redis/distributed_marshaled_redis_spec.rb",
48
+ "spec/redis/marshaled_redis_spec.rb",
49
+ "spec/redis/redis_factory_spec.rb",
50
+ "spec/spec_helper.rb",
51
+ "tasks/redis.tasks.rb"
52
+ ]
53
+ s.homepage = %q{http://github.com/jodosha/redis-store}
54
+ s.rdoc_options = ["--charset=UTF-8"]
55
+ s.require_paths = ["lib"]
56
+ s.rubygems_version = %q{1.3.5}
57
+ s.summary = %q{Rack::Session, Rack::Cache and cache Redis stores for Ruby web frameworks.}
58
+ s.test_files = [
59
+ "spec/cache/merb/redis_store_spec.rb",
60
+ "spec/cache/rails/redis_store_spec.rb",
61
+ "spec/cache/sinatra/redis_store_spec.rb",
62
+ "spec/rack/cache/entitystore/redis_spec.rb",
63
+ "spec/rack/cache/metastore/redis_spec.rb",
64
+ "spec/rack/session/redis_spec.rb",
65
+ "spec/redis/distributed_marshaled_redis_spec.rb",
66
+ "spec/redis/marshaled_redis_spec.rb",
67
+ "spec/redis/redis_factory_spec.rb",
68
+ "spec/spec_helper.rb"
69
+ ]
70
+
71
+ if s.respond_to? :specification_version then
72
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
73
+ s.specification_version = 3
74
+
75
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
76
+ else
77
+ end
78
+ else
79
+ end
80
+ end
@@ -0,0 +1,140 @@
1
+ require File.join(File.dirname(__FILE__), "/../../spec_helper")
2
+
3
+ module Merb
4
+ module Cache
5
+ describe "Merb::Cache::RedisStore" do
6
+ before(:each) do
7
+ @store = Merb::Cache::RedisStore.new
8
+ @dstore = Merb::Cache::RedisStore.new :servers => ["localhost:6380/1", "localhost:6381/1"]
9
+ @rabbit = OpenStruct.new :name => "bunny"
10
+ @white_rabbit = OpenStruct.new :color => "white"
11
+ with_store_management do |store|
12
+ store.write "rabbit", @rabbit
13
+ store.delete "rub-a-dub"
14
+ end
15
+ end
16
+
17
+ it "should accept connection params" do
18
+ redis = instantiate_store
19
+ redis.to_s.should == "Redis Client connected to 127.0.0.1:6379 against DB 0"
20
+
21
+ redis = instantiate_store "localhost"
22
+ redis.to_s.should == "Redis Client connected to localhost:6379 against DB 0"
23
+
24
+ redis = instantiate_store "localhost:6380"
25
+ redis.to_s.should == "Redis Client connected to localhost:6380 against DB 0"
26
+
27
+ redis = instantiate_store "localhost:6380/13"
28
+ redis.to_s.should == "Redis Client connected to localhost:6380 against DB 13"
29
+ end
30
+
31
+ it "should instantiate a ring" do
32
+ store = instantiate_store
33
+ store.should be_kind_of(MarshaledRedis)
34
+ store = instantiate_store ["localhost:6379/0", "localhost:6379/1"]
35
+ store.should be_kind_of(DistributedMarshaledRedis)
36
+ end
37
+
38
+ it "should verify if writable" do
39
+ with_store_management do |store|
40
+ store.writable?("rabbit").should be_true
41
+ end
42
+ end
43
+
44
+ it "should read the data" do
45
+ with_store_management do |store|
46
+ store.read("rabbit").should === @rabbit
47
+ end
48
+ end
49
+
50
+ it "should read raw data" do
51
+ with_store_management do |store|
52
+ store.read("rabbit", {}, :raw => true).should == "\004\bU:\017OpenStruct{\006:\tname\"\nbunny"
53
+ end
54
+ end
55
+
56
+ it "should write the data" do
57
+ with_store_management do |store|
58
+ store.write "rabbit", @white_rabbit
59
+ store.read("rabbit").should === @white_rabbit
60
+ end
61
+ end
62
+
63
+ it "should write raw data" do
64
+ with_store_management do |store|
65
+ store.write "rabbit", @white_rabbit, {}, :raw => true
66
+ store.read("rabbit", {}, :raw => true).should == %(#<OpenStruct color="white">)
67
+ end
68
+ end
69
+
70
+ it "should write the data with expiration time" do
71
+ with_store_management do |store|
72
+ store.write "rabbit", @white_rabbit, {}, :expires_in => 1.second
73
+ store.read("rabbit").should === @white_rabbit ; sleep 2
74
+ store.read("rabbit").should be_nil
75
+ end
76
+ end
77
+
78
+ it "should not write data if :unless_exist option is true" do
79
+ with_store_management do |store|
80
+ store.write "rabbit", @white_rabbit, {}, :unless_exist => true
81
+ store.read("rabbit").should === @rabbit
82
+ end
83
+ end
84
+
85
+ it "should write all the data" do
86
+ with_store_management do |store|
87
+ store.write_all "rabbit", @white_rabbit
88
+ store.read("rabbit").should === @white_rabbit
89
+ end
90
+ end
91
+
92
+ it "should fetch data" do
93
+ with_store_management do |store|
94
+ store.fetch("rabbit").should == @rabbit
95
+ store.fetch("rub-a-dub").should be_nil
96
+ store.fetch("rub-a-dub") { "Flora de Cana" }
97
+ store.fetch("rub-a-dub").should === "Flora de Cana"
98
+ end
99
+ end
100
+
101
+ it "should verify existence" do
102
+ with_store_management do |store|
103
+ store.exists?("rabbit").should be_true
104
+ store.exists?("rab-a-dub").should be_false
105
+ end
106
+ end
107
+
108
+ it "should delete data" do
109
+ with_store_management do |store|
110
+ store.delete "rabbit"
111
+ store.read("rabbit").should be_nil
112
+ end
113
+ end
114
+
115
+ it "should delete all the data" do
116
+ with_store_management do |store|
117
+ store.delete_all
118
+ store.instance_variable_get(:@data).keys("*").flatten.should be_empty
119
+ end
120
+ end
121
+
122
+ it "should delete all the data with bang method" do
123
+ with_store_management do |store|
124
+ store.delete_all!
125
+ store.instance_variable_get(:@data).keys("*").flatten.should be_empty
126
+ end
127
+ end
128
+
129
+ private
130
+ def instantiate_store(addresses = nil)
131
+ Merb::Cache::RedisStore.new(:servers => [addresses].flatten).instance_variable_get(:@data)
132
+ end
133
+
134
+ def with_store_management
135
+ yield @store
136
+ yield @dstore
137
+ end
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,77 @@
1
+ require File.join(File.dirname(__FILE__), "/../../spec_helper")
2
+
3
+ module ActionController
4
+ module Session
5
+ describe "ActionController::Session::RedisSessionStore" do
6
+ attr_reader :app
7
+ before(:each) do
8
+ @app = Object.new
9
+ @store = ActionController::Session::RedisSessionStore.new(app)
10
+ @dstore = ActionController::Session::RedisSessionStore.new app, :servers => ["localhost:6380/1", "localhost:6381/1"]
11
+ @rabbit = OpenStruct.new :name => "bunny"
12
+ @white_rabbit = OpenStruct.new :color => "white"
13
+ with_store_management do |store|
14
+ class << store
15
+ attr_reader :pool
16
+ public :get_session, :set_session
17
+ end
18
+ store.set_session({'rack.session.options' => {}}, "rabbit", @rabbit)
19
+ store.pool.del "counter"
20
+ store.pool.del "rub-a-dub"
21
+ end
22
+ end
23
+
24
+ it "should accept connection params" do
25
+ redis = instantiate_store
26
+ redis.to_s.should == "Redis Client connected to 127.0.0.1:6379 against DB 0"
27
+
28
+ redis = instantiate_store :servers => "localhost"
29
+ redis.to_s.should == "Redis Client connected to localhost:6379 against DB 0"
30
+
31
+ redis = instantiate_store :servers => "localhost:6380"
32
+ redis.to_s.should == "Redis Client connected to localhost:6380 against DB 0"
33
+
34
+ redis = instantiate_store :servers => "localhost:6380/13"
35
+ redis.to_s.should == "Redis Client connected to localhost:6380 against DB 13"
36
+ end
37
+
38
+ it "should instantiate a ring" do
39
+ store = instantiate_store
40
+ store.should be_kind_of(MarshaledRedis)
41
+ store = instantiate_store :servers => ["localhost:6379/0", "localhost:6379/1"]
42
+ store.should be_kind_of(DistributedMarshaledRedis)
43
+ end
44
+
45
+ it "should read the data" do
46
+ with_store_management do |store|
47
+ store.get_session({}, "rabbit").should === ["rabbit", @rabbit]
48
+ end
49
+ end
50
+
51
+ it "should write the data" do
52
+ with_store_management do |store|
53
+ store.set_session({"rack.session.options" => {}}, "rabbit", @white_rabbit)
54
+ store.get_session({}, "rabbit").should === ["rabbit", @white_rabbit]
55
+ end
56
+ end
57
+
58
+ it "should write the data with expiration time" do
59
+ with_store_management do |store|
60
+ store.set_session({"rack.session.options" => {:expires_in => 1.second}}, "rabbit", @white_rabbit)
61
+ store.get_session({}, "rabbit").should === ["rabbit", @white_rabbit]; sleep 2
62
+ store.get_session({}, "rabbit").should === ["rabbit", {}]
63
+ end
64
+ end
65
+
66
+ private
67
+ def instantiate_store(params={})
68
+ ActionController::Session::RedisSessionStore.new(app, params).instance_variable_get(:@pool)
69
+ end
70
+
71
+ def with_store_management
72
+ yield @store
73
+ yield @dstore
74
+ end
75
+ end
76
+ end
77
+ end