Skip to content

Commit

Permalink
Merge pull request #16 from crystalcommerce/custom-encoder-class
Browse files Browse the repository at this point in the history
Custom encoder class
  • Loading branch information
meatballhat committed Mar 17, 2014
2 parents 3a396b4 + a957987 commit 94f2118
Show file tree
Hide file tree
Showing 2 changed files with 107 additions and 4 deletions.
57 changes: 53 additions & 4 deletions lib/redis-session-store.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,14 @@ def initialize(app, options = {})
@redis = Redis.new(redis_options)
@on_sid_collision = options[:on_sid_collision]
@on_redis_down = options[:on_redis_down]
@serializer = determine_serializer(options[:serializer])
end

attr_accessor :on_sid_collision, :on_redis_down

private

attr_reader :redis, :key, :default_options
attr_reader :redis, :key, :default_options, :serializer

def prefixed(sid)
"#{default_options[:key_prefix]}#{sid}"
Expand Down Expand Up @@ -81,22 +82,30 @@ def get_session(env, sid)

def load_session_from_redis(sid)
data = redis.get(prefixed(sid))
data ? Marshal.load(data) : nil
data ? decode(data) : nil
end

def decode(data)
serializer.load(data)
end

def set_session(env, sid, session_data, options = nil)
expiry = (options || env[ENV_SESSION_OPTIONS_KEY])[:expire_after]
if expiry
redis.setex(prefixed(sid), expiry, Marshal.dump(session_data))
redis.setex(prefixed(sid), expiry, encode(session_data))
else
redis.set(prefixed(sid), Marshal.dump(session_data))
redis.set(prefixed(sid), encode(session_data))
end
return sid
rescue Errno::ECONNREFUSED => e
on_redis_down.call(e, env, sid) if on_redis_down
return false
end

def encode(session_data)
serializer.dump(session_data)
end

def destroy_session(env, sid, options)
redis.del(prefixed(sid))
return nil if (options || {})[:drop]
Expand All @@ -112,4 +121,44 @@ def destroy(env)
on_redis_down.call(e, env, sid) if on_redis_down
false
end

def determine_serializer(serializer)
serializer ||= :marshal
case serializer
when :marshal then Marshal
when :json then JsonSerializer
when :hybrid then HybridSerializer
else serializer
end
end

# Uses built-in JSON library to encode/decode session
class JsonSerializer
def self.load(value)
JSON.parse(value, quirks_mode: true)
end

def self.dump(value)
JSON.generate(value, quirks_mode: true)
end
end

# Transparently migrates existing session values from Marshal to JSON
class HybridSerializer < JsonSerializer
MARSHAL_SIGNATURE = "\x04\x08".freeze

def self.load(value)
if needs_migration?(value)
Marshal.load(value)
else
super
end
end

private

def self.needs_migration?(value)
value.start_with?(MARSHAL_SIGNATURE)
end
end
end
54 changes: 54 additions & 0 deletions spec/redis_session_store_spec.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# vim:fileencoding=utf-8
require 'json'

describe RedisSessionStore do
let :random_string do
Expand Down Expand Up @@ -284,4 +285,57 @@
end
end
end

describe 'session encoding' do
let(:env) { double('env') }
let(:session_id) { 12_345 }
let(:session_data) { { 'some' => 'data' } }
let(:options) { {} }
let(:encoded_data) { Marshal.dump(session_data) }
let(:redis) { double('redis', set: nil, get: encoded_data) }
let(:expected_encoding) { encoded_data }

before do
store.stub(:redis).and_return(redis)
end

shared_examples_for 'serializer' do
it 'encodes correctly' do
redis.should_receive(:set).with('12345', expected_encoding)
store.send(:set_session, env, session_id, session_data, options)
end

it 'decodes correctly' do
expect(store.send(:get_session, env, session_id))
.to eq([session_id, session_data])
end
end

context 'marshal' do
let(:options) { { serializer: :marshal } }
it_should_behave_like 'serializer'
end

context 'json' do
let(:options) { { serializer: :json } }
let(:encoded_data) { '{"some":"data"}' }

it_should_behave_like 'serializer'
end

context 'hybrid' do
let(:options) { { serializer: :hybrid } }
let(:expected_encoding) { '{"some":"data"}' }

context 'marshal encoded data' do
it_should_behave_like 'serializer'
end

context 'json encoded data' do
let(:encoded_data) { '{"some":"data"}' }

it_should_behave_like 'serializer'
end
end
end
end

0 comments on commit 94f2118

Please sign in to comment.