mongo-store 0.1.0
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/.document +5 -0
- data/.gitignore +22 -0
- data/LICENSE +20 -0
- data/README.rdoc +24 -0
- data/Rakefile +38 -0
- data/VERSION +1 -0
- data/lib/mongo-store.rb +4 -0
- data/lib/rack/session/mongo.rb +147 -0
- data/spec/mongo-store_spec.rb +238 -0
- data/spec/spec.opts +2 -0
- data/spec/spec_helper.rb +14 -0
- metadata +125 -0
data/.document
ADDED
data/.gitignore
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010 Jonathan Rudenberg
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
= mongo-store
|
2
|
+
|
3
|
+
Rack session store for MongoDB. See the docs[http://rdoc.info/projects/titanous/mongo-store] for more usage details.
|
4
|
+
|
5
|
+
== Installation
|
6
|
+
gem install mongo-store
|
7
|
+
|
8
|
+
== Usage
|
9
|
+
use Rack::Session::Mongo, :connection => @existing_mongodb_connection,
|
10
|
+
:expire_after => 1800
|
11
|
+
|
12
|
+
== Note on Patches/Pull Requests
|
13
|
+
|
14
|
+
* Fork the project.
|
15
|
+
* Make your feature addition or bug fix.
|
16
|
+
* Add tests for it. This is important so I don't break it in a
|
17
|
+
future version unintentionally.
|
18
|
+
* Commit, do not mess with rakefile, version, or history.
|
19
|
+
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
20
|
+
* Send me a pull request. Bonus points for topic branches.
|
21
|
+
|
22
|
+
== Copyright
|
23
|
+
|
24
|
+
Copyright (c) 2010 Jonathan Rudenberg. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'jeweler'
|
4
|
+
require 'spec/rake/spectask'
|
5
|
+
require 'yard'
|
6
|
+
|
7
|
+
Jeweler::Tasks.new do |gem|
|
8
|
+
gem.name = 'mongo-store'
|
9
|
+
gem.summary = 'Rack session store for MongoDB'
|
10
|
+
gem.email = 'jonathan@titanous.com'
|
11
|
+
gem.homepage = 'http://github.com/titanous/mongo-store'
|
12
|
+
gem.authors = ['Jonathan Rudenberg']
|
13
|
+
gem.add_dependency 'mongo'
|
14
|
+
gem.add_dependency 'rack', '~> 1.1.0'
|
15
|
+
gem.add_development_dependency 'rspec', '>= 1.2.9'
|
16
|
+
gem.add_development_dependency 'yard', '>= 0'
|
17
|
+
end
|
18
|
+
|
19
|
+
Jeweler::GemcutterTasks.new
|
20
|
+
|
21
|
+
Spec::Rake::SpecTask.new(:spec) do |spec|
|
22
|
+
spec.libs << 'lib' << 'spec'
|
23
|
+
spec.spec_files = FileList['spec/**/*_spec.rb']
|
24
|
+
end
|
25
|
+
|
26
|
+
Spec::Rake::SpecTask.new(:rcov) do |spec|
|
27
|
+
spec.libs << 'lib' << 'spec'
|
28
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
29
|
+
spec.rcov = true
|
30
|
+
end
|
31
|
+
|
32
|
+
task :spec => :check_dependencies
|
33
|
+
|
34
|
+
task :default => :spec
|
35
|
+
|
36
|
+
YARD::Rake::YardocTask.new(:doc) do |t|
|
37
|
+
t.options = ['--legacy'] if RUBY_VERSION < '1.9.0'
|
38
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
data/lib/mongo-store.rb
ADDED
@@ -0,0 +1,147 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'mongo'
|
3
|
+
require 'rack/session/abstract/id'
|
4
|
+
|
5
|
+
module Rack
|
6
|
+
module Session
|
7
|
+
# Implements the +Rack::Session::Abstract::ID+ session store interface
|
8
|
+
#
|
9
|
+
# Cookies sent to the client for maintaining sessions will only contain an
|
10
|
+
# id reference. See {Rack::Session::Mongo#initialize below} for options.
|
11
|
+
#
|
12
|
+
# == Usage Example
|
13
|
+
#
|
14
|
+
# use Rack::Session::Mongo, :connection => @existing_mongodb_connection,
|
15
|
+
# :expire_after => 1800
|
16
|
+
class Mongo < Abstract::ID
|
17
|
+
attr_reader :mutex, :pool, :connection
|
18
|
+
DEFAULT_OPTIONS = Abstract::ID::DEFAULT_OPTIONS.merge :db => 'rack', :collection => 'sessions', :drop => false
|
19
|
+
|
20
|
+
# Creates a new Mongo session store pool. You probably won't initialize
|
21
|
+
# it his way. See the overview for standard usage instructions.
|
22
|
+
#
|
23
|
+
# Unless specified, the options can be set on a per request basis, in the
|
24
|
+
# +rack.session.options+ environment hash. Additionally the id of
|
25
|
+
# the session can be found within the options hash at the key +:id+. It is
|
26
|
+
# highly not recommended to change its value.
|
27
|
+
#
|
28
|
+
# @param app a Rack application
|
29
|
+
# @param [Hash] options configuration for the session pool
|
30
|
+
# @option options [Mongo::Connection] :connection (Mongo::Connection.new)
|
31
|
+
# used to create the pool. Change this if you already have a connection
|
32
|
+
# setup, or want to connect to a server other than +localhost+.
|
33
|
+
# — <i>pool instance global</i>
|
34
|
+
# @option options [String] :db ('rack') the Mongo db to use — <i>pool
|
35
|
+
# instance global</i>
|
36
|
+
# @option options [String] :collection ('sessions') the Mongo collection
|
37
|
+
# to use. — <i>pool instance global</i>
|
38
|
+
# @option options [Integer] :expire_after (nil) the time in seconds for
|
39
|
+
# the session to last for. *Example:* If this is set to +1800+, the
|
40
|
+
# session will be deleted if the client doesn't make a request within 30
|
41
|
+
# minutes of its last request.
|
42
|
+
# @option options [true, false] :defer (false) don't set the session
|
43
|
+
# cookie for this request.
|
44
|
+
# @option options [true, false] :renew (false) causes the generation of
|
45
|
+
# a new session id, and migrates the data to id. Overrides +:defer+.
|
46
|
+
# @option options [true, false] :drop (false) destroys the current
|
47
|
+
# session, and creates a new one.
|
48
|
+
# @option options [String] :key ('rack.session') the name of the cookie
|
49
|
+
# that stores the session id
|
50
|
+
# @option options [String] :path ('/') the cookie path
|
51
|
+
# @option options [String] :domain (nil) the cookie domain
|
52
|
+
# @option options [true, false] :secure (false) the cookie security flag;
|
53
|
+
# tells the client to only send the cookie over HTTPS.
|
54
|
+
# @option options [true, false] :httponly (true) the cookie HttpOnly flag;
|
55
|
+
# makes the cookie invisible to client-side Javascript.
|
56
|
+
def initialize(app, options = {})
|
57
|
+
super
|
58
|
+
@mutex = Mutex.new
|
59
|
+
@connection = @default_options[:connection] || ::Mongo::Connection.new
|
60
|
+
@pool = @connection.db(@default_options[:db]).collection(@default_options[:collection])
|
61
|
+
@pool.create_index('sid', true)
|
62
|
+
end
|
63
|
+
|
64
|
+
def get_session(env, sid)
|
65
|
+
@mutex.lock if env['rack.multithread']
|
66
|
+
session = find_session(sid) if sid
|
67
|
+
unless sid and session
|
68
|
+
env['rack.errors'].puts("Session '#{sid}' not found, initializing...") if $VERBOSE and not sid.nil?
|
69
|
+
session = {}
|
70
|
+
sid = generate_sid
|
71
|
+
save_session(sid)
|
72
|
+
end
|
73
|
+
session.instance_variable_set('@old', {}.merge(session))
|
74
|
+
return [sid, session]
|
75
|
+
ensure
|
76
|
+
@mutex.unlock if env['rack.multithread']
|
77
|
+
end
|
78
|
+
|
79
|
+
def set_session(env, sid, new_session, options)
|
80
|
+
@mutex.lock if env['rack.multithread']
|
81
|
+
expires = Time.now + options[:expire_after] if !options[:expire_after].nil?
|
82
|
+
session = find_session(sid) || {}
|
83
|
+
if options[:renew] or options[:drop]
|
84
|
+
delete_session(sid)
|
85
|
+
return false if options[:drop]
|
86
|
+
sid = generate_sid
|
87
|
+
save_session(sid, session, expires)
|
88
|
+
end
|
89
|
+
old_session = new_session.instance_variable_get('@old') || {}
|
90
|
+
session = merge_sessions(sid, old_session, new_session, session)
|
91
|
+
save_session(sid, session, expires)
|
92
|
+
return sid
|
93
|
+
ensure
|
94
|
+
@mutex.unlock if env['rack.multithread']
|
95
|
+
end
|
96
|
+
|
97
|
+
private
|
98
|
+
def generate_sid
|
99
|
+
loop do
|
100
|
+
sid = super
|
101
|
+
break sid unless find_session(sid)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def find_session(sid)
|
106
|
+
@pool.remove :expires => {'$lte' => Time.now} # clean out expired sessions
|
107
|
+
session = @pool.find_one :sid => sid
|
108
|
+
session ? unpack(session['data']) : false
|
109
|
+
end
|
110
|
+
|
111
|
+
def delete_session(sid)
|
112
|
+
@pool.remove :sid => sid
|
113
|
+
end
|
114
|
+
|
115
|
+
def save_session(sid, session={}, expires=nil)
|
116
|
+
@pool.update({:sid => sid}, {:sid => sid, :data => pack(session), :expires => expires}, :upsert => true)
|
117
|
+
end
|
118
|
+
|
119
|
+
def merge_sessions(sid, old, new, current=nil)
|
120
|
+
current ||= {}
|
121
|
+
unless Hash === old and Hash === new
|
122
|
+
warn 'Bad old or new sessions provided.'
|
123
|
+
return current
|
124
|
+
end
|
125
|
+
|
126
|
+
delete = old.keys - new.keys
|
127
|
+
warn "//@#{sid}: dropping #{delete*','}" if $DEBUG and not delete.empty?
|
128
|
+
delete.each{|k| current.delete k }
|
129
|
+
|
130
|
+
update = new.keys.select{|k| new[k] != old[k] }
|
131
|
+
warn "//@#{sid}: updating #{update*','}" if $DEBUG and not update.empty?
|
132
|
+
update.each{|k| current[k] = new[k] }
|
133
|
+
|
134
|
+
current
|
135
|
+
end
|
136
|
+
|
137
|
+
def pack(data)
|
138
|
+
[Marshal.dump(data)].pack("m*")
|
139
|
+
end
|
140
|
+
|
141
|
+
def unpack(packed)
|
142
|
+
return nil unless packed
|
143
|
+
Marshal.load(packed.unpack("m*").first)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
@@ -0,0 +1,238 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
|
4
|
+
describe 'Rack::Session::Mongo' do
|
5
|
+
before(:each) do
|
6
|
+
@session_key = Rack::Session::Mongo::DEFAULT_OPTIONS[:key]
|
7
|
+
@session_match = /#{@session_key}=[0-9a-fA-F]+;/
|
8
|
+
@incrementor = lambda do |env|
|
9
|
+
env['rack.session']['counter'] ||= 0
|
10
|
+
env['rack.session']['counter'] += 1
|
11
|
+
Rack::Response.new(env['rack.session'].inspect).to_a
|
12
|
+
end
|
13
|
+
@drop_session = proc do |env|
|
14
|
+
env['rack.session.options'][:drop] = true
|
15
|
+
@incrementor.call(env)
|
16
|
+
end
|
17
|
+
@renew_session = proc do |env|
|
18
|
+
env['rack.session.options'][:renew] = true
|
19
|
+
@incrementor.call(env)
|
20
|
+
end
|
21
|
+
@defer_session = proc do |env|
|
22
|
+
env['rack.session.options'][:defer] = true
|
23
|
+
@incrementor.call(env)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'should specify connection params' do
|
28
|
+
mongo = Rack::Session::Mongo.new(@incrementor,
|
29
|
+
:connection => Mongo::Connection.new('localhost'), :db => 'rack-test',
|
30
|
+
:collection => 'mongo-test')
|
31
|
+
pool = mongo.pool
|
32
|
+
connection = mongo.connection
|
33
|
+
|
34
|
+
connection.host.should == 'localhost'
|
35
|
+
|
36
|
+
pool.should be_kind_of(Mongo::Collection)
|
37
|
+
pool.db.name.should == 'rack-test'
|
38
|
+
pool.name.should == 'mongo-test'
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'creates a new cookie' do
|
42
|
+
pool = Rack::Session::Mongo.new(@incrementor)
|
43
|
+
res = Rack::MockRequest.new(pool).get('/')
|
44
|
+
res['Set-Cookie'].should match(/#{@session_key}=/)
|
45
|
+
res.body.should == '{"counter"=>1}'
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'determines session from a cookie' do
|
49
|
+
pool = Rack::Session::Mongo.new(@incrementor)
|
50
|
+
req = Rack::MockRequest.new(pool)
|
51
|
+
res = req.get('/')
|
52
|
+
cookie = res['Set-Cookie']
|
53
|
+
req.get('/', 'HTTP_COOKIE' => cookie).
|
54
|
+
body.should == '{"counter"=>2}'
|
55
|
+
req.get('/', 'HTTP_COOKIE' => cookie).
|
56
|
+
body.should == '{"counter"=>3}'
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'survives nonexistant cookies' do
|
60
|
+
bad_cookie = 'rack.session=blarghfasel'
|
61
|
+
pool = Rack::Session::Mongo.new(@incrementor)
|
62
|
+
res = Rack::MockRequest.new(pool).
|
63
|
+
get('/', 'HTTP_COOKIE' => bad_cookie)
|
64
|
+
res.body.should == '{"counter"=>1}'
|
65
|
+
cookie = res['Set-Cookie'][@session_match]
|
66
|
+
cookie.should_not match(/#{bad_cookie}/)
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'should maintain freshness' do
|
70
|
+
pool = Rack::Session::Mongo.new(@incrementor, :expire_after => 3)
|
71
|
+
res = Rack::MockRequest.new(pool).get('/')
|
72
|
+
res.body.should include('"counter"=>1')
|
73
|
+
cookie = res['Set-Cookie']
|
74
|
+
res = Rack::MockRequest.new(pool).get('/', 'HTTP_COOKIE' => cookie)
|
75
|
+
res['Set-Cookie'].should == cookie
|
76
|
+
res.body.should include('"counter"=>2')
|
77
|
+
puts 'Sleeping to expire session' if $DEBUG
|
78
|
+
sleep 4
|
79
|
+
res = Rack::MockRequest.new(pool).get('/', 'HTTP_COOKIE' => cookie)
|
80
|
+
res['Set-Cookie'].should_not == cookie
|
81
|
+
res.body.should include('"counter"=>1')
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'deletes cookies with :drop option' do
|
85
|
+
pool = Rack::Session::Mongo.new(@incrementor)
|
86
|
+
req = Rack::MockRequest.new(pool)
|
87
|
+
drop = Rack::Utils::Context.new(pool, @drop_session)
|
88
|
+
dreq = Rack::MockRequest.new(drop)
|
89
|
+
|
90
|
+
res0 = req.get('/')
|
91
|
+
session = (cookie = res0['Set-Cookie'])[@session_match]
|
92
|
+
res0.body.should == '{"counter"=>1}'
|
93
|
+
|
94
|
+
res1 = req.get('/', 'HTTP_COOKIE' => cookie)
|
95
|
+
res1['Set-Cookie'][@session_match].should == session
|
96
|
+
res1.body.should == '{"counter"=>2}'
|
97
|
+
|
98
|
+
res2 = dreq.get('/', 'HTTP_COOKIE' => cookie)
|
99
|
+
res2['Set-Cookie'].should be_nil
|
100
|
+
res2.body.should == '{"counter"=>3}'
|
101
|
+
|
102
|
+
res3 = req.get('/', 'HTTP_COOKIE' => cookie)
|
103
|
+
res3['Set-Cookie'][@session_match].should_not == session
|
104
|
+
res3.body.should == '{"counter"=>1}'
|
105
|
+
end
|
106
|
+
|
107
|
+
it 'provides new session id with :renew option' do
|
108
|
+
pool = Rack::Session::Mongo.new(@incrementor)
|
109
|
+
req = Rack::MockRequest.new(pool)
|
110
|
+
renew = Rack::Utils::Context.new(pool, @renew_session)
|
111
|
+
rreq = Rack::MockRequest.new(renew)
|
112
|
+
|
113
|
+
res0 = req.get('/')
|
114
|
+
session = (cookie = res0['Set-Cookie'])[@session_match]
|
115
|
+
res0.body.should == '{"counter"=>1}'
|
116
|
+
|
117
|
+
res1 = req.get('/', 'HTTP_COOKIE' => cookie)
|
118
|
+
res1['Set-Cookie'][@session_match].should == session
|
119
|
+
res1.body.should == '{"counter"=>2}'
|
120
|
+
|
121
|
+
res2 = rreq.get('/', 'HTTP_COOKIE' => cookie)
|
122
|
+
new_cookie = res2['Set-Cookie']
|
123
|
+
new_session = new_cookie[@session_match]
|
124
|
+
new_session.should_not == session
|
125
|
+
res2.body.should == '{"counter"=>3}'
|
126
|
+
|
127
|
+
res3 = req.get('/', 'HTTP_COOKIE' => new_cookie)
|
128
|
+
res3['Set-Cookie'][@session_match].should == new_session
|
129
|
+
res3.body.should == '{"counter"=>4}'
|
130
|
+
end
|
131
|
+
|
132
|
+
specify 'omits cookie with :defer option' do
|
133
|
+
pool = Rack::Session::Mongo.new(@incrementor)
|
134
|
+
req = Rack::MockRequest.new(pool)
|
135
|
+
defer = Rack::Utils::Context.new(pool, @defer_session)
|
136
|
+
dreq = Rack::MockRequest.new(defer)
|
137
|
+
|
138
|
+
res0 = req.get('/')
|
139
|
+
session = (cookie = res0['Set-Cookie'])[@session_match]
|
140
|
+
res0.body.should == '{"counter"=>1}'
|
141
|
+
|
142
|
+
res1 = req.get('/', 'HTTP_COOKIE' => cookie)
|
143
|
+
res1['Set-Cookie'][@session_match].should == session
|
144
|
+
res1.body.should == '{"counter"=>2}'
|
145
|
+
|
146
|
+
res2 = dreq.get('/', 'HTTP_COOKIE' => cookie)
|
147
|
+
res2['Set-Cookie'].should be_nil
|
148
|
+
res2.body.should == '{"counter"=>3}'
|
149
|
+
|
150
|
+
res3 = req.get('/', 'HTTP_COOKIE' => cookie)
|
151
|
+
res3['Set-Cookie'][@session_match].should == session
|
152
|
+
res3.body.should == '{"counter"=>4}'
|
153
|
+
end
|
154
|
+
|
155
|
+
# anyone know how to do this better?
|
156
|
+
specify 'multithread: should cleanly merge sessions' do
|
157
|
+
next unless $DEBUG
|
158
|
+
warn 'Running multithread test for Session::Mongo'
|
159
|
+
pool = Rack::Session::Mongo.new(@incrementor)
|
160
|
+
req = Rack::MockRequest.new(pool)
|
161
|
+
|
162
|
+
res = req.get('/')
|
163
|
+
res.body.should == '{"counter"=>1}'
|
164
|
+
cookie = res['Set-Cookie']
|
165
|
+
sess_id = cookie[/#{pool.key}=([^,;]+)/,1]
|
166
|
+
|
167
|
+
delta_incrementor = lambda do |env|
|
168
|
+
# emulate disconjoinment of threading
|
169
|
+
env['rack.session'] = env['rack.session'].dup
|
170
|
+
Thread.stop
|
171
|
+
env['rack.session'][(Time.now.usec*rand).to_i] = true
|
172
|
+
@incrementor.call(env)
|
173
|
+
end
|
174
|
+
tses = Rack::Utils::Context.new pool, delta_incrementor
|
175
|
+
treq = Rack::MockRequest.new(tses)
|
176
|
+
tnum = rand(7).to_i+5
|
177
|
+
r = Array.new(tnum) do
|
178
|
+
Thread.new(treq) do |run|
|
179
|
+
run.get('/', 'HTTP_COOKIE' => cookie, 'rack.multithread' => true)
|
180
|
+
end
|
181
|
+
end.reverse.map{|t| t.run.join.value }
|
182
|
+
r.each do |res|
|
183
|
+
res['Set-Cookie'].should == cookie
|
184
|
+
res.body.should include('"counter"=>2')
|
185
|
+
end
|
186
|
+
|
187
|
+
session = pool.pool.get(sess_id)
|
188
|
+
session.size.should == tnum+1 # counter
|
189
|
+
session['counter'].should == 2 # meeeh
|
190
|
+
|
191
|
+
tnum = rand(7).to_i+5
|
192
|
+
r = Array.new(tnum) do |i|
|
193
|
+
delta_time = proc do |env|
|
194
|
+
env['rack.session'][i] = Time.now
|
195
|
+
Thread.stop
|
196
|
+
env['rack.session'] = env['rack.session'].dup
|
197
|
+
env['rack.session'][i] -= Time.now
|
198
|
+
@incrementor.call(env)
|
199
|
+
end
|
200
|
+
app = Rack::Utils::Context.new pool, time_delta
|
201
|
+
req = Rack::MockRequest.new app
|
202
|
+
Thread.new(req) do |run|
|
203
|
+
run.get('/', 'HTTP_COOKIE' => cookie, 'rack.multithread' => true)
|
204
|
+
end
|
205
|
+
end.reverse.map{|t| t.run.join.value }
|
206
|
+
r.each do |res|
|
207
|
+
res['Set-Cookie'].should == cookie
|
208
|
+
res.body.should include('"counter"=>3')
|
209
|
+
end
|
210
|
+
|
211
|
+
session = pool.pool.get(sess_id)
|
212
|
+
session.size.should == tnum+1
|
213
|
+
session['counter'].should == 3
|
214
|
+
|
215
|
+
drop_counter = proc do |env|
|
216
|
+
env['rack.session'].delete 'counter'
|
217
|
+
env['rack.session']['foo'] = 'bar'
|
218
|
+
[200, {'Content-Type'=>'text/plain'}, env['rack.session'].inspect]
|
219
|
+
end
|
220
|
+
tses = Rack::Utils::Context.new pool, drop_counter
|
221
|
+
treq = Rack::MockRequest.new(tses)
|
222
|
+
tnum = rand(7).to_i+5
|
223
|
+
r = Array.new(tnum) do
|
224
|
+
Thread.new(treq) do |run|
|
225
|
+
run.get('/', 'HTTP_COOKIE' => cookie, 'rack.multithread' => true)
|
226
|
+
end
|
227
|
+
end.reverse.map{|t| t.run.join.value }
|
228
|
+
r.each do |res|
|
229
|
+
res['Set-Cookie'].should == cookie
|
230
|
+
res.body.should include('"foo"=>"bar"')
|
231
|
+
end
|
232
|
+
|
233
|
+
session = pool.pool.get(sess_id)
|
234
|
+
session.size.should == r.size+1
|
235
|
+
session['counter'].should be_nil
|
236
|
+
session['foo'].should == 'bar'
|
237
|
+
end
|
238
|
+
end
|
data/spec/spec.opts
ADDED
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
2
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
3
|
+
require 'mongo-store'
|
4
|
+
require 'spec'
|
5
|
+
require 'spec/autorun'
|
6
|
+
require 'rack/mock'
|
7
|
+
require 'rack/response'
|
8
|
+
require 'thread'
|
9
|
+
|
10
|
+
Spec::Runner.configure do |config|
|
11
|
+
config.before do
|
12
|
+
Mongo::Connection.new.db('rack').collection('rack-sessions').drop
|
13
|
+
end
|
14
|
+
end
|
metadata
ADDED
@@ -0,0 +1,125 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mongo-store
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
version: 0.1.0
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Jonathan Rudenberg
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-02-23 00:00:00 -05:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: mongo
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
segments:
|
28
|
+
- 0
|
29
|
+
version: "0"
|
30
|
+
type: :runtime
|
31
|
+
version_requirements: *id001
|
32
|
+
- !ruby/object:Gem::Dependency
|
33
|
+
name: rack
|
34
|
+
prerelease: false
|
35
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - ~>
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
segments:
|
40
|
+
- 1
|
41
|
+
- 1
|
42
|
+
- 0
|
43
|
+
version: 1.1.0
|
44
|
+
type: :runtime
|
45
|
+
version_requirements: *id002
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: rspec
|
48
|
+
prerelease: false
|
49
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
segments:
|
54
|
+
- 1
|
55
|
+
- 2
|
56
|
+
- 9
|
57
|
+
version: 1.2.9
|
58
|
+
type: :development
|
59
|
+
version_requirements: *id003
|
60
|
+
- !ruby/object:Gem::Dependency
|
61
|
+
name: yard
|
62
|
+
prerelease: false
|
63
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
segments:
|
68
|
+
- 0
|
69
|
+
version: "0"
|
70
|
+
type: :development
|
71
|
+
version_requirements: *id004
|
72
|
+
description:
|
73
|
+
email: jonathan@titanous.com
|
74
|
+
executables: []
|
75
|
+
|
76
|
+
extensions: []
|
77
|
+
|
78
|
+
extra_rdoc_files:
|
79
|
+
- LICENSE
|
80
|
+
- README.rdoc
|
81
|
+
files:
|
82
|
+
- .document
|
83
|
+
- .gitignore
|
84
|
+
- LICENSE
|
85
|
+
- README.rdoc
|
86
|
+
- Rakefile
|
87
|
+
- VERSION
|
88
|
+
- lib/mongo-store.rb
|
89
|
+
- lib/rack/session/mongo.rb
|
90
|
+
- spec/mongo-store_spec.rb
|
91
|
+
- spec/spec.opts
|
92
|
+
- spec/spec_helper.rb
|
93
|
+
has_rdoc: true
|
94
|
+
homepage: http://github.com/titanous/mongo-store
|
95
|
+
licenses: []
|
96
|
+
|
97
|
+
post_install_message:
|
98
|
+
rdoc_options:
|
99
|
+
- --charset=UTF-8
|
100
|
+
require_paths:
|
101
|
+
- lib
|
102
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
103
|
+
requirements:
|
104
|
+
- - ">="
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
segments:
|
107
|
+
- 0
|
108
|
+
version: "0"
|
109
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
110
|
+
requirements:
|
111
|
+
- - ">="
|
112
|
+
- !ruby/object:Gem::Version
|
113
|
+
segments:
|
114
|
+
- 0
|
115
|
+
version: "0"
|
116
|
+
requirements: []
|
117
|
+
|
118
|
+
rubyforge_project:
|
119
|
+
rubygems_version: 1.3.6
|
120
|
+
signing_key:
|
121
|
+
specification_version: 3
|
122
|
+
summary: Rack session store for MongoDB
|
123
|
+
test_files:
|
124
|
+
- spec/mongo-store_spec.rb
|
125
|
+
- spec/spec_helper.rb
|