Created
June 25, 2018 07:13
Star
You must be signed in to star a gist
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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