Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

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

…by world.
  • Loading branch information...
commit 50215f9525b6b5e3bfe703724b9f68177ed8565d 1 parent 5836af8
@josevalim josevalim authored
View
1  Gemfile
@@ -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
6 actionpack/CHANGELOG
@@ -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)*
View
8 actionpack/lib/action_controller/test_case.rb
@@ -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
View
5 actionpack/lib/action_dispatch/http/url.rb
@@ -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"]
View
280 actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
@@ -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
View
64 actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
@@ -1,5 +1,7 @@
require 'active_support/core_ext/hash/keys'
require 'active_support/core_ext/object/blank'
+require 'action_dispatch/middleware/session/abstract_store'
+require 'rack/session/cookie'
module ActionDispatch
module Session
@@ -38,58 +40,32 @@ module Session
# "rake secret" and set the key in config/initializers/secret_token.rb.
#
# Note that changing digest or secret invalidates all existing sessions!
- class CookieStore < AbstractStore
-
- def initialize(app, options = {})
- super(app, options.merge!(:cookie_only => true))
- freeze
- end
+ class CookieStore < Rack::Session::Cookie
+ include Compatibility
+ include StaleSessionCheck
private
- def load_session(env)
- data = unpacked_cookie_data(env)
- data = persistent_session_id!(data)
- [data["session_id"], data]
- end
-
- def extract_session_id(env)
- if data = unpacked_cookie_data(env)
- data["session_id"]
- else
- nil
- end
- end
-
- def unpacked_cookie_data(env)
- env["action_dispatch.request.unsigned_session_cookie"] ||= begin
- stale_session_check! do
- request = ActionDispatch::Request.new(env)
- if data = request.cookie_jar.signed[@key]
- data.stringify_keys!
- end
- data || {}
+ def unpacked_cookie_data(env)
+ env["action_dispatch.request.unsigned_session_cookie"] ||= begin
+ stale_session_check! do
+ request = ActionDispatch::Request.new(env)
+ if data = request.cookie_jar.signed[@key]
+ data.stringify_keys!
end
+ data || {}
end
end
+ end
- def set_cookie(request, options)
- request.cookie_jar.signed[@key] = options
- end
-
- def set_session(env, sid, session_data)
- persistent_session_id!(session_data, sid)
- end
-
- def destroy(env)
- # session data is stored on client; nothing to do here
- end
+ def set_session(env, sid, session_data, options)
+ persistent_session_id!(session_data, sid)
+ end
- def persistent_session_id!(data, sid=nil)
- data ||= {}
- data["session_id"] ||= sid || generate_sid
- data
- end
+ def set_cookie(env, session_id, cookie)
+ request = ActionDispatch::Request.new(env)
+ request.cookie_jar.signed[@key] = cookie
+ end
end
end
end
View
53 actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb
@@ -1,56 +1,17 @@
+require 'action_dispatch/middleware/session/abstract_store'
+require 'rack/session/memcache'
+
module ActionDispatch
module Session
- class MemCacheStore < AbstractStore
+ class MemCacheStore < Rack::Session::Memcache
+ include Compatibility
+ include StaleSessionCheck
+
def initialize(app, options = {})
require 'memcache'
-
- # Support old :expires option
options[:expire_after] ||= options[:expires]
-
- super
-
- @default_options = {
- :namespace => 'rack:session',
- :memcache_server => 'localhost:11211'
- }.merge(@default_options)
-
- @pool = options[:cache] || 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
-
super
end
-
- private
- def get_session(env, sid)
- sid ||= generate_sid
- begin
- session = @pool.get(sid) || {}
- rescue MemCache::MemCacheError, Errno::ECONNREFUSED
- session = {}
- end
- [sid, session]
- end
-
- def set_session(env, sid, session_data)
- options = env['rack.session.options']
- expiry = options[:expire_after] || 0
- @pool.set(sid, session_data, expiry)
- sid
- rescue MemCache::MemCacheError, Errno::ECONNREFUSED
- false
- end
-
- def destroy(env)
- if sid = current_session_id(env)
- @pool.delete(sid)
- end
- rescue MemCache::MemCacheError, Errno::ECONNREFUSED
- false
- end
-
end
end
end
View
12 actionpack/test/dispatch/session/cookie_store_test.rb
@@ -53,18 +53,6 @@ def change_session_id
def rescue_action(e) raise end
end
- def test_raises_argument_error_if_missing_session_key
- assert_raise(ArgumentError, nil.inspect) {
- ActionDispatch::Session::CookieStore.new(nil,
- :key => nil, :secret => SessionSecret)
- }
-
- assert_raise(ArgumentError, ''.inspect) {
- ActionDispatch::Session::CookieStore.new(nil,
- :key => '', :secret => SessionSecret)
- }
- end
-
def test_setting_session_value
with_test_route_set do
get '/set_session_value'
View
7 activerecord/lib/active_record/session_store.rb
@@ -288,6 +288,7 @@ def destroy
self.session_class = Session
SESSION_RECORD_KEY = 'rack.session.record'
+ ENV_SESSION_OPTIONS_KEY = Rack::Session::Abstract::ENV_SESSION_OPTIONS_KEY
private
def get_session(env, sid)
@@ -299,7 +300,7 @@ def get_session(env, sid)
end
end
- def set_session(env, sid, session_data)
+ def set_session(env, sid, session_data, options)
Base.silence do
record = get_session_model(env, sid)
record.data = session_data
@@ -316,12 +317,14 @@ def set_session(env, sid, session_data)
sid
end
- def destroy(env)
+ def destroy_session(env, session_id, options)
if sid = current_session_id(env)
Base.silence do
get_session_model(env, sid).destroy
end
end
+
+ generate_sid unless options[:drop]
end
def get_session_model(env, sid)

6 comments on commit 50215f9

@rolfb

This breaks.

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

@josevalim
Owner

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

@rolfb

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

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

@josevalim
Owner

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

@dtrasbo

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.
Something went wrong with that request. Please try again.