require 'md5' require 'webrick' require 'glue/hash' require 'glue/attribute' require 'glue/configuration' require 'nitro/cookie' module Nitro # A web application session. # # State is a neccessary evil but session variables # should be avoided as much as possible. Session state # is typically distributed to many servers so avoid # storing complete objects in session variables, only # store oids and small integer/strings. # # The session should be persistable to survive server # shutdowns. class Session < Hash # Session id salt. setting :session_id_salt, :default => 'SALT', :doc => 'Session id salt' # The name of the cookie that stores the session id. setting :cookie_name, :default => 'nsid', :doc => 'The name of the cookie that stores the session id' # The sessions store. By default sessions are # stored in memory. cattr_accessor :store; @@store = Glue::SafeHash.new # Set the session store. The following options are # available: # # * :memory [default] # * :drb # * :memcached (not available yet) # * :og (not available yet) # * :file (not available yet) def self.store_type=(store_type) # gmosx: RDoc complains about this, so lets use an # eval, AAAAAAAARGH! # require "nitro/session/#{store_type}" eval %{ require 'nitro/session/#{store_type}' } end # Lookup the session in the store by using the session # cookie value as a key. If the session does not exist # creates a new session, inserts it in the store and # appends a new session cookie in the response. def self.lookup(context) if session_id = context.cookies[Session.cookie_name] session = Session.store[session_id] end unless session # Create new session. session = Session.new(context) cookie = Cookie.new(Session.cookie_name, session.session_id) context.add_cookie(cookie) Session.store[session.session_id] = session else # Access ('touch') the existing session. session.touch! end return session end # The unique id of this session. attr_reader :session_id # The time this session was created. attr_reader :ctime alias_method :create_time, :ctime # The time this session was last modified. attr_accessor :mtime alias_method :modify_time, :mtime # The time this session was last accessed. attr_accessor :atime alias_method :access_time, :mtime # Create the session for the given context. def initialize(context = nil) @session_id = create_id @ctime = @mtime = @atime = Time.now end # Like the unix touch command, upadtes # the atime to now. def touch! @atime = Time.now end # Synchronize the session store, by # restoring this session. Especially useful # in distributed and/or multiprocess setups. def sync Session.store[@session_id] = self end alias_method :restore, :sync protected # Calculates a unique id. # # The session id must be unique, a monotonically # increasing function like time is appropriate. # Random may produce equal ids? add a prefix # (SALT) to stop hackers from creating session_ids. def create_id now = Time.now md5 = Digest::MD5.new md5.update(now.to_s) md5.update(now.usec.to_s) md5.update(rand(0).to_s) md5.update(Session.session_id_salt) md5.hexdigest end end # Abstract class for a session store manager. class SessionStore def []=(sid, session) end def [](sid) end def delete(sid) end end end # * George Moschovitis