1 # AUTHOR: blink <blinketje@gmail.com>; blink#ruby-lang@irc.freenode.net
3 require 'rack/session/abstract/id'
8 # Rack::Session::Memcache provides simple cookie based session management.
9 # Session data is stored in memcached. The corresponding session key is
10 # maintained in the cookie.
11 # You may treat Session::Memcache as you would Session::Pool with the
14 # * Setting :expire_after to 0 would note to the Memcache server to hang
15 # onto the session data until it would drop it according to it's own
16 # specifications. However, the cookie sent to the client would expire
19 # Note that memcache does drop data before it may be listed to expire. For
20 # a full description of behaviour, please see memcache's documentation.
22 class Memcache
< Abstract
::ID
23 attr_reader
:mutex, :pool
24 DEFAULT_OPTIONS
= Abstract
::ID::DEFAULT_OPTIONS.merge \
25 :namespace => 'rack:session',
26 :memcache_server => 'localhost:11211'
28 def initialize(app
, options
={})
33 new
@default_options[:memcache_server], @default_options
34 raise 'No memcache servers' unless @pool.servers
.any
?{|s
|s
.alive
?}
40 break sid
unless @pool.get(sid
, true)
44 def get_session(env, sid
)
45 session
= @pool.get(sid
) if sid
46 @mutex.lock
if env['rack.multithread']
47 unless sid
and session
48 env['rack.errors'].puts("Session '#{sid.inspect}' not found, initializing...") if $VERBOSE and not sid
.nil?
51 ret
= @pool.add sid
, session
52 raise "Session collision on '#{sid.inspect}'" unless /^STORED/ =~ ret
54 session
.instance_variable_set('@old', {}.merge(session
))
56 rescue MemCache
::MemCacheError, Errno
::ECONNREFUSED # MemCache server cannot be contacted
57 warn
"#{self} is unable to find server."
61 @mutex.unlock
if env['rack.multithread']
64 def set_session(env, session_id
, new_session
, options
)
65 expiry
= options
[:expire_after]
66 expiry
= expiry
.nil? ? 0 : expiry
+ 1
68 @mutex.lock
if env['rack.multithread']
69 session
= @pool.get(session_id
) || {}
70 if options
[:renew] or options
[:drop]
71 @pool.delete session_id
72 return false if options
[:drop]
73 session_id
= generate_sid
74 @pool.add session_id
, 0 # so we don't worry about cache miss on #set
76 old_session
= new_session
.instance_variable_get('@old') || {}
77 session
= merge_sessions session_id
, old_session
, new_session
, session
78 @pool.set session_id
, session
, expiry
80 rescue MemCache
::MemCacheError, Errno
::ECONNREFUSED # MemCache server cannot be contacted
81 warn
"#{self} is unable to find server."
85 @mutex.unlock
if env['rack.multithread']
90 def merge_sessions sid
, old
, new
, cur
=nil
92 unless Hash
=== old
and Hash
=== new
93 warn
'Bad old or new sessions provided.'
97 delete
= old
.keys
- new
.keys
98 warn
"//@#{sid}: delete #{delete*','}" if $VERBOSE and not delete
.empty
?
99 delete
.each
{|k
| cur
.delete k
}
101 update
= new
.keys
.select
{|k
| new
[k
] != old
[k
] }
102 warn
"//@#{sid}: update #{update*','}" if $VERBOSE and not update
.empty
?
103 update
.each
{|k
| cur
[k
] = new
[k
] }