rack-session-leveldb 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.
@@ -0,0 +1,5 @@
1
+ *.gem
2
+ .bundle
3
+ pkg/*
4
+ *.swp
5
+ .DS_Store
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ script: "bacon -I./lib spec/spec_session_leveldb.rb"
3
+ rvm:
4
+ - 1.8.7
5
+ - 1.9.2
6
+ - 1.9.3
@@ -0,0 +1,6 @@
1
+ CHANGES
2
+ =======
3
+
4
+ ### [0.0.1](https://github.com/migrs/rack-session-leveldb/tree/v0.0.1) / 2012-01-22
5
+
6
+ * Initial Release
data/Gemfile ADDED
@@ -0,0 +1,11 @@
1
+ source :rubygems
2
+
3
+ # Specify your gem's dependencies in rack-session-leveldb.gemspec
4
+ # gemspec
5
+
6
+ gem 'rack'
7
+ gem 'leveldb-ruby'
8
+
9
+ group :test do
10
+ gem 'bacon'
11
+ end
@@ -0,0 +1,14 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ bacon (1.1.0)
5
+ leveldb-ruby (0.10)
6
+ rack (1.4.0)
7
+
8
+ PLATFORMS
9
+ ruby
10
+
11
+ DEPENDENCIES
12
+ bacon
13
+ leveldb-ruby
14
+ rack
@@ -0,0 +1,41 @@
1
+ rack-session-leveldb
2
+ ====================
3
+
4
+ Rack session store for LevelDB
5
+
6
+ ## Installation
7
+
8
+ gem install rack-session-leveldb
9
+
10
+ ## Usage
11
+
12
+ Simple (db\_path: ENV['TMP']/rack.session)
13
+
14
+ use Rack::Session::LevelDB
15
+
16
+ Specify DB path
17
+
18
+ use Rack::Session::LevelDB, File.dirname(__FILE__) + '/tmp/rack.session'
19
+
20
+ Set LevelDB instance
21
+
22
+ leveldb = LevelDB::DB.new(File.dirname(__FILE__) + '/db')
23
+ use Rack::Session::LevelDB, leveldb
24
+
25
+ Specify DB path with some config
26
+
27
+ use Rack::Session::LevelDB, {
28
+ :db_path => File.dirname(__FILE__) + '/tmp/rack.session',
29
+ :expire_after => 600
30
+ }
31
+
32
+ Set LevelDB instance with some config
33
+
34
+ leveldb = LevelDB::DB.new(File.dirname(__FILE__) + '/db')
35
+ use Rack::Session::LevelDB, {
36
+ :cache => leveldb,
37
+ :cleanup => false,
38
+ :expire_after => 600
39
+ }
40
+
41
+
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1 @@
1
+ require "rack/session/leveldb"
@@ -0,0 +1,108 @@
1
+ require 'rack/session/abstract/id'
2
+ require 'leveldb'
3
+ module Rack
4
+ module Session
5
+ class LevelDB < Abstract::ID
6
+ VERSION = '0.0.1'
7
+
8
+ attr_reader :mutex, :pool
9
+
10
+ DEFAULT_OPTIONS = Abstract::ID::DEFAULT_OPTIONS.merge \
11
+ :namespace => 'rack.session:',
12
+ :db_path => "#{ENV['TMP'] || '/tmp'}/rack.session",
13
+ :cleanup => true
14
+
15
+ def initialize(app, options={})
16
+ options = {:db_path => options } if options.is_a? String
17
+ options = {:cache => options } if options.is_a? ::LevelDB::DB
18
+ super
19
+ if options[:cache]
20
+ if options[:cache].kind_of? ::LevelDB::DB
21
+ @pool = options[:cache]
22
+ else
23
+ raise ":cache is not LevelDB"
24
+ end
25
+ else
26
+ @pool = ::LevelDB::DB.new @default_options[:db_path]
27
+ end
28
+
29
+ cleanup_expired if @default_options[:cleanup]
30
+ @mutex = Mutex.new
31
+ end
32
+
33
+ def generate_sid
34
+ loop do
35
+ sid = super
36
+ break sid unless _exists? sid
37
+ end
38
+ end
39
+
40
+ def get_session(env, sid)
41
+ with_lock(env, [nil, {}]) do
42
+ unless sid and session = _get(sid)
43
+ sid, session = generate_sid, {}
44
+ _put sid, session
45
+ end
46
+ [sid, session]
47
+ end
48
+ end
49
+
50
+ def set_session(env, session_id, new_session, options)
51
+ with_lock(env, false) do
52
+ _put session_id, new_session
53
+ session_id
54
+ end
55
+ end
56
+
57
+ def destroy_session(env, session_id, options)
58
+ with_lock(env) do
59
+ _delete(session_id)
60
+ generate_sid unless options[:drop]
61
+ end
62
+ end
63
+
64
+ def with_lock(env, default=nil)
65
+ @mutex.lock if env['rack.multithread']
66
+ yield
67
+ rescue
68
+ warn "#{self} is unable to open #{@default_options[:db_path]}."
69
+ warn $!.inspect
70
+ default
71
+ ensure
72
+ @mutex.unlock if @mutex.locked?
73
+ end
74
+
75
+
76
+ def cleanup_expired(time = Time.now)
77
+ return unless @default_options[:expire_after]
78
+ @pool.each do |k, v|
79
+ if k.start_with?(@default_options[:namespace]) and
80
+ time - Marshal.load(v)[:datetime] > @default_options[:expire_after]
81
+ @pool.delete(k)
82
+ end
83
+ end
84
+ end
85
+
86
+ private
87
+ def _put(sid, session)
88
+ @pool.put(_key(sid), Marshal.dump({:data => session, :datetime => Time.now}))
89
+ end
90
+
91
+ def _get(sid)
92
+ Marshal.load(@pool.get(_key(sid)))[:data] rescue nil
93
+ end
94
+
95
+ def _delete(sid)
96
+ @pool.delete(_key(sid))
97
+ end
98
+
99
+ def _exists?(sid)
100
+ @pool.exists?(_key(sid))
101
+ end
102
+
103
+ def _key(sid)
104
+ "#{@default_options[:namespace]}#{sid}"
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "rack-session-leveldb"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "rack-session-leveldb"
7
+ s.version = Rack::Session::LevelDB::VERSION
8
+ s.authors = ["Masato Igarashi"]
9
+ s.email = ["m@igrs.jp"]
10
+ s.homepage = "http://github.com/migrs/rack-session-leveldb"
11
+ s.summary = %q{Rack session store for LevelDB}
12
+ s.description = %q{Rack session store for LevelDB}
13
+
14
+ #s.rubyforge_project = "rack-session-leveldb"
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 "leveldb-ruby"
25
+ end
@@ -0,0 +1,215 @@
1
+ require 'thread'
2
+ require 'rack/mock'
3
+ require 'rack/session/leveldb'
4
+ require 'fileutils'
5
+
6
+ describe Rack::Session::LevelDB do
7
+ session_key = Rack::Session::LevelDB::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
+ before do
40
+ Rack::Session::LevelDB::DEFAULT_OPTIONS[:db_path] = "#{ENV['TMP'] || '/tmp'}/rack.session-#{rand}"
41
+ end
42
+
43
+ after do
44
+ FileUtils.rm_r(Rack::Session::LevelDB::DEFAULT_OPTIONS[:db_path])
45
+ end
46
+
47
+ it "creates a new cookie" do
48
+ pool = Rack::Session::LevelDB.new(incrementor)
49
+ res = Rack::MockRequest.new(pool).get("/")
50
+ res["Set-Cookie"].should.match session_match
51
+ res.body.should.equal '{"counter"=>1}'
52
+ end
53
+
54
+ it "determines session from a cookie" do
55
+ pool = Rack::Session::LevelDB.new(incrementor)
56
+ req = Rack::MockRequest.new(pool)
57
+ cookie = req.get("/")["Set-Cookie"]
58
+ req.get("/", "HTTP_COOKIE" => cookie).
59
+ body.should.equal '{"counter"=>2}'
60
+ req.get("/", "HTTP_COOKIE" => cookie).
61
+ body.should.equal '{"counter"=>3}'
62
+ end
63
+
64
+ it "survives nonexistant cookies" do
65
+ pool = Rack::Session::LevelDB.new(incrementor)
66
+ res = Rack::MockRequest.new(pool).
67
+ get("/", "HTTP_COOKIE" => "#{session_key}=blarghfasel")
68
+ res.body.should.equal '{"counter"=>1}'
69
+ end
70
+
71
+ it "does not send the same session id if it did not change" do
72
+ pool = Rack::Session::LevelDB.new(incrementor)
73
+ req = Rack::MockRequest.new(pool)
74
+
75
+ res0 = req.get("/")
76
+ cookie = res0["Set-Cookie"][session_match]
77
+ res0.body.should.equal '{"counter"=>1}'
78
+ pool.pool.size.should.equal 1
79
+
80
+ res1 = req.get("/", "HTTP_COOKIE" => cookie)
81
+ res1["Set-Cookie"].should.be.nil
82
+ res1.body.should.equal '{"counter"=>2}'
83
+ pool.pool.size.should.equal 1
84
+
85
+ res2 = req.get("/", "HTTP_COOKIE" => cookie)
86
+ res2["Set-Cookie"].should.be.nil
87
+ res2.body.should.equal '{"counter"=>3}'
88
+ pool.pool.size.should.equal 1
89
+ end
90
+
91
+ it "deletes cookies with :drop option" do
92
+ pool = Rack::Session::LevelDB.new(incrementor)
93
+ req = Rack::MockRequest.new(pool)
94
+ drop = Rack::Utils::Context.new(pool, drop_session)
95
+ dreq = Rack::MockRequest.new(drop)
96
+
97
+ res1 = req.get("/")
98
+ session = (cookie = res1["Set-Cookie"])[session_match]
99
+ res1.body.should.equal '{"counter"=>1}'
100
+ pool.pool.size.should.equal 1
101
+
102
+ res2 = dreq.get("/", "HTTP_COOKIE" => cookie)
103
+ res2["Set-Cookie"].should.be.nil
104
+ res2.body.should.equal '{"counter"=>2}'
105
+ pool.pool.size.should.equal 0
106
+
107
+ res3 = req.get("/", "HTTP_COOKIE" => cookie)
108
+ res3["Set-Cookie"][session_match].should.not.equal session
109
+ res3.body.should.equal '{"counter"=>1}'
110
+ pool.pool.size.should.equal 1
111
+ end
112
+
113
+ it "provides new session id with :renew option" do
114
+ pool = Rack::Session::LevelDB.new(incrementor)
115
+ req = Rack::MockRequest.new(pool)
116
+ renew = Rack::Utils::Context.new(pool, renew_session)
117
+ rreq = Rack::MockRequest.new(renew)
118
+
119
+ res1 = req.get("/")
120
+ session = (cookie = res1["Set-Cookie"])[session_match]
121
+ res1.body.should.equal '{"counter"=>1}'
122
+ pool.pool.size.should.equal 1
123
+
124
+ res2 = rreq.get("/", "HTTP_COOKIE" => cookie)
125
+ new_cookie = res2["Set-Cookie"]
126
+ new_session = new_cookie[session_match]
127
+ new_session.should.not.equal session
128
+ res2.body.should.equal '{"counter"=>2}'
129
+ pool.pool.size.should.equal 1
130
+
131
+ res3 = req.get("/", "HTTP_COOKIE" => new_cookie)
132
+ res3.body.should.equal '{"counter"=>3}'
133
+ pool.pool.size.should.equal 1
134
+
135
+ res4 = req.get("/", "HTTP_COOKIE" => cookie)
136
+ res4.body.should.equal '{"counter"=>1}'
137
+ pool.pool.size.should.equal 2
138
+ end
139
+
140
+ it "omits cookie with :defer option" do
141
+ pool = Rack::Session::LevelDB.new(incrementor)
142
+ defer = Rack::Utils::Context.new(pool, defer_session)
143
+ dreq = Rack::MockRequest.new(defer)
144
+
145
+ res1 = dreq.get("/")
146
+ res1["Set-Cookie"].should.equal nil
147
+ res1.body.should.equal '{"counter"=>1}'
148
+ pool.pool.size.should.equal 1
149
+ end
150
+
151
+ # anyone know how to do this better?
152
+ it "should merge sessions when multithreaded" do
153
+ unless $DEBUG
154
+ 1.should.equal 1
155
+ next
156
+ end
157
+
158
+ warn 'Running multithread tests for Session::LevelDB'
159
+ pool = Rack::Session::LevelDB.new(incrementor)
160
+ req = Rack::MockRequest.new(pool)
161
+
162
+ res = req.get('/')
163
+ res.body.should.equal '{"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 |resp|
183
+ resp['Set-Cookie'].should.equal cookie
184
+ resp.body.should.include '"counter"=>2'
185
+ end
186
+
187
+ session = pool.pool[sess_id]
188
+ session.size.should.equal tnum+1 # counter
189
+ session['counter'].should.equal 2 # meeeh
190
+ end
191
+
192
+ it "does not return a cookie if cookie was not read/written" do
193
+ app = Rack::Session::LevelDB.new(nothing)
194
+ res = Rack::MockRequest.new(app).get("/")
195
+ res["Set-Cookie"].should.be.nil
196
+ end
197
+
198
+ it "does not return a cookie if cookie was not written (only read)" do
199
+ app = Rack::Session::LevelDB.new(session_id)
200
+ res = Rack::MockRequest.new(app).get("/")
201
+ res["Set-Cookie"].should.be.nil
202
+ end
203
+
204
+ it "returns even if not read/written if :expire_after is set" do
205
+ app = Rack::Session::LevelDB.new(nothing, :expire_after => 3600)
206
+ res = Rack::MockRequest.new(app).get("/", 'rack.session' => {'not' => 'empty'})
207
+ res["Set-Cookie"].should.not.be.nil
208
+ end
209
+
210
+ it "returns no cookie if no data was written and no session was created previously, even if :expire_after is set" do
211
+ app = Rack::Session::LevelDB.new(nothing, :expire_after => 3600)
212
+ res = Rack::MockRequest.new(app).get("/")
213
+ res["Set-Cookie"].should.be.nil
214
+ end
215
+ end
metadata ADDED
@@ -0,0 +1,90 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rack-session-leveldb
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-01-21 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: bacon
16
+ requirement: &70238763246740 !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: *70238763246740
25
+ - !ruby/object:Gem::Dependency
26
+ name: rack
27
+ requirement: &70238763245760 !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: *70238763245760
36
+ - !ruby/object:Gem::Dependency
37
+ name: leveldb-ruby
38
+ requirement: &70238763244980 !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: *70238763244980
47
+ description: Rack session store for LevelDB
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
+ - Gemfile.lock
59
+ - README.md
60
+ - Rakefile
61
+ - lib/rack-session-leveldb.rb
62
+ - lib/rack/session/leveldb.rb
63
+ - rack-session-leveldb.gemspec
64
+ - spec/spec_session_leveldb.rb
65
+ homepage: http://github.com/migrs/rack-session-leveldb
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 LevelDB
89
+ test_files:
90
+ - spec/spec_session_leveldb.rb