Permalink
Browse files

Rely on Rack::Session stores API for more compatibility across the Ru…

…by world.
  • Loading branch information...
josevalim committed Sep 20, 2010
1 parent 5836af8 commit 50215f9525b6b5e3bfe703724b9f68177ed8565d
View
@@ -6,6 +6,7 @@ else
gem "arel", :git => "git://github.com/rails/arel.git"
end
+gem "rack", :git => "git://github.com/rack/rack.git"
gem "rails", :path => File.dirname(__FILE__)
gem "rake", ">= 0.8.7"
View
@@ -1,12 +1,12 @@
*Rails 3.1.0 (unreleased)*
+* Rely on Rack::Session stores API for more compatibility across the Ruby world. This is backwards incompatible since Rack::Session expects #get_session to accept 4 arguments and requires #destroy_session instead of simply #destroy. [José Valim]
+
* file_field automatically adds :multipart => true to the enclosing form. [Santiago Pastorino]
* Renames csrf_meta_tag -> csrf_meta_tags, and aliases csrf_meta_tag for backwards compatibility. [fxn]
-* Add Rack::Cache to the default stack. Create a Rails store that delegates to the Rails cache, so by default, whatever caching layer you are using will be used
- for HTTP caching. Note that Rack::Cache will be used if you use #expires_in, #fresh_when or #stale with :public => true. Otherwise, the caching rules will apply
- to the browser only.
+* Add Rack::Cache to the default stack. Create a Rails store that delegates to the Rails cache, so by default, whatever caching layer you are using will be used for HTTP caching. Note that Rack::Cache will be used if you use #expires_in, #fresh_when or #stale with :public => true. Otherwise, the caching rules will apply to the browser only. [Yehuda Katz, Carl Lerche]
*Rails 3.0.0 (August 29, 2010)*
@@ -187,15 +187,17 @@ def recycle!
end
end
- class TestSession < ActionDispatch::Session::AbstractStore::SessionHash #:nodoc:
- DEFAULT_OPTIONS = ActionDispatch::Session::AbstractStore::DEFAULT_OPTIONS
+ class TestSession < Rack::Session::Abstract::SessionHash #:nodoc:
+ DEFAULT_OPTIONS = Rack::Session::Abstract::ID::DEFAULT_OPTIONS
def initialize(session = {})
replace(session.stringify_keys)
@loaded = true
end
- def exists?; true; end
+ def exists?
+ true
+ end
end
# Superclass for ActionController functional tests. Functional tests allow you to
@@ -18,11 +18,6 @@ def protocol
@protocol ||= ssl? ? 'https://' : 'http://'
end
- # Is this an SSL request?
- def ssl?
- @ssl ||= @env['HTTPS'] == 'on' || @env['HTTP_X_FORWARDED_PROTO'] == 'https'
- end
-
# Returns the \host for this request, such as "example.com".
def raw_host_with_port
if forwarded = env["HTTP_X_FORWARDED_HOST"]
@@ -1,5 +1,6 @@
require 'rack/utils'
require 'rack/request'
+require 'rack/session/abstract/id'
require 'action_dispatch/middleware/cookies'
require 'active_support/core_ext/object/blank'
@@ -8,252 +9,69 @@ module Session
class SessionRestoreError < StandardError #:nodoc:
end
- class AbstractStore
- ENV_SESSION_KEY = 'rack.session'.freeze
- ENV_SESSION_OPTIONS_KEY = 'rack.session.options'.freeze
-
- # thin wrapper around Hash that allows us to lazily
- # load session id into session_options
- class OptionsHash < Hash
- def initialize(by, env, default_options)
- @by = by
- @env = env
- @session_id_loaded = false
- merge!(default_options)
- end
-
- def [](key)
- if key == :id
- load_session_id! unless key?(:id) || has_session_id?
- end
- super
- end
-
- private
-
- def has_session_id?
- @session_id_loaded
- end
-
- def load_session_id!
- self[:id] = @by.send(:extract_session_id, @env)
- @session_id_loaded = true
- end
- end
-
- class SessionHash < Hash
- def initialize(by, env)
- super()
- @by = by
- @env = env
- @loaded = false
- end
-
- def [](key)
- load_for_read!
- super(key.to_s)
- end
-
- def has_key?(key)
- load_for_read!
- super(key.to_s)
- end
-
- def []=(key, value)
- load_for_write!
- super(key.to_s, value)
- end
-
- def clear
- load_for_write!
- super
- end
-
- def to_hash
- load_for_read!
- h = {}.replace(self)
- h.delete_if { |k,v| v.nil? }
- h
- end
-
- def update(hash)
- load_for_write!
- super(hash.stringify_keys)
- end
-
- def delete(key)
- load_for_write!
- super(key.to_s)
- end
-
- def inspect
- load_for_read!
- super
- end
-
- def exists?
- return @exists if instance_variable_defined?(:@exists)
- @exists = @by.send(:exists?, @env)
- end
-
- def loaded?
- @loaded
- end
-
- def destroy
- clear
- @by.send(:destroy, @env) if defined?(@by) && @by
- @env[ENV_SESSION_OPTIONS_KEY][:id] = nil if defined?(@env) && @env && @env[ENV_SESSION_OPTIONS_KEY]
- @loaded = false
- end
-
- private
-
- def load_for_read!
- load! if !loaded? && exists?
- end
-
- def load_for_write!
- load! unless loaded?
- end
-
- def load!
- id, session = @by.send(:load_session, @env)
- @env[ENV_SESSION_OPTIONS_KEY][:id] = id
- replace(session.stringify_keys)
- @loaded = true
- end
-
+ module DestroyableSession
+ def destroy
+ clear
+ options = @env[Rack::Session::Abstract::ENV_SESSION_OPTIONS_KEY] if @env
+ options ||= {}
+ @by.send(:destroy_session, @env, options[:id], options) if @by
+ options[:id] = nil
+ @loaded = false
end
+ end
- DEFAULT_OPTIONS = {
- :key => '_session_id',
- :path => '/',
- :domain => nil,
- :expire_after => nil,
- :secure => false,
- :httponly => true,
- :cookie_only => true
- }
+ ::Rack::Session::Abstract::SessionHash.send :include, DestroyableSession
+ module Compatibility
def initialize(app, options = {})
- @app = app
- @default_options = DEFAULT_OPTIONS.merge(options)
- @key = @default_options.delete(:key).freeze
- @cookie_only = @default_options.delete(:cookie_only)
- ensure_session_key!
+ options[:key] ||= '_session_id'
+ super
end
- def call(env)
- prepare!(env)
- response = @app.call(env)
-
- session_data = env[ENV_SESSION_KEY]
- options = env[ENV_SESSION_OPTIONS_KEY]
-
- if !session_data.is_a?(AbstractStore::SessionHash) || session_data.loaded? || options[:expire_after]
- request = ActionDispatch::Request.new(env)
-
- return response if (options[:secure] && !request.ssl?)
-
- session_data.send(:load!) if session_data.is_a?(AbstractStore::SessionHash) && !session_data.loaded?
-
- sid = options[:id] || generate_sid
- session_data = session_data.to_hash
-
- value = set_session(env, sid, session_data)
- return response unless value
-
- cookie = { :value => value }
- if options[:expire_after]
- cookie[:expires] = Time.now + options.delete(:expire_after)
- end
-
- set_cookie(request, cookie.merge!(options))
- end
-
- response
+ def generate_sid
+ ActiveSupport::SecureRandom.hex(16)
end
+ end
- private
-
- def prepare!(env)
- env[ENV_SESSION_KEY] = SessionHash.new(self, env)
- env[ENV_SESSION_OPTIONS_KEY] = OptionsHash.new(self, env, @default_options)
- end
-
- def generate_sid
- ActiveSupport::SecureRandom.hex(16)
- end
-
- def set_cookie(request, options)
- if request.cookie_jar[@key] != options[:value] || !options[:expires].nil?
- request.cookie_jar[@key] = options
- end
- end
-
- def load_session(env)
- stale_session_check! do
- sid = current_session_id(env)
- sid, session = get_session(env, sid)
- [sid, session]
- end
- end
-
- def extract_session_id(env)
- stale_session_check! do
- request = ActionDispatch::Request.new(env)
- sid = request.cookies[@key]
- sid ||= request.params[@key] unless @cookie_only
- sid
- end
- end
-
- def current_session_id(env)
- env[ENV_SESSION_OPTIONS_KEY][:id]
- end
+ module StaleSessionCheck
+ def load_session(env)
+ stale_session_check! { super }
+ end
- def ensure_session_key!
- if @key.blank?
- raise ArgumentError, 'A key is required to write a ' +
- 'cookie containing the session data. Use ' +
- 'config.session_store SESSION_STORE, { :key => ' +
- '"_myapp_session" } in config/application.rb'
- end
- end
+ def extract_session_id(env)
+ stale_session_check! { super }
+ 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 ActionDispatch::Session::SessionRestoreError, "Session contains objects whose class definition isn't available.\nRemember to require the classes for all objects kept in the session.\n(Original exception: #{const_error.message} [#{const_error.class}])\n"
- end
- retry
- else
- raise
+ 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 ActionDispatch::Session::SessionRestoreError, "Session contains objects whose class definition isn't available.\nRemember to require the classes for all objects kept in the session.\n(Original exception: #{const_error.message} [#{const_error.class}])\n"
end
+ retry
+ else
+ raise
end
+ end
+ end
- def exists?(env)
- current_session_id(env).present?
- end
-
- def get_session(env, sid)
- raise '#get_session needs to be implemented.'
- end
+ class AbstractStore < Rack::Session::Abstract::ID
+ include Compatibility
+ include StaleSessionCheck
- def set_session(env, sid, session_data)
- raise '#set_session needs to be implemented and should return ' <<
- 'the value to be stored in the cookie (usually the sid)'
- end
+ def destroy_session(env, sid, options)
+ ActiveSupport::Deprecation.warn "Implementing #destroy in session stores is deprecated. " <<
+ "Please implement destroy_session(env, session_id, options) instead."
+ destroy(env)
+ end
- def destroy(env)
- raise '#destroy needs to be implemented.'
- end
+ def destroy(env)
+ raise '#destroy needs to be implemented.'
+ end
end
end
end
Oops, something went wrong.

