Skip to content

Instantly share code, notes, and snippets.

@angelfan
Created June 25, 2018 07:13
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save angelfan/9552d1b6900a8157f069272f1219ea4e to your computer and use it in GitHub Desktop.
# frozen_string_literal: true
module SessionRedisPatch
def generate_unique_sid(_session)
loop do
sid = generate_sid
first = with do |c|
[*init_session(c, sid, @default_options)].first
end
break sid if [1, true].include?(first)
end
end
def get_session(env, sid)
if env['rack.session.options'][:skip]
[generate_sid, {}]
else
with_lock(env, [nil, {}]) do
session = with { |c| c.hgetall(sid) } if sid
unless sid and session and !session.empty?
session = {}
sid = generate_unique_sid(session)
end
[sid, session.except(sid)]
end
end
end
def set_session(env, session_id, new_session, options)
with_lock(env, false) do
with do |c|
if options[:cleared]
c.del(session_id)
c.hset(session_id, session_id, 1)
end
options[:changed_keys].each do |key|
val = new_session[key.to_s]
c.hset(session_id, key, val) if val
end
ttl = expires_in(options)
c.expire(session_id, ttl.to_i) if ttl
end
session_id
end
end
def destroy_session(env, session_id, options)
with_lock(env) do
with do |c|
c.del(session_id)
end
generate_sid unless options[:drop]
end
end
private
def expires_in(options)
if options
# Rack::Session Merb Rails/Sinatra
options[:expire_after] || options[:expires_in] || options[:expire_in]
end
end
def init_session(store, sid, options)
if ttl = expires_in(options)
store.multi do
store.hsetnx(sid, sid, 1)
store.expire(sid, ttl.to_i)
end
else
store.hsetnx(sid, sid, 1)
end
end
end
module CommitSessionPatch
def commit_session(req, res)
session = req.get_header Rack::RACK_SESSION
options = session.options
if options[:drop] || options[:renew]
session_id = delete_session(req, session.id || generate_sid, options)
return unless session_id
end
return unless commit_session?(req, session, options)
session.send(:load!) unless loaded_session?(session)
session_id ||= session.id
session_data = session.to_hash.delete_if { |k, v| v.nil? }
options[:changed_keys] = session.changed_keys
options[:cleared] = session.cleared?
if not data = write_session(req, session_id, session_data, options)
req.get_header(Rack::RACK_ERRORS).puts("Warning! #{self.class.name} failed to save session. Content dropped.")
elsif options[:defer] and not options[:renew]
req.get_header(RACK_ERRORS).puts("Deferring cookie for #{session_id}") if $VERBOSE
else
cookie = Hash.new
cookie[:value] = data
cookie[:expires] = Time.now + options[:expire_after] if options[:expire_after]
cookie[:expires] = Time.now + options[:max_age] if options[:max_age]
set_cookie(req, res, cookie.merge!(options))
end
end
end
module SessionDirtyPatch
def initialize(by, req)
@changed_keys = Set.new
@cleared = false
super(by, req)
end
def changed_keys
@changed_keys
end
def cleared?
@cleared
end
def []=(key, value)
@changed_keys << key
super
end
def update(hash)
@changed_keys.merge(hash.keys)
super
end
def delete(key)
@changed_keys << key
super
end
def merge!(other)
@changed_keys.merge(other.keys)
super
end
def clear
@cleared = true
@changed_keys.clear
super
end
end
module StoreNamespacePatch
def hgetall(key)
namespace(key) { |k| super(k) }
end
def hsetnx(key, field, val)
namespace(key) { |k| super(k, field, val) }
end
def hset(key, field, val)
namespace(key) { |k| super(k, field, val) }
end
end
Redis::Store.include StoreNamespacePatch
ActionDispatch::Session::RedisStore.include SessionRedisPatch
ActionDispatch::Session::RedisStore.include CommitSessionPatch
ActionDispatch::Request::Session.prepend SessionDirtyPatch
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment