Skip to content

Commit

Permalink
Switch to Rack based session stores.
Browse files Browse the repository at this point in the history
  • Loading branch information
josh committed Dec 15, 2008
1 parent e8c1915 commit ed70830
Show file tree
Hide file tree
Showing 31 changed files with 1,127 additions and 1,799 deletions.
15 changes: 6 additions & 9 deletions actionpack/lib/action_controller.rb
Expand Up @@ -89,18 +89,15 @@ module Http
autoload :Headers, 'action_controller/headers'
end

# DEPRECATE: Remove CGI support
autoload :CgiRequest, 'action_controller/cgi_process'
autoload :CGIHandler, 'action_controller/cgi_process'
end

class CGI
class Session
autoload :ActiveRecordStore, 'action_controller/session/active_record_store'
module Session
autoload :AbstractStore, 'action_controller/session/abstract_store'
autoload :CookieStore, 'action_controller/session/cookie_store'
autoload :DRbStore, 'action_controller/session/drb_store'
autoload :MemCacheStore, 'action_controller/session/mem_cache_store'
end

# DEPRECATE: Remove CGI support
autoload :CgiRequest, 'action_controller/cgi_process'
autoload :CGIHandler, 'action_controller/cgi_process'
end

autoload :Mime, 'action_controller/mime_type'
Expand Down
12 changes: 2 additions & 10 deletions actionpack/lib/action_controller/base.rb
Expand Up @@ -164,8 +164,8 @@ class UnknownHttpMethod < ActionControllerError #:nodoc:
#
# Other options for session storage are:
#
# * ActiveRecordStore - Sessions are stored in your database, which works better than PStore with multiple app servers and,
# unlike CookieStore, hides your session contents from the user. To use ActiveRecordStore, set
# * ActiveRecord::SessionStore - Sessions are stored in your database, which works better than PStore with multiple app servers and,
# unlike CookieStore, hides your session contents from the user. To use ActiveRecord::SessionStore, set
#
# config.action_controller.session_store = :active_record_store
#
Expand Down Expand Up @@ -1216,7 +1216,6 @@ def initialize_current_url
def log_processing
if logger && logger.info?
log_processing_for_request_id
log_processing_for_session_id
log_processing_for_parameters
end
end
Expand All @@ -1229,13 +1228,6 @@ def log_processing_for_request_id
logger.info(request_id)
end

def log_processing_for_session_id
if @_session && @_session.respond_to?(:session_id) && @_session.respond_to?(:dbman) &&
!@_session.dbman.is_a?(CGI::Session::CookieStore)
logger.info " Session ID: #{@_session.session_id}"
end
end

def log_processing_for_parameters
parameters = respond_to?(:filter_parameters) ? filter_parameters(params) : params.dup
parameters = parameters.except!(:controller, :action, :format, :_method)
Expand Down
1 change: 0 additions & 1 deletion actionpack/lib/action_controller/cgi_ext.rb
@@ -1,7 +1,6 @@
require 'action_controller/cgi_ext/stdinput'
require 'action_controller/cgi_ext/query_extension'
require 'action_controller/cgi_ext/cookie'
require 'action_controller/cgi_ext/session'

class CGI #:nodoc:
include ActionController::CgiExt::Stdinput
Expand Down
53 changes: 0 additions & 53 deletions actionpack/lib/action_controller/cgi_ext/session.rb

This file was deleted.

2 changes: 1 addition & 1 deletion actionpack/lib/action_controller/cgi_process.rb
Expand Up @@ -61,7 +61,7 @@ def self.dispatch_cgi(app, cgi, out = $stdout)

