instructure-redis-store 1.0.0.1.instructure1
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.
- data/.travis.yml +7 -0
- data/CHANGELOG +311 -0
- data/Gemfile +34 -0
- data/MIT-LICENSE +20 -0
- data/README.md +239 -0
- data/Rakefile +60 -0
- data/VERSION +1 -0
- data/lib/action_controller/session/redis_session_store.rb +81 -0
- data/lib/active_support/cache/redis_store.rb +254 -0
- data/lib/cache/merb/redis_store.rb +79 -0
- data/lib/cache/sinatra/redis_store.rb +131 -0
- data/lib/i18n/backend/redis.rb +67 -0
- data/lib/rack/cache/redis_entitystore.rb +48 -0
- data/lib/rack/cache/redis_metastore.rb +40 -0
- data/lib/rack/session/merb.rb +32 -0
- data/lib/rack/session/redis.rb +88 -0
- data/lib/redis-store.rb +45 -0
- data/lib/redis/distributed_store.rb +39 -0
- data/lib/redis/factory.rb +46 -0
- data/lib/redis/store.rb +39 -0
- data/lib/redis/store/interface.rb +17 -0
- data/lib/redis/store/marshalling.rb +51 -0
- data/lib/redis/store/namespace.rb +62 -0
- data/lib/redis/store/ttl.rb +37 -0
- data/lib/redis/store/version.rb +12 -0
- data/spec/action_controller/session/redis_session_store_spec.rb +126 -0
- data/spec/active_support/cache/redis_store_spec.rb +426 -0
- data/spec/cache/merb/redis_store_spec.rb +143 -0
- data/spec/cache/sinatra/redis_store_spec.rb +192 -0
- data/spec/config/node-one.conf +417 -0
- data/spec/config/node-two.conf +417 -0
- data/spec/config/redis.conf +417 -0
- data/spec/i18n/backend/redis_spec.rb +72 -0
- data/spec/rack/cache/entitystore/pony.jpg +0 -0
- data/spec/rack/cache/entitystore/redis_spec.rb +124 -0
- data/spec/rack/cache/metastore/redis_spec.rb +259 -0
- data/spec/rack/session/redis_spec.rb +234 -0
- data/spec/redis/distributed_store_spec.rb +55 -0
- data/spec/redis/factory_spec.rb +110 -0
- data/spec/redis/store/interface_spec.rb +23 -0
- data/spec/redis/store/marshalling_spec.rb +119 -0
- data/spec/redis/store/namespace_spec.rb +76 -0
- data/spec/redis/store/version_spec.rb +7 -0
- data/spec/redis/store_spec.rb +13 -0
- data/spec/spec_helper.rb +43 -0
- data/tasks/redis.tasks.rb +235 -0
- metadata +249 -0
data/Rakefile
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
$:.unshift 'lib'
|
2
|
+
require 'rubygems'
|
3
|
+
require 'rake'
|
4
|
+
require 'rake/testtask'
|
5
|
+
require 'rake/rdoctask'
|
6
|
+
require 'spec/rake/spectask'
|
7
|
+
|
8
|
+
task :default => "spec:suite"
|
9
|
+
|
10
|
+
begin
|
11
|
+
require "jeweler"
|
12
|
+
Jeweler::Tasks.new do |gemspec|
|
13
|
+
gemspec.name = "instructure-redis-store"
|
14
|
+
gemspec.summary = "Namespaced Rack::Session, Rack::Cache, I18n and cache Redis stores for Ruby web frameworks."
|
15
|
+
gemspec.description = "Namespaced Rack::Session, Rack::Cache, I18n and cache Redis stores for Ruby web frameworks."
|
16
|
+
gemspec.email = "brianp@instructure.com"
|
17
|
+
gemspec.homepage = "http://github.com/instructure/redis-store"
|
18
|
+
gemspec.authors = [ "Luca Guidi", "Brian Palmer" ]
|
19
|
+
gemspec.executables = [ ]
|
20
|
+
gemspec.add_dependency "redis", "3.0.1"
|
21
|
+
end
|
22
|
+
|
23
|
+
Jeweler::GemcutterTasks.new
|
24
|
+
rescue LoadError
|
25
|
+
puts "Jeweler not available. Install it with: sudo gem install jeweler"
|
26
|
+
end
|
27
|
+
|
28
|
+
namespace :spec do
|
29
|
+
desc "Run all the examples by starting a detached Redis instance"
|
30
|
+
task :suite => :prepare do
|
31
|
+
invoke_with_redis_replication "spec:run"
|
32
|
+
end
|
33
|
+
|
34
|
+
Spec::Rake::SpecTask.new(:run) do |t|
|
35
|
+
t.spec_files = FileList['spec/**/*_spec.rb']
|
36
|
+
t.spec_opts = %w(-fs --color)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
desc "Run all examples with RCov"
|
41
|
+
task :rcov => :prepare do
|
42
|
+
invoke_with_redis_replication "rcov_run"
|
43
|
+
end
|
44
|
+
|
45
|
+
Spec::Rake::SpecTask.new(:rcov_run) do |t|
|
46
|
+
t.spec_files = FileList['spec/**/*_spec.rb']
|
47
|
+
t.rcov = true
|
48
|
+
end
|
49
|
+
|
50
|
+
task :prepare do
|
51
|
+
`mkdir -p tmp && rm tmp/*.rdb`
|
52
|
+
end
|
53
|
+
|
54
|
+
namespace :bundle do
|
55
|
+
task :clean do
|
56
|
+
system "rm -rf ~/.bundle/ ~/.gem/ .bundle/ Gemfile.lock"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
load "tasks/redis.tasks.rb"
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.0.0.1.instructure1
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require "redis-store"
|
2
|
+
|
3
|
+
module RedisStore
|
4
|
+
module Rack
|
5
|
+
module Session
|
6
|
+
# Redis session storage for Rails, and for Rails only. Derived from
|
7
|
+
# the MemCacheStore code, simply dropping in Redis instead.
|
8
|
+
#
|
9
|
+
# Options:
|
10
|
+
# :key => Same as with the other cookie stores, key name
|
11
|
+
# :secret => Encryption secret for the key
|
12
|
+
# :host => Redis host name, default is localhost
|
13
|
+
# :port => Redis port, default is 6379
|
14
|
+
# :db => Database number, defaults to 0. Useful to separate your session storage from other data
|
15
|
+
# :key_prefix => Prefix for keys used in Redis, e.g. myapp-. Useful to separate session storage keys visibly from others
|
16
|
+
# :expire_after => A number in seconds to set the timeout interval for the session. Will map directly to expiry in Redis
|
17
|
+
module Rails
|
18
|
+
def initialize(app, options = {})
|
19
|
+
# Support old :expires option
|
20
|
+
options[:expire_after] ||= options[:expires]
|
21
|
+
|
22
|
+
super
|
23
|
+
|
24
|
+
options = options.dup
|
25
|
+
servers = [ options.delete(:servers) ].flatten.compact
|
26
|
+
servers = [ "redis://localhost:6379/0" ] if servers.empty?
|
27
|
+
servers.map! do |server|
|
28
|
+
server = Redis::Factory.convert_to_redis_client_options(server)
|
29
|
+
server.merge(options)
|
30
|
+
end
|
31
|
+
|
32
|
+
@pool = Redis::Factory.create(*servers)
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
def get_session(env, sid)
|
37
|
+
sid ||= generate_sid
|
38
|
+
begin
|
39
|
+
session = @pool.get(sid) || {}
|
40
|
+
rescue Errno::ECONNREFUSED
|
41
|
+
session = {}
|
42
|
+
end
|
43
|
+
[sid, session]
|
44
|
+
end
|
45
|
+
|
46
|
+
def set_session(env, sid, session_data, opts=nil)
|
47
|
+
options = env['rack.session.options']
|
48
|
+
@pool.set(sid, session_data, options)
|
49
|
+
return(::Redis::Store.rails3? ? sid : true)
|
50
|
+
rescue Errno::ECONNREFUSED
|
51
|
+
return false
|
52
|
+
end
|
53
|
+
|
54
|
+
def destroy(env)
|
55
|
+
if sid = current_session_id(env)
|
56
|
+
@pool.del(sid)
|
57
|
+
sid
|
58
|
+
end
|
59
|
+
rescue Errno::ECONNREFUSED
|
60
|
+
false
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
if ::Redis::Store.rails31?
|
67
|
+
require 'action_dispatch/middleware/session/abstract_store'
|
68
|
+
class ActionDispatch::Session::RedisSessionStore < Rack::Session::Redis
|
69
|
+
include ActionDispatch::Session::Compatibility
|
70
|
+
include ActionDispatch::Session::StaleSessionCheck
|
71
|
+
end
|
72
|
+
elsif ::Redis::Store.rails3?
|
73
|
+
class ActionDispatch::Session::RedisSessionStore < ActionDispatch::Session::AbstractStore
|
74
|
+
include RedisStore::Rack::Session::Rails
|
75
|
+
end
|
76
|
+
else
|
77
|
+
class ActionController::Session::RedisSessionStore < ActionController::Session::AbstractStore
|
78
|
+
include RedisStore::Rack::Session::Rails
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
@@ -0,0 +1,254 @@
|
|
1
|
+
require "redis-store"
|
2
|
+
|
3
|
+
module ::RedisStore
|
4
|
+
module Cache
|
5
|
+
module Rails2
|
6
|
+
# Instantiate the store.
|
7
|
+
#
|
8
|
+
# Example:
|
9
|
+
# RedisStore.new
|
10
|
+
# # => host: localhost, port: 6379, db: 0
|
11
|
+
#
|
12
|
+
# RedisStore.new "example.com"
|
13
|
+
# # => host: example.com, port: 6379, db: 0
|
14
|
+
#
|
15
|
+
# RedisStore.new "example.com:23682"
|
16
|
+
# # => host: example.com, port: 23682, db: 0
|
17
|
+
#
|
18
|
+
# RedisStore.new "example.com:23682/1"
|
19
|
+
# # => host: example.com, port: 23682, db: 1
|
20
|
+
#
|
21
|
+
# RedisStore.new "example.com:23682/1/theplaylist"
|
22
|
+
# # => host: example.com, port: 23682, db: 1, namespace: theplaylist
|
23
|
+
#
|
24
|
+
# RedisStore.new "localhost:6379/0", "localhost:6380/0"
|
25
|
+
# # => instantiate a cluster
|
26
|
+
def initialize(*addresses)
|
27
|
+
@data = ::Redis::Factory.create(addresses)
|
28
|
+
end
|
29
|
+
|
30
|
+
def write(key, value, options = nil)
|
31
|
+
super
|
32
|
+
method = options && options[:unless_exist] ? :setnx : :set
|
33
|
+
@data.send method, key, value, options
|
34
|
+
end
|
35
|
+
|
36
|
+
def read(key, options = nil)
|
37
|
+
super
|
38
|
+
@data.get key, options
|
39
|
+
end
|
40
|
+
|
41
|
+
def delete(key, options = nil)
|
42
|
+
super
|
43
|
+
@data.del key
|
44
|
+
end
|
45
|
+
|
46
|
+
def exist?(key, options = nil)
|
47
|
+
super
|
48
|
+
@data.exists key
|
49
|
+
end
|
50
|
+
|
51
|
+
# Delete objects for matched keys.
|
52
|
+
#
|
53
|
+
# Example:
|
54
|
+
# cache.del_matched "rab*"
|
55
|
+
def delete_matched(matcher, options = nil)
|
56
|
+
instrument(:delete_matched, matcher, options) do
|
57
|
+
!(keys = @data.keys(matcher)).empty? && @data.del(*keys)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
def instrument(operation, key, options = nil)
|
63
|
+
log(operation.to_s, key, options)
|
64
|
+
yield
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
module Rails3
|
69
|
+
# Instantiate the store.
|
70
|
+
#
|
71
|
+
# Example:
|
72
|
+
# RedisStore.new
|
73
|
+
# # => host: localhost, port: 6379, db: 0
|
74
|
+
#
|
75
|
+
# RedisStore.new "example.com"
|
76
|
+
# # => host: example.com, port: 6379, db: 0
|
77
|
+
#
|
78
|
+
# RedisStore.new "example.com:23682"
|
79
|
+
# # => host: example.com, port: 23682, db: 0
|
80
|
+
#
|
81
|
+
# RedisStore.new "example.com:23682/1"
|
82
|
+
# # => host: example.com, port: 23682, db: 1
|
83
|
+
#
|
84
|
+
# RedisStore.new "example.com:23682/1/theplaylist"
|
85
|
+
# # => host: example.com, port: 23682, db: 1, namespace: theplaylist
|
86
|
+
#
|
87
|
+
# RedisStore.new "localhost:6379/0", "localhost:6380/0"
|
88
|
+
# # => instantiate a cluster
|
89
|
+
def initialize(*addresses)
|
90
|
+
@data = ::Redis::Factory.create(addresses)
|
91
|
+
super(addresses.extract_options!)
|
92
|
+
end
|
93
|
+
|
94
|
+
# Delete objects for matched keys.
|
95
|
+
#
|
96
|
+
# Example:
|
97
|
+
# cache.del_matched "rab*"
|
98
|
+
def delete_matched(matcher, options = nil)
|
99
|
+
options = merged_options(options)
|
100
|
+
instrument(:delete_matched, matcher.inspect) do
|
101
|
+
matcher = key_matcher(matcher, options)
|
102
|
+
begin
|
103
|
+
!(keys = @data.keys(matcher)).empty? && @data.del(*keys)
|
104
|
+
rescue Errno::ECONNREFUSED => e
|
105
|
+
false
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
protected
|
111
|
+
def write_entry(key, entry, options)
|
112
|
+
method = options && options[:unless_exist] ? :setnx : :set
|
113
|
+
@data.send method, key, entry, options
|
114
|
+
rescue Errno::ECONNREFUSED => e
|
115
|
+
false
|
116
|
+
end
|
117
|
+
|
118
|
+
def read_entry(key, options)
|
119
|
+
entry = @data.get key, options
|
120
|
+
if entry
|
121
|
+
entry.is_a?(ActiveSupport::Cache::Entry) ? entry : ActiveSupport::Cache::Entry.new(entry)
|
122
|
+
end
|
123
|
+
rescue Errno::ECONNREFUSED => e
|
124
|
+
nil
|
125
|
+
end
|
126
|
+
|
127
|
+
##
|
128
|
+
# Implement the ActiveSupport::Cache#delete_entry
|
129
|
+
#
|
130
|
+
# It's really needed and use
|
131
|
+
#
|
132
|
+
def delete_entry(key, options)
|
133
|
+
@data.del key
|
134
|
+
rescue Errno::ECONNREFUSED => e
|
135
|
+
false
|
136
|
+
end
|
137
|
+
|
138
|
+
|
139
|
+
# Add the namespace defined in the options to a pattern designed to match keys.
|
140
|
+
#
|
141
|
+
# This implementation is __different__ than ActiveSupport:
|
142
|
+
# __it doesn't accept Regular expressions__, because the Redis matcher is designed
|
143
|
+
# only for strings with wildcards.
|
144
|
+
def key_matcher(pattern, options)
|
145
|
+
prefix = options[:namespace].is_a?(Proc) ? options[:namespace].call : options[:namespace]
|
146
|
+
if prefix
|
147
|
+
raise "Regexps aren't supported, please use string with wildcards." if pattern.is_a?(Regexp)
|
148
|
+
"#{prefix}:#{pattern}"
|
149
|
+
else
|
150
|
+
pattern
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
module Store
|
156
|
+
include ::Redis::Store.rails3? ? Rails3 : Rails2
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
module ActiveSupport
|
162
|
+
module Cache
|
163
|
+
class RedisStore < Store
|
164
|
+
include ::RedisStore::Cache::Store
|
165
|
+
|
166
|
+
# Reads multiple keys from the cache using a single call to the
|
167
|
+
# servers for all keys. Options can be passed in the last argument.
|
168
|
+
#
|
169
|
+
# Example:
|
170
|
+
# cache.read_multi "rabbit", "white-rabbit"
|
171
|
+
# cache.read_multi "rabbit", "white-rabbit", :raw => true
|
172
|
+
def read_multi(*names)
|
173
|
+
values = @data.mget(*names)
|
174
|
+
|
175
|
+
# Remove the options hash before mapping keys to values
|
176
|
+
names.extract_options!
|
177
|
+
|
178
|
+
result = Hash[names.zip(values)]
|
179
|
+
result.reject!{ |k,v| v.nil? }
|
180
|
+
result
|
181
|
+
end
|
182
|
+
|
183
|
+
# Increment a key in the store.
|
184
|
+
#
|
185
|
+
# If the key doesn't exist it will be initialized on 0.
|
186
|
+
# If the key exist but it isn't a Fixnum it will be initialized on 0.
|
187
|
+
#
|
188
|
+
# Example:
|
189
|
+
# We have two objects in cache:
|
190
|
+
# counter # => 23
|
191
|
+
# rabbit # => #<Rabbit:0x5eee6c>
|
192
|
+
#
|
193
|
+
# cache.increment "counter"
|
194
|
+
# cache.read "counter", :raw => true # => "24"
|
195
|
+
#
|
196
|
+
# cache.increment "counter", 6
|
197
|
+
# cache.read "counter", :raw => true # => "30"
|
198
|
+
#
|
199
|
+
# cache.increment "a counter"
|
200
|
+
# cache.read "a counter", :raw => true # => "1"
|
201
|
+
#
|
202
|
+
# cache.increment "rabbit"
|
203
|
+
# cache.read "rabbit", :raw => true # => "1"
|
204
|
+
def increment(key, amount = 1)
|
205
|
+
instrument(:increment, key, :amount => amount) do
|
206
|
+
@data.incrby key, amount
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
# Decrement a key in the store
|
211
|
+
#
|
212
|
+
# If the key doesn't exist it will be initialized on 0.
|
213
|
+
# If the key exist but it isn't a Fixnum it will be initialized on 0.
|
214
|
+
#
|
215
|
+
# Example:
|
216
|
+
# We have two objects in cache:
|
217
|
+
# counter # => 23
|
218
|
+
# rabbit # => #<Rabbit:0x5eee6c>
|
219
|
+
#
|
220
|
+
# cache.decrement "counter"
|
221
|
+
# cache.read "counter", :raw => true # => "22"
|
222
|
+
#
|
223
|
+
# cache.decrement "counter", 2
|
224
|
+
# cache.read "counter", :raw => true # => "20"
|
225
|
+
#
|
226
|
+
# cache.decrement "a counter"
|
227
|
+
# cache.read "a counter", :raw => true # => "-1"
|
228
|
+
#
|
229
|
+
# cache.decrement "rabbit"
|
230
|
+
# cache.read "rabbit", :raw => true # => "-1"
|
231
|
+
def decrement(key, amount = 1)
|
232
|
+
instrument(:decrement, key, :amount => amount) do
|
233
|
+
@data.decrby key, amount
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
# Clear all the data from the store.
|
238
|
+
def clear
|
239
|
+
instrument(:clear, nil, nil) do
|
240
|
+
@data.flushdb
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
def stats
|
245
|
+
@data.info
|
246
|
+
end
|
247
|
+
|
248
|
+
# Force client reconnection, useful Unicorn deployed apps.
|
249
|
+
def reconnect
|
250
|
+
@data.reconnect
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
254
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module Merb
|
2
|
+
module Cache
|
3
|
+
class RedisStore < AbstractStore
|
4
|
+
# Instantiate the store.
|
5
|
+
#
|
6
|
+
# Example:
|
7
|
+
# RedisStore.new
|
8
|
+
# # => host: localhost, port: 6379, db: 0
|
9
|
+
#
|
10
|
+
# RedisStore.new :servers => ["example.com"]
|
11
|
+
# # => host: example.com, port: 6379, db: 0
|
12
|
+
#
|
13
|
+
# RedisStore.new :servers => ["example.com:23682"]
|
14
|
+
# # => host: example.com, port: 23682, db: 0
|
15
|
+
#
|
16
|
+
# RedisStore.new :servers => ["example.com:23682/1"]
|
17
|
+
# # => host: example.com, port: 23682, db: 1
|
18
|
+
#
|
19
|
+
# RedisStore.new :servers => ["example.com:23682/1/theplaylist"]
|
20
|
+
# # => host: example.com, port: 23682, db: 1, namespace: theplaylist
|
21
|
+
#
|
22
|
+
# RedisStore.new :servers => ["localhost:6379/0", "localhost:6380/0"]
|
23
|
+
# # => instantiate a cluster
|
24
|
+
def initialize(config = { })
|
25
|
+
@data = Redis::Factory.create config[:servers]
|
26
|
+
end
|
27
|
+
|
28
|
+
def writable?(key, parameters = {}, conditions = {})
|
29
|
+
true
|
30
|
+
end
|
31
|
+
|
32
|
+
def read(key, parameters = {}, conditions = {})
|
33
|
+
@data.get normalize(key, parameters), conditions
|
34
|
+
end
|
35
|
+
|
36
|
+
def write(key, data = nil, parameters = {}, conditions = {})
|
37
|
+
if writable?(key, parameters, conditions)
|
38
|
+
method = conditions && conditions[:unless_exist] ? :setnx : :set
|
39
|
+
@data.send method, normalize(key, parameters), data, conditions
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def write_all(key, data = nil, parameters = {}, conditions = {})
|
44
|
+
write key, data, parameters, conditions
|
45
|
+
end
|
46
|
+
|
47
|
+
def fetch(key, parameters = {}, conditions = {}, &blk)
|
48
|
+
(data = read(key, parameters)) || block_given? && begin
|
49
|
+
data = yield
|
50
|
+
write(key, data, parameters, conditions)
|
51
|
+
end
|
52
|
+
data || nil
|
53
|
+
end
|
54
|
+
|
55
|
+
def exists?(key, parameters = {})
|
56
|
+
@data.exists normalize(key, parameters)
|
57
|
+
end
|
58
|
+
|
59
|
+
def delete(key, parameters = {})
|
60
|
+
@data.del normalize(key, parameters)
|
61
|
+
end
|
62
|
+
|
63
|
+
def delete_all
|
64
|
+
@data.flushdb
|
65
|
+
end
|
66
|
+
|
67
|
+
def delete_all!
|
68
|
+
delete_all
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
# Returns cache key calculated from base key
|
73
|
+
# and SHA2 hex from parameters.
|
74
|
+
def normalize(key, parameters = {})
|
75
|
+
parameters.empty? ? "#{key}" : "#{key}--#{parameters.to_sha2}"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|