aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/session
diff options
context:
space:
mode:
authorJoshua Peek <josh@joshpeek.com>2008-11-22 14:33:00 -0600
committerJoshua Peek <josh@joshpeek.com>2008-11-22 14:33:00 -0600
commitcc67272cba35e50afa73cfec856c1677b204ae7e (patch)
tree1bdbc4862fe0ea486bf8d2476ab12184e4b71807 /actionpack/lib/action_controller/vendor/rack-0.4.0/rack/session
parent4b36f76e7a997fb03a6cccb08b8272ddccde5a3e (diff)
downloadrails-cc67272cba35e50afa73cfec856c1677b204ae7e.tar.gz
rails-cc67272cba35e50afa73cfec856c1677b204ae7e.tar.bz2
rails-cc67272cba35e50afa73cfec856c1677b204ae7e.zip
Vendor rack 0.4.0
Diffstat (limited to 'actionpack/lib/action_controller/vendor/rack-0.4.0/rack/session')
-rw-r--r--actionpack/lib/action_controller/vendor/rack-0.4.0/rack/session/abstract/id.rb140
-rw-r--r--actionpack/lib/action_controller/vendor/rack-0.4.0/rack/session/cookie.rb71
-rw-r--r--actionpack/lib/action_controller/vendor/rack-0.4.0/rack/session/memcache.rb97
-rw-r--r--actionpack/lib/action_controller/vendor/rack-0.4.0/rack/session/pool.rb73
4 files changed, 381 insertions, 0 deletions
diff --git a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/session/abstract/id.rb b/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/session/abstract/id.rb
new file mode 100644
index 0000000000..c220b2cb8d
--- /dev/null
+++ b/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/session/abstract/id.rb
@@ -0,0 +1,140 @@
+# AUTHOR: blink <blinketje@gmail.com>; blink#ruby-lang@irc.freenode.net
+# bugrep: Andreas Zehnder
+
+require 'rack/utils'
+require 'time'
+
+module Rack
+ module Session
+ module Abstract
+ # ID sets up a basic framework for implementing an id based sessioning
+ # service. Cookies sent to the client for maintaining sessions will only
+ # contain an id reference. Only #get_session and #set_session should
+ # need to be overwritten.
+ #
+ # All parameters are optional.
+ # * :key determines the name of the cookie, by default it is
+ # 'rack.session'
+ # * :domain and :path set the related cookie values, by default
+ # domain is nil, and the path is '/'.
+ # * :expire_after is the number of seconds in which the session
+ # cookie will expire. By default it is set not to provide any
+ # expiry time.
+ class ID
+ attr_reader :key
+ DEFAULT_OPTIONS = {
+ :key => 'rack.session',
+ :path => '/',
+ :domain => nil,
+ :expire_after => nil
+ }
+
+ def initialize(app, options={})
+ @default_options = self.class::DEFAULT_OPTIONS.merge(options)
+ @key = @default_options[:key]
+ @default_context = context app
+ end
+
+ def call(env)
+ @default_context.call(env)
+ end
+
+ def context(app)
+ Rack::Utils::Context.new self, app do |env|
+ load_session env
+ response = app.call(env)
+ commit_session env, response
+ response
+ end
+ end
+
+ private
+
+ # Extracts the session id from provided cookies and passes it and the
+ # environment to #get_session. It then sets the resulting session into
+ # 'rack.session', and places options and session metadata into
+ # 'rack.session.options'.
+ def load_session(env)
+ sid = (env['HTTP_COOKIE']||'')[/#{@key}=([^,;]+)/,1]
+ sid, session = get_session(env, sid)
+ unless session.is_a?(Hash)
+ puts 'Session: '+sid.inspect+"\n"+session.inspect if $DEBUG
+ raise TypeError, 'Session not a Hash'
+ end
+
+ options = @default_options.
+ merge({ :id => sid, :by => self, :at => Time.now })
+
+ env['rack.session'] = session
+ env['rack.session.options'] = options
+
+ return true
+ end
+
+ # Acquires the session from the environment and the session id from
+ # the session options and passes them to #set_session. It then
+ # proceeds to set a cookie up in the response with the session's id.
+ def commit_session(env, response)
+ unless response.is_a?(Array)
+ puts 'Response: '+response.inspect if $DEBUG
+ raise ArgumentError, 'Response is not an array.'
+ end
+
+ options = env['rack.session.options']
+ unless options.is_a?(Hash)
+ puts 'Options: '+options.inspect if $DEBUG
+ raise TypeError, 'Options not a Hash'
+ end
+
+ sid, time, z = options.values_at(:id, :at, :by)
+ unless self == z
+ warn "#{self} not managing this session."
+ return
+ end
+
+ unless env['rack.session'].is_a?(Hash)
+ warn 'Session: '+sid.inspect+"\n"+session.inspect if $DEBUG
+ raise TypeError, 'Session not a Hash'
+ end
+
+ unless set_session(env, sid)
+ warn "Session not saved." if $DEBUG
+ warn "#{env['rack.session'].inspect} has been lost."if $DEBUG
+ return false
+ end
+
+ cookie = Utils.escape(@key)+'='+Utils.escape(sid)
+ cookie<< "; domain=#{options[:domain]}" if options[:domain]
+ cookie<< "; path=#{options[:path]}" if options[:path]
+ if options[:expire_after]
+ expiry = time + options[:expire_after]
+ cookie<< "; expires=#{expiry.httpdate}"
+ end
+
+ case a = (h = response[1])['Set-Cookie']
+ when Array then a << cookie
+ when String then h['Set-Cookie'] = [a, cookie]
+ when nil then h['Set-Cookie'] = cookie
+ end
+
+ return true
+ end
+
+ # Should return [session_id, session]. All thread safety and session
+ # retrival proceedures should occur here.
+ # If nil is provided as the session id, generation of a new valid id
+ # should occur within.
+ def get_session(env, sid)
+ raise '#get_session needs to be implemented.'
+ end
+
+ # All thread safety and session storage proceedures should occur here.
+ # Should return true or false dependant on whether or not the session
+ # was saved or not.
+ def set_session(env, sid)
+ raise '#set_session needs to be implemented.'
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/session/cookie.rb b/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/session/cookie.rb
new file mode 100644
index 0000000000..154a87c3ea
--- /dev/null
+++ b/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/session/cookie.rb
@@ -0,0 +1,71 @@
+module Rack
+
+ module Session
+
+ # Rack::Session::Cookie provides simple cookie based session management.
+ # The session is a Ruby Hash stored as base64 encoded marshalled data
+ # set to :key (default: rack.session).
+ #
+ # Example:
+ #
+ # use Rack::Session::Cookie, :key => 'rack.session',
+ # :domain => 'foo.com',
+ # :path => '/',
+ # :expire_after => 2592000
+ #
+ # All parameters are optional.
+
+ class Cookie
+
+ def initialize(app, options={})
+ @app = app
+ @key = options[:key] || "rack.session"
+ @default_options = {:domain => nil,
+ :path => "/",
+ :expire_after => nil}.merge(options)
+ end
+
+ def call(env)
+ load_session(env)
+ status, headers, body = @app.call(env)
+ commit_session(env, status, headers, body)
+ end
+
+ private
+
+ def load_session(env)
+ request = Rack::Request.new(env)
+ session_data = request.cookies[@key]
+
+ begin
+ session_data = session_data.unpack("m*").first
+ session_data = Marshal.load(session_data)
+ env["rack.session"] = session_data
+ rescue
+ env["rack.session"] = Hash.new
+ end
+
+ env["rack.session.options"] = @default_options.dup
+ end
+
+ def commit_session(env, status, headers, body)
+ session_data = Marshal.dump(env["rack.session"])
+ session_data = [session_data].pack("m*")
+
+ if session_data.size > (4096 - @key.size)
+ env["rack.errors"].puts("Warning! Rack::Session::Cookie data size exceeds 4K. Content dropped.")
+ [status, headers, body]
+ else
+ options = env["rack.session.options"]
+ cookie = Hash.new
+ cookie[:value] = session_data
+ cookie[:expires] = Time.now + options[:expire_after] unless options[:expire_after].nil?
+ response = Rack::Response.new(body, status, headers)
+ response.set_cookie(@key, cookie.merge(options))
+ response.to_a
+ end
+ end
+
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/session/memcache.rb b/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/session/memcache.rb
new file mode 100644
index 0000000000..7cda9d8697
--- /dev/null
+++ b/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/session/memcache.rb
@@ -0,0 +1,97 @@
+# AUTHOR: blink <blinketje@gmail.com>; blink#ruby-lang@irc.freenode.net
+
+require 'rack/session/abstract/id'
+require 'memcache'
+
+module Rack
+ module Session
+ # Rack::Session::Memcache provides simple cookie based session management.
+ # Session data is stored in memcached. The corresponding session key is
+ # maintained in the cookie.
+ # You may treat Session::Memcache as you would Session::Pool with the
+ # following caveats.
+ #
+ # * Setting :expire_after to 0 would note to the Memcache server to hang
+ # onto the session data until it would drop it according to it's own
+ # specifications. However, the cookie sent to the client would expire
+ # immediately.
+ #
+ # Note that memcache does drop data before it may be listed to expire. For
+ # a full description of behaviour, please see memcache's documentation.
+
+ class Memcache < Abstract::ID
+ attr_reader :mutex, :pool
+ DEFAULT_OPTIONS = Abstract::ID::DEFAULT_OPTIONS.merge({
+ :namespace => 'rack:session',
+ :memcache_server => 'localhost:11211'
+ })
+
+ def initialize(app, options={})
+ super
+ @pool = MemCache.new @default_options[:memcache_server], @default_options
+ unless @pool.servers.any?{|s|s.alive?}
+ raise "#{self} unable to find server during initialization."
+ end
+ @mutex = Mutex.new
+ end
+
+ private
+
+ def get_session(env, sid)
+ session = sid && @pool.get(sid)
+ unless session and session.is_a?(Hash)
+ session = {}
+ lc = 0
+ @mutex.synchronize do
+ begin
+ raise RuntimeError, 'Unique id finding looping excessively' if (lc+=1) > 1000
+ sid = "%08x" % rand(0xffffffff)
+ ret = @pool.add(sid, session)
+ end until /^STORED/ =~ ret
+ end
+ end
+ class << session
+ @deleted = []
+ def delete key
+ (@deleted||=[]) << key
+ super
+ end
+ end
+ [sid, session]
+ rescue MemCache::MemCacheError, Errno::ECONNREFUSED # MemCache server cannot be contacted
+ warn "#{self} is unable to find server."
+ warn $!.inspect
+ return [ nil, {} ]
+ end
+
+ def set_session(env, sid)
+ session = env['rack.session']
+ options = env['rack.session.options']
+ expiry = options[:expire_after] || 0
+ o, s = @mutex.synchronize do
+ old_session = @pool.get(sid)
+ unless old_session.is_a?(Hash)
+ warn 'Session not properly initialized.' if $DEBUG
+ old_session = {}
+ @pool.add sid, old_session, expiry
+ end
+ session.instance_eval do
+ @deleted.each{|k| old_session.delete(k) } if defined? @deleted
+ end
+ @pool.set sid, old_session.merge(session), expiry
+ [old_session, session]
+ end
+ s.each do |k,v|
+ next unless o.has_key?(k) and v != o[k]
+ warn "session value assignment collision at #{k.inspect}:"+
+ "\n\t#{o[k].inspect}\n\t#{v.inspect}"
+ end if $DEBUG and env['rack.multithread']
+ return true
+ rescue MemCache::MemCacheError, Errno::ECONNREFUSED # MemCache server cannot be contacted
+ warn "#{self} is unable to find server."
+ warn $!.inspect
+ return false
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/session/pool.rb b/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/session/pool.rb
new file mode 100644
index 0000000000..6e5e788210
--- /dev/null
+++ b/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/session/pool.rb
@@ -0,0 +1,73 @@
+# AUTHOR: blink <blinketje@gmail.com>; blink#ruby-lang@irc.freenode.net
+# THANKS:
+# apeiros, for session id generation, expiry setup, and threadiness
+# sergio, threadiness and bugreps
+
+require 'rack/session/abstract/id'
+require 'thread'
+
+module Rack
+ module Session
+ # Rack::Session::Pool provides simple cookie based session management.
+ # Session data is stored in a hash held by @pool.
+ # In the context of a multithreaded environment, sessions being
+ # committed to the pool is done in a merging manner.
+ #
+ # Example:
+ # myapp = MyRackApp.new
+ # sessioned = Rack::Session::Pool.new(myapp,
+ # :key => 'rack.session',
+ # :domain => 'foo.com',
+ # :path => '/',
+ # :expire_after => 2592000
+ # )
+ # Rack::Handler::WEBrick.run sessioned
+
+ class Pool < Abstract::ID
+ attr_reader :mutex, :pool
+ DEFAULT_OPTIONS = Abstract::ID::DEFAULT_OPTIONS.dup
+
+ def initialize(app, options={})
+ super
+ @pool = Hash.new
+ @mutex = Mutex.new
+ end
+
+ private
+
+ def get_session(env, sid)
+ session = @mutex.synchronize do
+ unless sess = @pool[sid] and ((expires = sess[:expire_at]).nil? or expires > Time.now)
+ @pool.delete_if{|k,v| expiry = v[:expire_at] and expiry < Time.now }
+ begin
+ sid = "%08x" % rand(0xffffffff)
+ end while @pool.has_key?(sid)
+ end
+ @pool[sid] ||= {}
+ end
+ [sid, session]
+ end
+
+ def set_session(env, sid)
+ options = env['rack.session.options']
+ expiry = options[:expire_after] && options[:at]+options[:expire_after]
+ @mutex.synchronize do
+ old_session = @pool[sid]
+ old_session[:expire_at] = expiry if expiry
+ session = old_session.merge(env['rack.session'])
+ @pool[sid] = session
+ session.each do |k,v|
+ next unless old_session.has_key?(k) and v != old_session[k]
+ warn "session value assignment collision at #{k}: #{old_session[k]} <- #{v}"
+ end if $DEBUG and env['rack.multithread']
+ end
+ return true
+ rescue
+ warn "#{self} is unable to find server."
+ warn "#{env['rack.session'].inspect} has been lost."
+ warn $!.inspect
+ return false
+ end
+ end
+ end
+end