6 comments on commit 50215f9

@rolfb

This comment has been minimized.

Show comment Hide comment
@rolfb

rolfb Oct 4, 2010

Contributor

This breaks.

Uninitialized constant Rack::Session::Abstract::SessionHash (NameError)

Contributor

rolfb replied Oct 4, 2010

This breaks.

Uninitialized constant Rack::Session::Abstract::SessionHash (NameError)

@josevalim

This comment has been minimized.

Show comment Hide comment
@josevalim

josevalim Oct 4, 2010

Contributor

If you are using Rails master, you need to link to Rack git repo form your Gemfile.

Contributor

josevalim replied Oct 4, 2010

If you are using Rails master, you need to link to Rack git repo form your Gemfile.

@rolfb

This comment has been minimized.

Show comment Hide comment
@rolfb

rolfb Oct 4, 2010

Contributor

Indeed, I was too quick to comment.
About to update my Gemfile to use previous commit, and noticed I hadn't specified 3-0-stable as the branch.

Sorry about that.

Contributor

rolfb replied Oct 4, 2010

Indeed, I was too quick to comment.
About to update my Gemfile to use previous commit, and noticed I hadn't specified 3-0-stable as the branch.

Sorry about that.

@dtrasbo

This comment has been minimized.

Show comment Hide comment
@dtrasbo