class CgiRequest #:nodoc:
DEFAULT_SESSION_OPTIONS = {
:database_manager => CGI::Session::CookieStore,
:database_manager => nil,
:prefix => "ruby_sess.",
:session_path => "/",
:session_key => "_session_id",
Expand Down
8 changes: 5 additions & 3 deletions actionpack/lib/action_controller/dispatcher.rb
Expand Up @@ -45,8 +45,10 @@ def to_prepare(identifier = nil, &block)
end

cattr_accessor :middleware
self.middleware = MiddlewareStack.new
self.middleware.use "ActionController::Failsafe"
self.middleware = MiddlewareStack.new do |middleware|
middleware.use "ActionController::Failsafe"
middleware.use "ActionController::SessionManagement::Middleware"
end

include ActiveSupport::Callbacks
define_callbacks :prepare_dispatch, :before_dispatch, :after_dispatch
Expand Down Expand Up @@ -89,7 +91,7 @@ def call(env)

def _call(env)
@request = RackRequest.new(env)
@response = RackResponse.new(@request)
@response = RackResponse.new
dispatch
end

Expand Down
4 changes: 2 additions & 2 deletions actionpack/lib/action_controller/integration.rb
Expand Up @@ -489,8 +489,8 @@ def reset!
# By default, a single session is automatically created for you, but you
# can use this method to open multiple sessions that ought to be tested
# simultaneously.
def open_session
application = ActionController::Dispatcher.new
def open_session(application = nil)
application ||= ActionController::Dispatcher.new
session = Integration::Session.new(application)

# delegate the fixture accessors back to the test instance
Expand Down
25 changes: 20 additions & 5 deletions actionpack/lib/action_controller/middleware_stack.rb
Expand Up @@ -4,7 +4,12 @@ class Middleware
attr_reader :klass, :args, :block

def initialize(klass, *args, &block)
@klass = klass.is_a?(Class) ? klass : klass.to_s.constantize
if klass.is_a?(Class)
@klass = klass
else
@klass = klass.to_s.constantize
end

@args = args
@block = block
end
Expand All @@ -21,18 +26,28 @@ def ==(middleware)
end

def inspect
str = @klass.to_s
@args.each { |arg| str += ", #{arg.inspect}" }
str = klass.to_s
args.each { |arg| str += ", #{arg.inspect}" }
str
end

def build(app)
klass.new(app, *args, &block)
if block
klass.new(app, *args, &block)
else
klass.new(app, *args)
end
end
end

def initialize(*args, &block)
super(*args)
block.call(self) if block_given?
end

def use(*args, &block)
push(Middleware.new(*args, &block))
middleware = Middleware.new(*args, &block)
push(middleware)
end

def build(app)
Expand Down
139 changes: 12 additions & 127 deletions actionpack/lib/action_controller/rack_process.rb
Expand Up @@ -3,24 +3,12 @@
module ActionController #:nodoc:
class RackRequest < AbstractRequest #:nodoc:
attr_accessor :session_options
attr_reader :cgi

class SessionFixationAttempt < StandardError #:nodoc:
end

DEFAULT_SESSION_OPTIONS = {
:database_manager => CGI::Session::CookieStore, # store data in cookie
:prefix => "ruby_sess.", # prefix session file names
:session_path => "/", # available to all paths in app
:session_key => "_session_id",
:cookie_only => true,
:session_http_only=> true
}

def initialize(env, session_options = DEFAULT_SESSION_OPTIONS)
@session_options = session_options
def initialize(env)
@env = env
@cgi = CGIWrapper.new(self)
super()
end

Expand Down Expand Up @@ -66,87 +54,25 @@ def server_software
@env['SERVER_SOFTWARE'].split("/").first
end

def session
unless defined?(@session)
if @session_options == false
@session = Hash.new
else
stale_session_check! do
if cookie_only? && query_parameters[session_options_with_string_keys['session_key']]
raise SessionFixationAttempt
end
case value = session_options_with_string_keys['new_session']
when true
@session = new_session
when false
begin
@session = CGI::Session.new(@cgi, session_options_with_string_keys)
# CGI::Session raises ArgumentError if 'new_session' == false
# and no session cookie or query param is present.
rescue ArgumentError
@session = Hash.new
end
when nil
@session = CGI::Session.new(@cgi, session_options_with_string_keys)
else
raise ArgumentError, "Invalid new_session option: #{value}"
end
@session['__valid_session']
end
end
end
@session
def session_options
@env['rack.session.options'] ||= {}
end

def reset_session
@session.delete if defined?(@session) && @session.is_a?(CGI::Session)
@session = new_session
def session_options=(options)
@env['rack.session.options'] = options
end

private
# Delete an old session if it exists then create a new one.
def new_session
if @session_options == false
Hash.new
else
CGI::Session.new(@cgi, session_options_with_string_keys.merge("new_session" => false)).delete rescue nil
CGI::Session.new(@cgi, session_options_with_string_keys.merge("new_session" => true))
end
end

def cookie_only?
session_options_with_string_keys['cookie_only']
end

def stale_session_check!
yield
rescue ArgumentError => argument_error
if argument_error.message =~ %r{undefined class/module ([\w:]*\w)}
begin
# Note that the regexp does not allow $1 to end with a ':'
$1.constantize
rescue LoadError, NameError => const_error
raise ActionController::SessionRestoreError, <<-end_msg
Session contains objects whose class definition isn\'t available.
Remember to require the classes for all objects kept in the session.
(Original exception: #{const_error.message} [#{const_error.class}])
end_msg
end

retry
else
raise
end
end
def session
@env['rack.session'] ||= {}
end

def session_options_with_string_keys
@session_options_with_string_keys ||= DEFAULT_SESSION_OPTIONS.merge(@session_options).stringify_keys
end
def reset_session
@env['rack.session'] = {}
end
end

class RackResponse < AbstractResponse #:nodoc:
def initialize(request)
@cgi = request.cgi
def initialize
@writer = lambda { |x| @body << x }
@block = nil
super()
Expand Down Expand Up @@ -247,49 +173,8 @@ def set_cookies!
else cookies << cookie.to_s
end

@cgi.output_cookies.each { |c| cookies << c.to_s } if @cgi.output_cookies

headers['Set-Cookie'] = [headers['Set-Cookie'], cookies].flatten.compact
end
end
end

class CGIWrapper < ::CGI
attr_reader :output_cookies

def initialize(request, *args)
@request = request
@args = *args
@input = request.body

super *args
end

def params
@params ||= @request.params
end

def cookies
@request.cookies
end

def query_string
@request.query_string
end

# Used to wrap the normal args variable used inside CGI.
def args
@args
end

# Used to wrap the normal env_table variable used inside CGI.
def env_table
@request.env
end

# Used to wrap the normal stdinput variable used inside CGI.
def stdinput
@input
end
end
end

2 comments on commit ed70830

@methodmissing
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Josh,

Not sure if this makes sense, but we’ve been using CookieSessionStore with a persistent session_id for a few months to be able to track features such as visitors to resource X also viewed resource Y etc.

Sole motivation being API compat with other stores that do yield a consistent session identifier minus having to use a server side session store.

I updated it today for compat. with this commit.

http://github.com/methodmissing/stable_session_id/tree/master

Would you consider such a feature useful at the framework level ?

@josh
Copy link
Contributor Author

@josh josh commented on ed70830 Dec 17, 2008

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, lets do that. I don’t want to invalidate anyones sessions intentionally.

Please open a ticket on LH and assign it to me.

Please sign in to comment.