instructure-redis-store 1.0.0.1.instructure1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|