rack-session-mongo 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/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ *.swp
data/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ language: ruby
2
+ script: "bacon -I./lib spec/spec_session_mongo.rb"
3
+ rvm:
4
+ - 1.8.7
5
+ - 1.9.2
6
+ - 1.9.3
7
+ - jruby
data/CHANGES.md ADDED
@@ -0,0 +1,6 @@
1
+ CHANGES
2
+ =======
3
+
4
+ ### [0.0.1](https://github.com/migrs/rack-session-mongo/tree/v0.0.1) / 2012-02-04
5
+
6
+ * Initial Release
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in rack-session-mongo.gemspec
4
+ gemspec
data/README.md ADDED
@@ -0,0 +1,46 @@
1
+ rack-session-mongo
2
+ ====================
3
+
4
+ Rack session store for MongoDB
5
+
6
+ <http://github.com/migrs/rack-session-mongo>
7
+
8
+ [![Build Status](https://secure.travis-ci.org/migrs/rack-session-mongo.png)](http://travis-ci.org/migrs/rack-session-mongo)
9
+
10
+ ## Installation
11
+
12
+ gem install rack-session-mongo
13
+
14
+ ## Usage
15
+
16
+ Simple (localhost:27017 db:sessions, collection:sessions)
17
+
18
+ use Rack::Session::Mongo
19
+
20
+ Set MongoDB connection
21
+
22
+ conn = Mongo::Coneection.new('myhost', 27017)
23
+ use Rack::Session::Mongo, conn
24
+
25
+ Set MongoDB instance
26
+
27
+ conn = Mongo::Coneection.new('myhost', 27017)
28
+ db = conn['myapp']
29
+ use Rack::Session::Mongo, db
30
+
31
+ Specify DB host with some config
32
+
33
+ use Rack::Session::Mongo, {
34
+ :host => 'myhost:27017',
35
+ :db_name => 'myapp',
36
+ :marshal_data => false,
37
+ :expire_after => 600
38
+ }
39
+
40
+ ## About MongoDB
41
+
42
+ - <http://www.mongodb.org/>
43
+ - <https://github.com/mongodb/mongo-ruby-driver>
44
+
45
+ ## License
46
+ [rack-session-mongo](http://github.com/migrs/rack-session-mongo) is Copyright (c) 2012 [Masato Igarashi](http://github.com/migrs)(@[migrs](http://twitter.com/migrs)) and distributed under the [MIT license](http://www.opensource.org/licenses/mit-license).
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,2 @@
1
+ require "rack/session/mongo"
2
+ require "rack-session-mongo/version"
@@ -0,0 +1,7 @@
1
+ module Rack
2
+ module Session
3
+ class Mongo
4
+ VERSION = "0.0.1"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,99 @@
1
+ require 'rack/session/abstract/id'
2
+ require 'mongo'
3
+
4
+ module Rack
5
+ module Session
6
+ class Mongo < Abstract::ID
7
+
8
+ attr_reader :mutex, :pool
9
+
10
+ DEFAULT_OPTIONS = Abstract::ID::DEFAULT_OPTIONS.merge \
11
+ :db_name => :sessions, :collection => :sessions, :marshal_data => true
12
+
13
+ def initialize(app, options={})
14
+ options = {:db => options } if options.is_a? ::Mongo::DB
15
+ options = {:connection => options } if options.is_a? ::Mongo::Connection
16
+ super
17
+ db = @default_options[:db] || begin
18
+ conn = @default_options[:connection] || begin
19
+ @default_options[:host] ? ::Mongo::Connection.new(*@default_options[:host].split(':')) : ::Mongo::Connection.new
20
+ end
21
+ conn[@default_options[:db_name].to_s]
22
+ end
23
+ @pool = db[@default_options[:collection].to_s]
24
+ @pool.create_index('sid', :unique => true)
25
+ @mutex = Mutex.new
26
+ end
27
+
28
+ def generate_sid
29
+ loop do
30
+ sid = super
31
+ break sid unless _exists? sid
32
+ end
33
+ end
34
+
35
+ def get_session(env, sid)
36
+ with_lock(env, [nil, {}]) do
37
+ unless sid and session = _get(sid)
38
+ sid, session = generate_sid, {}
39
+ _put sid, session
40
+ end
41
+ [sid, session]
42
+ end
43
+ end
44
+
45
+ def set_session(env, session_id, new_session, options)
46
+ with_lock(env, false) do
47
+ _put session_id, new_session
48
+ session_id
49
+ end
50
+ end
51
+
52
+ def destroy_session(env, session_id, options)
53
+ with_lock(env) do
54
+ _delete(session_id)
55
+ generate_sid unless options[:drop]
56
+ end
57
+ end
58
+
59
+ def with_lock(env, default=nil)
60
+ @mutex.lock if env['rack.multithread']
61
+ yield
62
+ rescue
63
+ default
64
+ ensure
65
+ @mutex.unlock if @mutex.locked?
66
+ end
67
+
68
+ private
69
+ def _put(sid, session)
70
+ @pool.update({ :sid => sid },
71
+ {"$set" => {:data => _pack(session), :updated_at => Time.now.utc}}, :upsert => true)
72
+ end
73
+
74
+ def _get(sid)
75
+ if doc = _exists?(sid)
76
+ _unpack(doc['data'])
77
+ end
78
+ end
79
+
80
+ def _delete(sid)
81
+ @pool.remove(:sid => sid)
82
+ end
83
+
84
+ def _exists?(sid)
85
+ @pool.find_one(:sid => sid)
86
+ end
87
+
88
+ def _pack(data)
89
+ return nil unless data
90
+ @default_options[:marshal_data] ? [Marshal.dump(data)].pack("m*") : data
91
+ end
92
+
93
+ def _unpack(packed)
94
+ return nil unless packed
95
+ @default_options[:marshal_data] ? Marshal.load(packed.unpack("m*").first) : packed
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "rack-session-mongo/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "rack-session-mongo"
7
+ s.version = Rack::Session::Mongo::VERSION
8
+ s.authors = ["Masato Igarashi"]
9
+ s.email = ["m@igrs.jp"]
10
+ s.homepage = "http://github.com/migrs/rack-session-mongo"
11
+ s.summary = %q{Rack session store for MongoDB}
12
+ s.description = %q{Rack session store for MongoDB}
13
+
14
+ #s.rubyforge_project = "rack-session-mongo"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ # specify any dependencies here; for example:
22
+ s.add_development_dependency "bacon"
23
+ s.add_runtime_dependency "rack"
24
+ s.add_runtime_dependency "mongo"
25
+ end
@@ -0,0 +1,212 @@
1
+ require 'thread'
2
+ require 'rack/mock'
3
+ require 'rack-session-mongo'
4
+ require 'fileutils'
5
+
6
+ describe Rack::Session::Mongo do
7
+ session_key = Rack::Session::Mongo::DEFAULT_OPTIONS[:key]
8
+ session_match = /#{session_key}=[0-9a-fA-F]+;/
9
+
10
+ incrementor = lambda do |env|
11
+ env["rack.session"]["counter"] ||= 0
12
+ env["rack.session"]["counter"] += 1
13
+ Rack::Response.new(env["rack.session"].inspect).to_a
14
+ end
15
+
16
+ session_id = lambda do |env|
17
+ Rack::Response.new(env["rack.session"].inspect).to_a
18
+ end
19
+
20
+ nothing = lambda do |env|
21
+ Rack::Response.new("Nothing").to_a
22
+ end
23
+
24
+ drop_session = lambda do |env|
25
+ env['rack.session.options'][:drop] = true
26
+ incrementor.call(env)
27
+ end
28
+
29
+ renew_session = lambda do |env|
30
+ env['rack.session.options'][:renew] = true
31
+ incrementor.call(env)
32
+ end
33
+
34
+ defer_session = lambda do |env|
35
+ env['rack.session.options'][:defer] = true
36
+ incrementor.call(env)
37
+ end
38
+
39
+ after do
40
+ pool = Rack::Session::Mongo.new(incrementor)
41
+ pool.pool.remove({})
42
+ end
43
+
44
+ it "creates a new cookie" do
45
+ pool = Rack::Session::Mongo.new(incrementor)
46
+ res = Rack::MockRequest.new(pool).get("/")
47
+ res["Set-Cookie"].should.match session_match
48
+ res.body.should.equal '{"counter"=>1}'
49
+ end
50
+
51
+ it "determines session from a cookie" do
52
+ pool = Rack::Session::Mongo.new(incrementor)
53
+ req = Rack::MockRequest.new(pool)
54
+ cookie = req.get("/")["Set-Cookie"]
55
+ req.get("/", "HTTP_COOKIE" => cookie).
56
+ body.should.equal '{"counter"=>2}'
57
+ req.get("/", "HTTP_COOKIE" => cookie).
58
+ body.should.equal '{"counter"=>3}'
59
+ end
60
+
61
+ it "survives nonexistant cookies" do
62
+ pool = Rack::Session::Mongo.new(incrementor)
63
+ res = Rack::MockRequest.new(pool).
64
+ get("/", "HTTP_COOKIE" => "#{session_key}=blarghfasel")
65
+ res.body.should.equal '{"counter"=>1}'
66
+ end
67
+
68
+ it "does not send the same session id if it did not change" do
69
+ pool = Rack::Session::Mongo.new(incrementor)
70
+ req = Rack::MockRequest.new(pool)
71
+
72
+ res0 = req.get("/")
73
+ cookie = res0["Set-Cookie"][session_match]
74
+ res0.body.should.equal '{"counter"=>1}'
75
+ pool.pool.count.should.equal 1
76
+
77
+ res1 = req.get("/", "HTTP_COOKIE" => cookie)
78
+ res1["Set-Cookie"].should.be.nil
79
+ res1.body.should.equal '{"counter"=>2}'
80
+ pool.pool.count.should.equal 1
81
+
82
+ res2 = req.get("/", "HTTP_COOKIE" => cookie)
83
+ res2["Set-Cookie"].should.be.nil
84
+ res2.body.should.equal '{"counter"=>3}'
85
+ pool.pool.count.should.equal 1
86
+ end
87
+
88
+ it "deletes cookies with :drop option" do
89
+ pool = Rack::Session::Mongo.new(incrementor)
90
+ req = Rack::MockRequest.new(pool)
91
+ drop = Rack::Utils::Context.new(pool, drop_session)
92
+ dreq = Rack::MockRequest.new(drop)
93
+
94
+ res1 = req.get("/")
95
+ session = (cookie = res1["Set-Cookie"])[session_match]
96
+ res1.body.should.equal '{"counter"=>1}'
97
+ pool.pool.count.should.equal 1
98
+
99
+ res2 = dreq.get("/", "HTTP_COOKIE" => cookie)
100
+ res2["Set-Cookie"].should.be.nil
101
+ res2.body.should.equal '{"counter"=>2}'
102
+ pool.pool.count.should.equal 0
103
+
104
+ res3 = req.get("/", "HTTP_COOKIE" => cookie)
105
+ res3["Set-Cookie"][session_match].should.not.equal session
106
+ res3.body.should.equal '{"counter"=>1}'
107
+ pool.pool.count.should.equal 1
108
+ end
109
+
110
+ it "provides new session id with :renew option" do
111
+ pool = Rack::Session::Mongo.new(incrementor)
112
+ req = Rack::MockRequest.new(pool)
113
+ renew = Rack::Utils::Context.new(pool, renew_session)
114
+ rreq = Rack::MockRequest.new(renew)
115
+
116
+ res1 = req.get("/")
117
+ session = (cookie = res1["Set-Cookie"])[session_match]
118
+ res1.body.should.equal '{"counter"=>1}'
119
+ pool.pool.count.should.equal 1
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.equal session
125
+ res2.body.should.equal '{"counter"=>2}'
126
+ pool.pool.count.should.equal 1
127
+
128
+ res3 = req.get("/", "HTTP_COOKIE" => new_cookie)
129
+ res3.body.should.equal '{"counter"=>3}'
130
+ pool.pool.count.should.equal 1
131
+
132
+ res4 = req.get("/", "HTTP_COOKIE" => cookie)
133
+ res4.body.should.equal '{"counter"=>1}'
134
+ pool.pool.count.should.equal 2
135
+ end
136
+
137
+ it "omits cookie with :defer option" do
138
+ pool = Rack::Session::Mongo.new(incrementor)
139
+ defer = Rack::Utils::Context.new(pool, defer_session)
140
+ dreq = Rack::MockRequest.new(defer)
141
+
142
+ res1 = dreq.get("/")
143
+ res1["Set-Cookie"].should.equal nil
144
+ res1.body.should.equal '{"counter"=>1}'
145
+ pool.pool.count.should.equal 1
146
+ end
147
+
148
+ # anyone know how to do this better?
149
+ it "should merge sessions when multithreaded" do
150
+ unless $DEBUG
151
+ 1.should.equal 1
152
+ next
153
+ end
154
+
155
+ warn 'Running multithread tests for Session::Mongo'
156
+ pool = Rack::Session::Mongo.new(incrementor)
157
+ req = Rack::MockRequest.new(pool)
158
+
159
+ res = req.get('/')
160
+ res.body.should.equal '{"counter"=>1}'
161
+ cookie = res["Set-Cookie"]
162
+ sess_id = cookie[/#{pool.key}=([^,;]+)/,1]
163
+
164
+ delta_incrementor = lambda do |env|
165
+ # emulate disconjoinment of threading
166
+ env['rack.session'] = env['rack.session'].dup
167
+ Thread.stop
168
+ env['rack.session'][(Time.now.usec*rand).to_i] = true
169
+ incrementor.call(env)
170
+ end
171
+ tses = Rack::Utils::Context.new pool, delta_incrementor
172
+ treq = Rack::MockRequest.new(tses)
173
+ tnum = rand(7).to_i+5
174
+ r = Array.new(tnum) do
175
+ Thread.new(treq) do |run|
176
+ run.get('/', "HTTP_COOKIE" => cookie, 'rack.multithread' => true)
177
+ end
178
+ end.reverse.map{|t| t.run.join.value }
179
+ r.each do |resp|
180
+ resp['Set-Cookie'].should.equal cookie
181
+ resp.body.should.include '"counter"=>2'
182
+ end
183
+
184
+ session = pool.pool[sess_id]
185
+ session.count.should.equal tnum+1 # counter
186
+ session['counter'].should.equal 2 # meeeh
187
+ end
188
+
189
+ it "does not return a cookie if cookie was not read/written" do
190
+ app = Rack::Session::Mongo.new(nothing)
191
+ res = Rack::MockRequest.new(app).get("/")
192
+ res["Set-Cookie"].should.be.nil
193
+ end
194
+
195
+ it "does not return a cookie if cookie was not written (only read)" do
196
+ app = Rack::Session::Mongo.new(session_id)
197
+ res = Rack::MockRequest.new(app).get("/")
198
+ res["Set-Cookie"].should.be.nil
199
+ end
200
+
201
+ it "returns even if not read/written if :expire_after is set" do
202
+ app = Rack::Session::Mongo.new(nothing, :expire_after => 3600)
203
+ res = Rack::MockRequest.new(app).get("/", 'rack.session' => {'not' => 'empty'})
204
+ res["Set-Cookie"].should.not.be.nil
205
+ end
206
+
207
+ it "returns no cookie if no data was written and no session was created previously, even if :expire_after is set" do
208
+ app = Rack::Session::Mongo.new(nothing, :expire_after => 3600)
209
+ res = Rack::MockRequest.new(app).get("/")
210
+ res["Set-Cookie"].should.be.nil
211
+ end
212
+ end
metadata ADDED
@@ -0,0 +1,90 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rack-session-mongo
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Masato Igarashi
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-02-04 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: bacon
16
+ requirement: &70265363549220 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: *70265363549220
25
+ - !ruby/object:Gem::Dependency
26
+ name: rack
27
+ requirement: &70265363548400 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *70265363548400
36
+ - !ruby/object:Gem::Dependency
37
+ name: mongo
38
+ requirement: &70265363547140 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :runtime
45
+ prerelease: false
46
+ version_requirements: *70265363547140
47
+ description: Rack session store for MongoDB
48
+ email:
49
+ - m@igrs.jp
50
+ executables: []
51
+ extensions: []
52
+ extra_rdoc_files: []
53
+ files:
54
+ - .gitignore
55
+ - .travis.yml
56
+ - CHANGES.md
57
+ - Gemfile
58
+ - README.md
59
+ - Rakefile
60
+ - lib/rack-session-mongo.rb
61
+ - lib/rack-session-mongo/version.rb
62
+ - lib/rack/session/mongo.rb
63
+ - rack-session-mongo.gemspec
64
+ - spec/spec_session_mongo.rb
65
+ homepage: http://github.com/migrs/rack-session-mongo
66
+ licenses: []
67
+ post_install_message:
68
+ rdoc_options: []
69
+ require_paths:
70
+ - lib
71
+ required_ruby_version: !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ! '>='
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ required_rubygems_version: !ruby/object:Gem::Requirement
78
+ none: false
79
+ requirements:
80
+ - - ! '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ requirements: []
84
+ rubyforge_project:
85
+ rubygems_version: 1.8.10
86
+ signing_key:
87
+ specification_version: 3
88
+ summary: Rack session store for MongoDB
89
+ test_files:
90
+ - spec/spec_session_mongo.rb