require 'md5' require 'webrick' require 'mega/synchash' require 'mega/time_in_english' require 'glue/attribute' require 'glue/configuration' require 'glue/logger' require 'glue/expirable' require 'nitro/cgi/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. # # TODO rehash of the session cookie class Session < Hash include Expirable # 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' # useful with persistents sessions stores setting :cookie_expires, :default => false, :doc => 'Set expires parameter of session cookie equal to the keepalive setting?' # The session keepalive time. The session is eligable for # garbage collection after this time passes. setting :keepalive, :default => 30.minutes, :doc => 'The session keepalive time' # The sessions store. cattr_accessor :store class << self # Set the session store. The following options are # available: # # * :memory [default] # * :drb # * :og # * :file (not safe yet with multiple process as in fastcgi) # * :memcached (not available yet) def store_type=(store_type) # gmosx: RDoc friendly. require 'nitro/session/' + store_type.to_s end alias_method :set_store_type, :store_type= # 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 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) if Session.cookie_expires cookie.expires = Time.now + Session.keepalive end context.add_cookie(cookie) Session.store[session.session_id] = session else # Access ('touch') the existing session. session.touch! end return session end end # By default sessions are stored in memory. #-- # gmosx: should be placed here. #++ set_store_type(:memory) # The unique id of this session. attr_reader :session_id # Create the session for the given context. # If the hook method 'created' is defined it is called # at the end. Typically used to initialize the session # hash. def initialize(context = nil) @session_id = create_id expires_after(Session.keepalive) created if respond_to?(:created) 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 def touch! expires_after(Session.keepalive) end 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 end # * George Moschovitis # * Guillaume Pierronnet