rack-session-mongo 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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