mongo_rack 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/HISTORY +2 -0
- data/README.rdoc +61 -0
- data/Rakefile +31 -0
- data/fred.rb +26 -0
- data/lib/core_ext/hash.rb +6 -0
- data/lib/mongo_rack.rb +159 -0
- data/lib/mongo_rack/session_hash.rb +144 -0
- data/spec/mongo_rack_spec.rb +207 -0
- data/spec/session_hash_spec.rb +42 -0
- data/spec/spec_helper.rb +20 -0
- data/tasks/bones.rake +20 -0
- data/tasks/gem.rake +202 -0
- data/tasks/git.rake +40 -0
- data/tasks/notes.rake +27 -0
- data/tasks/post_load.rake +32 -0
- data/tasks/rdoc.rake +54 -0
- data/tasks/setup.rb +292 -0
- data/tasks/spec.rake +54 -0
- data/tasks/svn.rake +47 -0
- metadata +115 -0
data/HISTORY
ADDED
data/README.rdoc
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
== mongo_rack
|
2
|
+
|
3
|
+
mongoDB based session management
|
4
|
+
|
5
|
+
== DESCRIPTION:
|
6
|
+
|
7
|
+
A mongoDB based rackable session store. Can be used with any rack based web frameworks.
|
8
|
+
|
9
|
+
== PROJECT INFORMATION
|
10
|
+
|
11
|
+
* Developer: Fernand Galiana
|
12
|
+
* Git: git://github.com/derailed/mongo-rack.git
|
13
|
+
|
14
|
+
== REQUIREMENTS:
|
15
|
+
|
16
|
+
* rack 1.0 or later
|
17
|
+
* mongo + mongo_ext ruby adapter 0.18 or later
|
18
|
+
|
19
|
+
== INSTALL:
|
20
|
+
|
21
|
+
* sudo gem install mongo_rack
|
22
|
+
|
23
|
+
== USAGE:
|
24
|
+
|
25
|
+
use Rack::Session::Mongo, { :server => 'localhost:27017/mongo_ses/sessions }
|
26
|
+
|
27
|
+
where server requires the following format
|
28
|
+
|
29
|
+
{server_name_or_ip}:{port}/{database_name}/{collection_name}
|
30
|
+
|
31
|
+
The server description by default will be localhost:2701/mongo_session/sessions
|
32
|
+
|
33
|
+
Other options includes:
|
34
|
+
|
35
|
+
pool_size ( default is 1 )
|
36
|
+
pool_timeout ( defaults to 1 )
|
37
|
+
|
38
|
+
== LICENSE:
|
39
|
+
|
40
|
+
(The MIT License)
|
41
|
+
|
42
|
+
Copyright (c) 2009
|
43
|
+
|
44
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
45
|
+
a copy of this software and associated documentation files (the
|
46
|
+
'Software'), to deal in the Software without restriction, including
|
47
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
48
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
49
|
+
permit persons to whom the Software is furnished to do so, subject to
|
50
|
+
the following conditions:
|
51
|
+
|
52
|
+
The above copyright notice and this permission notice shall be
|
53
|
+
included in all copies or substantial portions of the Software.
|
54
|
+
|
55
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
56
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
57
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
58
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
59
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
60
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
61
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
begin
|
2
|
+
require 'bones'
|
3
|
+
Bones.setup
|
4
|
+
rescue LoadError
|
5
|
+
begin
|
6
|
+
load 'tasks/setup.rb'
|
7
|
+
rescue LoadError
|
8
|
+
raise RuntimeError, '### please install the "bones" gem ###'
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
ensure_in_path 'lib'
|
13
|
+
require 'mongo_rack'
|
14
|
+
|
15
|
+
task :default => 'spec:run'
|
16
|
+
|
17
|
+
PROJ.name = 'mongo_rack'
|
18
|
+
PROJ.authors = 'Fernand Galiana'
|
19
|
+
PROJ.email = 'fernand.galiana@gmail.com'
|
20
|
+
PROJ.url = 'http://github.com/derailed/mongo_rack'
|
21
|
+
PROJ.summary = "Rackable mongoDB based session management"
|
22
|
+
PROJ.version = "0.0.1"
|
23
|
+
PROJ.ruby_opts = %w[-W0]
|
24
|
+
PROJ.readme = 'README.rdoc'
|
25
|
+
PROJ.rcov.opts = ["--sort", "coverage", "-T"]
|
26
|
+
PROJ.spec.opts << '--color'
|
27
|
+
|
28
|
+
# Dependencies
|
29
|
+
depend_on "rack" , ">= 1.0.0"
|
30
|
+
depend_on "mongo" , ">= 0.18.1"
|
31
|
+
depend_on "mongo_ext", ">= 0.18.1"
|
data/fred.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# tnum = 10
|
2
|
+
# r = Array.new(tnum) do
|
3
|
+
# Thread.new do
|
4
|
+
# puts "Making request"
|
5
|
+
# 10
|
6
|
+
# end
|
7
|
+
# end.reverse.map{|t| puts t.inspect;t.join; puts t.value }
|
8
|
+
#
|
9
|
+
# r.each do |res|
|
10
|
+
# puts "Checking request #{res}"
|
11
|
+
# end
|
12
|
+
|
13
|
+
|
14
|
+
a = Array.new( 10 ) do
|
15
|
+
Thread.new( 20 ) do |run|
|
16
|
+
puts "Blee + #{run}"
|
17
|
+
20
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
puts a.inspect
|
22
|
+
|
23
|
+
b = a.reverse.map{ |t| t.join.value }
|
24
|
+
|
25
|
+
puts "Here"
|
26
|
+
puts b.inspect
|
data/lib/mongo_rack.rb
ADDED
@@ -0,0 +1,159 @@
|
|
1
|
+
require 'rack/session/abstract/id'
|
2
|
+
require 'mongo'
|
3
|
+
require File.join( File.dirname(__FILE__), %w[mongo_rack session_hash.rb] )
|
4
|
+
|
5
|
+
module Rack
|
6
|
+
module Session
|
7
|
+
class Mongo < Abstract::ID
|
8
|
+
attr_reader :mutex, :connection, :db, :sessions #:nodoc:
|
9
|
+
|
10
|
+
# === Options for mongo_rack
|
11
|
+
# :server ::
|
12
|
+
# Specifies server, port, db and collection location. Defaults
|
13
|
+
# to localhost:27017/mongo_session/sessions. Format must conform to
|
14
|
+
# the format {host}:{port}/{database_name}/{collection_name}.
|
15
|
+
# :pool_size ::
|
16
|
+
# The connection socket pool size - see mongo-ruby-driver docs for settings.
|
17
|
+
# Defaults to 1 connection.
|
18
|
+
# :pool_timeout ::
|
19
|
+
# The connection pool timeout. see mongo-ruby-driver docs for settings.
|
20
|
+
# Defaults to 1 sec.
|
21
|
+
DEFAULT_OPTIONS = Abstract::ID::DEFAULT_OPTIONS.merge \
|
22
|
+
:server => 'localhost:27017/mongo_session/sessions',
|
23
|
+
:pool_size => 1,
|
24
|
+
:pool_timeout => 1.0
|
25
|
+
|
26
|
+
# Initializes mongo_rack. Pass in options for default override.
|
27
|
+
def initialize(app, options={})
|
28
|
+
super
|
29
|
+
|
30
|
+
host, port, db_name, cltn_name = parse_server_desc( @default_options[:server] )
|
31
|
+
|
32
|
+
@mutex = Mutex.new
|
33
|
+
@connection = ::Mongo::Connection.new( host, port,
|
34
|
+
:pool_size => @default_options[:pool_size],
|
35
|
+
:timeout => @default_options[:pool_timeout] )
|
36
|
+
@db = @connection.db( db_name )
|
37
|
+
@sessions = @db[cltn_name]
|
38
|
+
end
|
39
|
+
|
40
|
+
# Fetch session with optional session id. Retrieve session from mongodb if any
|
41
|
+
def get_session( env, sid )
|
42
|
+
return _get_session( env, sid ) unless env['rack.multithread']
|
43
|
+
mutex.synchronize do
|
44
|
+
return _get_session( env, sid )
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Update session params and sync to mongoDB.
|
49
|
+
def set_session( env, sid, new_session, options )
|
50
|
+
return _set_session( env, sid, new_session, options ) unless env['rack.multithread']
|
51
|
+
mutex.synchronize do
|
52
|
+
return _set_session( env, sid, new_session, options )
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# =======================================================================
|
57
|
+
private
|
58
|
+
|
59
|
+
# Generates unique session id
|
60
|
+
def generate_sid
|
61
|
+
loop do
|
62
|
+
sid = super
|
63
|
+
break sid unless sessions.find_one( { :_id => sid } )
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Check session expiration date
|
68
|
+
def fresh?( ses_obj )
|
69
|
+
return true if ses_obj['expire'] == 0
|
70
|
+
now = Time.now
|
71
|
+
ses_obj['expire'] >= now
|
72
|
+
end
|
73
|
+
|
74
|
+
# Clean out all expired sessions
|
75
|
+
def clean_expired!
|
76
|
+
sessions.remove( { :expire => { '$lt' => Time.now } } )
|
77
|
+
end
|
78
|
+
|
79
|
+
# parse server description string into host, port, db, cltn
|
80
|
+
def parse_server_desc( desc )
|
81
|
+
tokens = desc.split( "/" )
|
82
|
+
raise "Invalid server description" unless tokens.size == 3
|
83
|
+
server_desc = tokens[0].split( ":" )
|
84
|
+
raise "Invalid host:port description" unless server_desc.size == 2
|
85
|
+
return server_desc.first, server_desc.last.to_i, tokens[1], tokens[2]
|
86
|
+
end
|
87
|
+
|
88
|
+
# fetch session with optional session id
|
89
|
+
def _get_session(env, sid)
|
90
|
+
if sid
|
91
|
+
ses_obj = sessions.find_one( { :_id => sid } )
|
92
|
+
session = MongoRack::SessionHash.new( ses_obj['data'] ) if ses_obj and fresh?( ses_obj )
|
93
|
+
end
|
94
|
+
|
95
|
+
unless sid and session
|
96
|
+
env['rack.errors'].puts("Session '#{sid.inspect}' not found, initializing...") if $VERBOSE and not sid.nil?
|
97
|
+
session = {}
|
98
|
+
sid = generate_sid
|
99
|
+
ret = sessions.save( { :_id => sid, :data => session } )
|
100
|
+
raise "Session collision on '#{sid.inspect}'" unless ret
|
101
|
+
end
|
102
|
+
session.instance_variable_set( '@old', MongoRack::SessionHash.new.merge(session) )
|
103
|
+
return [sid, session]
|
104
|
+
rescue => boom
|
105
|
+
warn "#{self} is unable to find server."
|
106
|
+
warn $!.inspect
|
107
|
+
return [ nil, {} ]
|
108
|
+
end
|
109
|
+
|
110
|
+
# update session information with new settings
|
111
|
+
def _set_session(env, sid, new_session, options)
|
112
|
+
ses_obj = sessions.find_one( { :_id => sid } )
|
113
|
+
if ses_obj
|
114
|
+
session = MongoRack::SessionHash.new( ses_obj['data'] )
|
115
|
+
else
|
116
|
+
session = MongoRack::SessionHash.new
|
117
|
+
end
|
118
|
+
|
119
|
+
if options[:renew] or options[:drop]
|
120
|
+
sessions.remove( { :_id => sid } )
|
121
|
+
return false if options[:drop]
|
122
|
+
sid = generate_sid
|
123
|
+
sessions.insert( {:_id => sid, :data => {} } )
|
124
|
+
end
|
125
|
+
old_session = new_session.instance_variable_get('@old') || MongoRack::SessionHash.new
|
126
|
+
merged = merge_sessions( sid, old_session, new_session, session )
|
127
|
+
|
128
|
+
expiry = options[:expire_after]
|
129
|
+
expiry = expiry ? Time.now + options[:expire_after] : 0
|
130
|
+
|
131
|
+
# BOZO ! Use upserts here if minor changes ?
|
132
|
+
sessions.save( { :_id => sid, :data => merged, :expire => expiry } )
|
133
|
+
return sid
|
134
|
+
rescue => boom
|
135
|
+
warn "#{self} is unable to find server."
|
136
|
+
warn $!.inspect
|
137
|
+
return false
|
138
|
+
end
|
139
|
+
|
140
|
+
# merge old, new to current session state
|
141
|
+
def merge_sessions( sid, old_s, new_s, cur={} )
|
142
|
+
unless Hash === old_s and Hash === new_s
|
143
|
+
warn 'Bad old or new sessions provided.'
|
144
|
+
return cur
|
145
|
+
end
|
146
|
+
|
147
|
+
delete = old_s.keys - new_s.keys
|
148
|
+
warn "//@#{sid}: delete #{delete*','}" if $VERBOSE and not delete.empty?
|
149
|
+
delete.each{ |k| cur.delete(k) }
|
150
|
+
|
151
|
+
update = new_s.keys.select{ |k| new_s[k] != old_s[k] }
|
152
|
+
warn "//@#{sid}: update #{update*','}" if $VERBOSE and not update.empty?
|
153
|
+
update.each{ |k| cur[k] = new_s[k] }
|
154
|
+
|
155
|
+
cur
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
# Snagged HashWithIndifferentAccess for A/S
|
2
|
+
module MongoRack
|
3
|
+
class SessionHash < Hash
|
4
|
+
|
5
|
+
# Need to enable users to access session using either a symbol or a string as key
|
6
|
+
# This call wraps hash to provide this kind of access. No default allowed here. If a key
|
7
|
+
# is not found nil will be returned.
|
8
|
+
def initialize(constructor = {})
|
9
|
+
if constructor.is_a?(Hash)
|
10
|
+
super(constructor)
|
11
|
+
update(constructor)
|
12
|
+
self.default = nil
|
13
|
+
else
|
14
|
+
super(constructor)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# Checks for default value. If key does not exits returns default for hash
|
19
|
+
def default(key = nil)
|
20
|
+
if key.is_a?(Symbol) && include?(key = key.to_s)
|
21
|
+
self[key]
|
22
|
+
else
|
23
|
+
super
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
alias_method :regular_writer, :[]= unless method_defined?(:regular_writer)
|
28
|
+
alias_method :regular_update, :update unless method_defined?(:regular_update)
|
29
|
+
|
30
|
+
# Assigns a new value to the hash:
|
31
|
+
#
|
32
|
+
# hash = SessionHash.new
|
33
|
+
# hash[:key] = "value"
|
34
|
+
#
|
35
|
+
def []=(key, value)
|
36
|
+
regular_writer(convert_key(key), convert_value(value))
|
37
|
+
end
|
38
|
+
|
39
|
+
# Updates the instantized hash with values from the second:
|
40
|
+
#
|
41
|
+
# hash_1 = SessionHash.new
|
42
|
+
# hash_1[:key] = "value"
|
43
|
+
#
|
44
|
+
# hash_2 = SessionHash.new
|
45
|
+
# hash_2[:key] = "New Value!"
|
46
|
+
#
|
47
|
+
# hash_1.update(hash_2) # => {"key"=>"New Value!"}
|
48
|
+
#
|
49
|
+
def update(other_hash)
|
50
|
+
other_hash.each_pair { |key, value| regular_writer(convert_key(key), convert_value(value)) }
|
51
|
+
self
|
52
|
+
end
|
53
|
+
|
54
|
+
alias_method :merge!, :update
|
55
|
+
|
56
|
+
# Checks the hash for a key matching the argument passed in:
|
57
|
+
#
|
58
|
+
# hash = SessionHash.new
|
59
|
+
# hash["key"] = "value"
|
60
|
+
# hash.key? :key # => true
|
61
|
+
# hash.key? "key" # => true
|
62
|
+
#
|
63
|
+
def key?(key)
|
64
|
+
super(convert_key(key))
|
65
|
+
end
|
66
|
+
|
67
|
+
alias_method :include?, :key?
|
68
|
+
alias_method :has_key?, :key?
|
69
|
+
alias_method :member?, :key?
|
70
|
+
|
71
|
+
# Fetches the value for the specified key, same as doing hash[key]
|
72
|
+
def fetch(key, *extras)
|
73
|
+
super(convert_key(key), *extras)
|
74
|
+
end
|
75
|
+
|
76
|
+
# Returns an array of the values at the specified indices:
|
77
|
+
#
|
78
|
+
# hash = SessionHash.new
|
79
|
+
# hash[:a] = "x"
|
80
|
+
# hash[:b] = "y"
|
81
|
+
# hash.values_at("a", "b") # => ["x", "y"]
|
82
|
+
#
|
83
|
+
def values_at(*indices)
|
84
|
+
indices.collect {|key| self[convert_key(key)]}
|
85
|
+
end
|
86
|
+
|
87
|
+
# Returns an exact copy of the hash.
|
88
|
+
def dup
|
89
|
+
SessionHash.new(self)
|
90
|
+
end
|
91
|
+
|
92
|
+
# Merges the instantized and the specified hashes together, giving precedence to the values from the second hash
|
93
|
+
# Does not overwrite the existing hash.
|
94
|
+
def merge(hash)
|
95
|
+
self.dup.update(hash)
|
96
|
+
end
|
97
|
+
|
98
|
+
# Removes a specified key from the hash.
|
99
|
+
def delete(key)
|
100
|
+
super(convert_key(key))
|
101
|
+
end
|
102
|
+
|
103
|
+
#:nodoc:
|
104
|
+
def stringify_keys!; self end
|
105
|
+
#:nodoc:
|
106
|
+
def symbolize_keys!; self end
|
107
|
+
#:nodoc:
|
108
|
+
def to_options!; self end
|
109
|
+
|
110
|
+
# Convert to a Hash with String keys.
|
111
|
+
def to_hash
|
112
|
+
Hash.new(default).merge(self)
|
113
|
+
end
|
114
|
+
|
115
|
+
# =========================================================================
|
116
|
+
private
|
117
|
+
|
118
|
+
# converts key to string if symbol
|
119
|
+
def convert_key(key)
|
120
|
+
key.kind_of?(Symbol) ? key.to_s : key
|
121
|
+
end
|
122
|
+
|
123
|
+
# check value and converts sub obj to session hash if any
|
124
|
+
def convert_value(value)
|
125
|
+
case value
|
126
|
+
when Hash
|
127
|
+
value.with_session_access
|
128
|
+
when Array
|
129
|
+
value.collect { |e| e.is_a?(Hash) ? e.with_session_access : e }
|
130
|
+
else
|
131
|
+
value
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
module MongoRack
|
138
|
+
module SessionAccess
|
139
|
+
def with_session_access
|
140
|
+
hash = MongoRack::SessionHash.new( self )
|
141
|
+
hash
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
@@ -0,0 +1,207 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), %w[spec_helper])
|
2
|
+
|
3
|
+
describe Rack::Session::Mongo do
|
4
|
+
before :all do
|
5
|
+
@session_key = 'rack.session'
|
6
|
+
@session_match = /#{@session_key}=[0-9a-fA-F]+;/
|
7
|
+
@db_name = 'mongo_test'
|
8
|
+
@cltn_name = 'sessions'
|
9
|
+
|
10
|
+
@con = Mongo::Connection.new
|
11
|
+
@db = @con.db( @db_name )
|
12
|
+
@sessions = @db['sessions']
|
13
|
+
|
14
|
+
@incrementor = lambda do |env|
|
15
|
+
env[@session_key]['counter'] ||= 0
|
16
|
+
env[@session_key]['counter'] += 1
|
17
|
+
Rack::Response.new( env[@session_key].inspect ).to_a
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should connect to a valid server" do
|
22
|
+
Rack::Session::Mongo.new( @incrementor, :server => "localhost:27017/#{@db_name}/#{@cltn_name}" )
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should fail if bad server specified" do
|
26
|
+
lambda do
|
27
|
+
Rack::Session::Mongo.new( @incrementor, :server => "blee:1111/#{@db_name}/#{@cltn_name}" )
|
28
|
+
end.should raise_error( Mongo::ConnectionFailure )
|
29
|
+
end
|
30
|
+
|
31
|
+
describe "cookies" do
|
32
|
+
before :each do
|
33
|
+
@pool = Rack::Session::Mongo.new( @incrementor, :server => "localhost:27017/#{@db_name}/#{@cltn_name}" )
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should create a new cookie correctly" do
|
37
|
+
res = Rack::MockRequest.new( @pool ).get( "/", 'rack.multithread' => false )
|
38
|
+
res['Set-Cookie'].should match( /^#{@session_key}=/ )
|
39
|
+
res.body.should == '{"counter"=>1}'
|
40
|
+
session_id = res['Set-Cookie'].match( /^#{@session_key}=(.*?);.*?/ )[1]
|
41
|
+
mongo_check( res, :counter, 1 )
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should determine a session from a cookie" do
|
45
|
+
req = Rack::MockRequest.new( @pool )
|
46
|
+
res = req.get("/", 'rack.multithread' => false )
|
47
|
+
cookie = res["Set-Cookie"]
|
48
|
+
req.get("/", "HTTP_COOKIE" => cookie, 'rack.multithread' => false ).body.should == '{"counter"=>2}'
|
49
|
+
mongo_check( res, :counter, 2 )
|
50
|
+
req.get("/", "HTTP_COOKIE" => cookie, 'rack.multithread' => false ).body.should == '{"counter"=>3}'
|
51
|
+
mongo_check( res, :counter, 3 )
|
52
|
+
end
|
53
|
+
|
54
|
+
it "survives nonexistant cookies" do
|
55
|
+
bad_cookie = "rack.session=bumblebeetuna"
|
56
|
+
res = Rack::MockRequest.new( @pool ).get("/", "HTTP_COOKIE" => bad_cookie, 'rack.multithread' => false )
|
57
|
+
res.body.should == '{"counter"=>1}'
|
58
|
+
cookie = res["Set-Cookie"][@session_match]
|
59
|
+
cookie.should_not match( /#{bad_cookie}/ )
|
60
|
+
end
|
61
|
+
|
62
|
+
it "maintains freshness" do
|
63
|
+
pool = Rack::Session::Mongo.new( @incrementor, :server => "localhost:27017/#{@db_name}/#{@cltn_name}", :expire_after => 1 )
|
64
|
+
res = Rack::MockRequest.new(pool).get('/', 'rack.multithread' => false )
|
65
|
+
res.body.should include('"counter"=>1')
|
66
|
+
cookie = res["Set-Cookie"]
|
67
|
+
res = Rack::MockRequest.new(pool).get('/', "HTTP_COOKIE" => cookie, 'rack.multithread' => false )
|
68
|
+
res["Set-Cookie"].should == cookie
|
69
|
+
res.body.should include('"counter"=>2')
|
70
|
+
puts 'Sleeping to expire session' if $DEBUG
|
71
|
+
sleep 2
|
72
|
+
res = Rack::MockRequest.new(pool).get('/', "HTTP_COOKIE" => cookie, 'rack.multithread' => false )
|
73
|
+
res["Set-Cookie"].should_not == cookie
|
74
|
+
res.body.should include( '"counter"=>1' )
|
75
|
+
end
|
76
|
+
|
77
|
+
it "deletes cookies with :drop option" do
|
78
|
+
drop_session = lambda do |env|
|
79
|
+
env['rack.session.options'][:drop] = true
|
80
|
+
@incrementor.call(env)
|
81
|
+
end
|
82
|
+
|
83
|
+
req = Rack::MockRequest.new(@pool)
|
84
|
+
drop = Rack::Utils::Context.new(@pool, drop_session)
|
85
|
+
dreq = Rack::MockRequest.new(drop)
|
86
|
+
|
87
|
+
res0 = req.get("/", 'rack.multithread' => false )
|
88
|
+
session = (cookie = res0["Set-Cookie"])[@session_match]
|
89
|
+
res0.body.should == '{"counter"=>1}'
|
90
|
+
|
91
|
+
res1 = req.get("/", "HTTP_COOKIE" => cookie, 'rack.multithread' => false )
|
92
|
+
res1["Set-Cookie"][@session_match].should == session
|
93
|
+
res1.body.should == '{"counter"=>2}'
|
94
|
+
|
95
|
+
res2 = dreq.get("/", "HTTP_COOKIE" => cookie, 'rack.multithread' => false )
|
96
|
+
res2["Set-Cookie"].should == nil
|
97
|
+
res2.body.should == '{"counter"=>3}'
|
98
|
+
|
99
|
+
res3 = req.get("/", "HTTP_COOKIE" => cookie, 'rack.multithread' => false)
|
100
|
+
res3["Set-Cookie"][@session_match].should_not == session
|
101
|
+
res3.body.should == '{"counter"=>1}'
|
102
|
+
end
|
103
|
+
|
104
|
+
it "provides new session id with :renew option" do
|
105
|
+
renew_session = lambda do |env|
|
106
|
+
env['rack.session.options'][:renew] = true
|
107
|
+
@incrementor.call(env)
|
108
|
+
end
|
109
|
+
|
110
|
+
req = Rack::MockRequest.new(@pool)
|
111
|
+
renew = Rack::Utils::Context.new(@pool, renew_session)
|
112
|
+
rreq = Rack::MockRequest.new(renew)
|
113
|
+
|
114
|
+
res0 = req.get("/", 'rack.multithread' => false )
|
115
|
+
session = (cookie = res0["Set-Cookie"])[@session_match]
|
116
|
+
res0.body.should == '{"counter"=>1}'
|
117
|
+
|
118
|
+
res1 = req.get("/", "HTTP_COOKIE" => cookie, 'rack.multithread' => false )
|
119
|
+
res1["Set-Cookie"][@session_match].should == session
|
120
|
+
res1.body.should == '{"counter"=>2}'
|
121
|
+
|
122
|
+
res2 = rreq.get("/", "HTTP_COOKIE" => cookie, 'rack.multithread' => false )
|
123
|
+
new_cookie = res2["Set-Cookie"]
|
124
|
+
new_session = new_cookie[@session_match]
|
125
|
+
new_session.should_not == session
|
126
|
+
res2.body.should == '{"counter"=>3}'
|
127
|
+
|
128
|
+
res3 = req.get("/", "HTTP_COOKIE" => new_cookie, 'rack.multithread' => false )
|
129
|
+
res3["Set-Cookie"][@session_match].should == new_session
|
130
|
+
res3.body.should == '{"counter"=>4}'
|
131
|
+
end
|
132
|
+
|
133
|
+
it "omits cookie with :defer option" do
|
134
|
+
defer_session = lambda do |env|
|
135
|
+
env['rack.session.options'][:defer] = true
|
136
|
+
@incrementor.call(env)
|
137
|
+
end
|
138
|
+
|
139
|
+
req = Rack::MockRequest.new(@pool)
|
140
|
+
defer = Rack::Utils::Context.new(@pool, defer_session)
|
141
|
+
dreq = Rack::MockRequest.new(defer)
|
142
|
+
|
143
|
+
res0 = req.get("/", 'rack.multithread' => false )
|
144
|
+
session = (cookie = res0["Set-Cookie"])[@session_match]
|
145
|
+
res0.body.should == '{"counter"=>1}'
|
146
|
+
|
147
|
+
res1 = req.get("/", "HTTP_COOKIE" => cookie, 'rack.multithread' => false )
|
148
|
+
res1["Set-Cookie"][@session_match].should == session
|
149
|
+
res1.body.should == '{"counter"=>2}'
|
150
|
+
|
151
|
+
res2 = dreq.get("/", "HTTP_COOKIE" => cookie, 'rack.multithread' => false )
|
152
|
+
res2["Set-Cookie"].should == nil
|
153
|
+
res2.body.should == '{"counter"=>3}'
|
154
|
+
|
155
|
+
res3 = req.get("/", "HTTP_COOKIE" => cookie, 'rack.multithread' => false )
|
156
|
+
res3["Set-Cookie"][@session_match].should == session
|
157
|
+
res3.body.should == '{"counter"=>4}'
|
158
|
+
end
|
159
|
+
|
160
|
+
it "multithread: should cleanly merge sessions" do
|
161
|
+
@pool = Rack::Session::Mongo.new( @incrementor, :server => "localhost:27017/#{@db_name}/#{@cltn_name}", :pool_size => 10 )
|
162
|
+
|
163
|
+
req = Rack::MockRequest.new( @pool )
|
164
|
+
|
165
|
+
res = req.get('/')
|
166
|
+
res.body.should == '{"counter"=>1}'
|
167
|
+
cookie = res["Set-Cookie"]
|
168
|
+
sess_id = cookie[/#{@pool.key}=([^,;]+)/,1]
|
169
|
+
|
170
|
+
r = Array.new( 10 ) do
|
171
|
+
Thread.new( req ) do |run|
|
172
|
+
req.get( "/", "HTTP_COOKIE" => cookie, 'rack.multithread' => true )
|
173
|
+
end
|
174
|
+
end.reverse.map{ |t| t.join.value }
|
175
|
+
|
176
|
+
r.each do |res|
|
177
|
+
res['Set-Cookie'].should == cookie
|
178
|
+
res.body.should include( '"counter"=>2' )
|
179
|
+
end
|
180
|
+
|
181
|
+
drop_counter = proc do |env|
|
182
|
+
env['rack.session'].delete 'counter'
|
183
|
+
env['rack.session']['foo'] = 'bar'
|
184
|
+
[200, {'Content-Type'=>'text/plain'}, env['rack.session'].inspect]
|
185
|
+
end
|
186
|
+
tses = Rack::Utils::Context.new @pool, drop_counter
|
187
|
+
treq = Rack::MockRequest.new( tses )
|
188
|
+
|
189
|
+
tnum = 10
|
190
|
+
r = Array.new(tnum) do
|
191
|
+
Thread.new(treq) do |run|
|
192
|
+
run.get('/', "HTTP_COOKIE" => cookie, 'rack.multithread' => true)
|
193
|
+
end
|
194
|
+
end.reverse.map{|t| t.join.value }
|
195
|
+
r.each do |res|
|
196
|
+
res['Set-Cookie'].should == cookie
|
197
|
+
res.body.should include('"foo"=>"bar"')
|
198
|
+
end
|
199
|
+
|
200
|
+
session = @pool.sessions.find_one( {:_id => sess_id } )
|
201
|
+
session['data'].size.should == 1
|
202
|
+
session['data']['counter'].should be_nil
|
203
|
+
session['data']['foo'].should == 'bar'
|
204
|
+
end
|
205
|
+
|
206
|
+
end
|
207
|
+
end
|