Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AUTHORS.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ Redis Session Store authors

- Ben Marini
- Dan Buch
- Connor Mullen
- Donald Plummer
- Edwin Cruz
- Gonçalo Silva
Expand Down
4 changes: 3 additions & 1 deletion lib/redis-session-store.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class RedisSessionStore < ActionDispatch::Session::AbstractStore
# * +:url+ - Redis url, default is redis://localhost:6379/0
# * +:key_prefix+ - Prefix for keys used in Redis, e.g. +myapp:+
# * +:expire_after+ - A number in seconds for session timeout
# * +:store_expire_after+ - A number in seconds for stored session timeout. Takes precedence over expire_after and does not effect session cookie expiry.
# * +:client+ - Connect to Redis with given object rather than create one
# * +:on_redis_down:+ - Called with err, env, and SID on Errno::ECONNREFUSED
# * +:on_session_load_error:+ - Called with err and SID on Marshal.load fail
Expand Down Expand Up @@ -115,7 +116,8 @@ def decode(data)
end

def set_session(env, sid, session_data, options = nil)
expiry = (options || env.fetch(ENV_SESSION_OPTIONS_KEY))[:expire_after]
options = options || env.fetch(ENV_SESSION_OPTIONS_KEY)
expiry = options[:store_expire_after] || options[:expire_after]
if expiry
redis.setex(prefixed(sid), expiry, encode(session_data))
else
Expand Down
141 changes: 114 additions & 27 deletions spec/redis_session_store_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@
port: 16_379,
db: 2,
key_prefix: 'myapp:session:',
expire_after: 60 * 120
expire_after: 60 * 120,
store_expire_after: 80 * 120,
}
}
end
Expand Down Expand Up @@ -57,6 +58,10 @@
it 'assigns the :expire_after option to @default_options' do
expect(default_options[:expire_after]).to eq(60 * 120)
end

it 'assigns the :store_expire_after option to @default_options' do
expect(default_options[:store_expire_after]).to eq(80 * 120)
end
end

describe 'when initializing with top-level redis options' do
Expand All @@ -68,7 +73,8 @@
port: 26_379,
db: 4,
key_prefix: 'appydoo:session:',
expire_after: 60 * 60
expire_after: 60 * 60,
store_expire_after: 80 * 60,
}
end

Expand All @@ -95,6 +101,10 @@
it 'assigns the :expire_after option to @default_options' do
expect(default_options[:expire_after]).to eq(60 * 60)
end

it 'assigns the :store_expire_after option to @default_options' do
expect(default_options[:store_expire_after]).to eq(80 * 60)
end
end

describe 'when initializing with existing redis object' do
Expand All @@ -105,7 +115,8 @@
redis: {
client: redis_client,
key_prefix: 'myapp:session:',
expire_after: 60 * 30
expire_after: 60 * 30,
store_expire_after: 80 * 30,
}
}
end
Expand All @@ -127,6 +138,10 @@
it 'assigns the :expire_after option to @default_options' do
expect(default_options[:expire_after]).to eq(60 * 30)
end

it 'assigns the :store_expire_after option to @default_options' do
expect(default_options[:store_expire_after]).to eq(80 * 30)
end
end

describe 'rack 1.45 compatibility' do
Expand Down Expand Up @@ -157,7 +172,7 @@
end
end

context 'when no expire_after option is given' do
context 'when no expiry options are given' do
let(:options) { {} }

it 'sets the session value without expiry' do
Expand Down Expand Up @@ -264,7 +279,7 @@
allow(store).to receive(:generate_sid).and_return(fake_key)
expect(redis).to receive(:get).with("#{options[:key_prefix]}#{fake_key}")

store.send(:get_session, double('env'), fake_key)
store.send(:get_session, ('env'), fake_key)
end

context 'when redis is down' do
Expand Down Expand Up @@ -514,28 +529,100 @@ def self.dump(_value)
end

describe 'setting the session' do
it 'allows changing the session' do
env = { 'rack.session.options' => {} }
sid = 1234
allow(store).to receive(:redis).and_return(Redis.new)
data1 = { 'foo' => 'bar' }
store.send(:set_session, env, sid, data1)
data2 = { 'baz' => 'wat' }
store.send(:set_session, env, sid, data2)
_, session = store.send(:get_session, env, sid)
expect(session).to eq(data2)
end

it 'allows changing the session when the session has an expiry' do
env = { 'rack.session.options' => { expire_after: 60 } }
sid = 1234
allow(store).to receive(:redis).and_return(Redis.new)
data1 = { 'foo' => 'bar' }
store.send(:set_session, env, sid, data1)
data2 = { 'baz' => 'wat' }
store.send(:set_session, env, sid, data2)
_, session = store.send(:get_session, env, sid)
expect(session).to eq(data2)
context 'no expiry specified' do
let(:options) { {} }

it 'allows changing the session' do
env = { 'rack.session.options' => options }
sid = 1234
allow(store).to receive(:redis).and_return(Redis.new)
data1 = { 'foo' => 'bar' }
store.send(:set_session, env, sid, data1)
data2 = { 'baz' => 'wat' }
store.send(:set_session, env, sid, data2)
_, session = store.send(:get_session, env, sid)
expect(session).to eq(data2)
end

it 'sets the session in redis without expiry' do
env = { 'rack.session.options' => options }
sid = 1234
redis = double('redis')
allow(store).to receive(:redis).and_return(redis)
expect(redis).to receive(:set).with("#{sid}", anything)
data = { 'foo' => 'bar' }
store.send(:set_session, env, sid, data)
end
end

context 'when expire_after is specified' do
let :options do
{
expire_after: 60
}
end

it 'allows changing the session' do
env = { 'rack.session.options' => options }
sid = 1234
allow(store).to receive(:redis).and_return(Redis.new)
data1 = { 'foo' => 'bar' }
store.send(:set_session, env, sid, data1)
data2 = { 'baz' => 'wat' }
store.send(:set_session, env, sid, data2)
_, session = store.send(:get_session, env, sid)
expect(session).to eq(data2)
end

it 'sets the session in redis with expiry' do
env = { 'rack.session.options' => options }
sid = 1234
redis = double('redis')
expect(redis).to receive(:setex).with("#{sid}", 60, anything)
allow(store).to receive(:redis).and_return(redis)
data = { 'foo' => 'bar' }
store.send(:set_session, env, sid, data)
end
end

context 'when store_expire_after is specified' do
let :options do
{
store_expire_after: 80
}
end

it 'allows changing the session' do
env = { 'rack.session.options' => options }
sid = 1234
allow(store).to receive(:redis).and_return(Redis.new)
data1 = { 'foo' => 'bar' }
store.send(:set_session, env, sid, data1)
data2 = { 'baz' => 'wat' }
store.send(:set_session, env, sid, data2)
_, session = store.send(:get_session, env, sid)
expect(session).to eq(data2)
end

it 'sets the session in redis with expiry' do
env = { 'rack.session.options' => options }
sid = 1234
redis = double('redis')
expect(redis).to receive(:setex).with("#{sid}", 80, anything)
allow(store).to receive(:redis).and_return(redis)
data = { 'foo' => 'bar' }
store.send(:set_session, env, sid, data)
end

it 'uses the store_expire_after if both expire_after and store_expire_after are specified' do
env = { 'rack.session.options' => {store_expire_after: 80, expire_after: 60}}
sid = 1234
redis = double('redis')
expect(redis).to receive(:setex).with("#{sid}", 80, anything)
allow(store).to receive(:redis).and_return(redis)
data = { 'foo' => 'bar' }
store.send(:set_session, env, sid, data)
end
end
end
end