dtrasbo Oct 8, 2010

Contributor

Why does this commit remove the ssl? method from ActionDispatch::Http::Url? Other methods in there seems to depend on it.

Contributor

dtrasbo replied Oct 8, 2010

Why does this commit remove the ssl? method from ActionDispatch::Http::Url? Other methods in there seems to depend on it.

@josevalim

This comment has been minimized.

Show comment Hide comment
@josevalim

josevalim Oct 8, 2010

Contributor

Don't fear! ssl? is now in Rack!

Contributor

josevalim replied Oct 8, 2010

Don't fear! ssl? is now in Rack!

@dtrasbo

This comment has been minimized.

Show comment Hide comment
@dtrasbo

dtrasbo Oct 8, 2010

Contributor

Ah, ok. I'm asking because of this ticket: https://rails.lighthouseapp.com/projects/8994/tickets/5750-requestssl-should-reflect-rackurl_scheme

I've already marked it as invalid, because as I said there might be a perfectly good reason. :)

Contributor

dtrasbo replied Oct 8, 2010

Ah, ok. I'm asking because of this ticket: https://rails.lighthouseapp.com/projects/8994/tickets/5750-requestssl-should-reflect-rackurl_scheme

I've already marked it as invalid, because as I said there might be a perfectly good reason. :)

Please sign in to comment.