rack-session-file 0.3.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/Gemfile +3 -0
- data/LICENSE +24 -0
- data/README.md +72 -0
- data/Rakefile +2 -0
- data/lib/rack-session-file.rb +1 -0
- data/lib/rack/session/file.rb +30 -0
- data/lib/rack/session/file/abstract.rb +168 -0
- data/lib/rack/session/file/marshal.rb +66 -0
- data/lib/rack/session/file/pstore.rb +70 -0
- data/lib/rack/session/file/yaml.rb +76 -0
- data/rack-session-file.gemspec +50 -0
- data/spec/common.rb +164 -0
- data/spec/rack-session-file-marshal_spec.rb +7 -0
- data/spec/rack-session-file-pstore_spec.rb +6 -0
- data/spec/rack-session-file-yaml_spec.rb +6 -0
- data/spec/rack-session-file_spec.rb +6 -0
- data/spec/spec_helper.rb +12 -0
- metadata +106 -0
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
Copyright (c) 2012 ITO Nobuaki
|
2
|
+
Copyright (c) 2011 Uchio Kondo
|
3
|
+
Copyright (c) 2010 Jonathan Rudenberg
|
4
|
+
|
5
|
+
MIT License
|
6
|
+
|
7
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
8
|
+
a copy of this software and associated documentation files (the
|
9
|
+
"Software"), to deal in the Software without restriction, including
|
10
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
11
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
12
|
+
permit persons to whom the Software is furnished to do so, subject to
|
13
|
+
the following conditions:
|
14
|
+
|
15
|
+
The above copyright notice and this permission notice shall be
|
16
|
+
included in all copies or substantial portions of the Software.
|
17
|
+
|
18
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
19
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
20
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
21
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
22
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
23
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
24
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
# Rack::Session::File
|
2
|
+
|
3
|
+
Rack session store on plain file system.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'rack-session-file', :git =>
|
11
|
+
'git://github.com/dayflower/rack-session-file.git'
|
12
|
+
```
|
13
|
+
|
14
|
+
And then execute:
|
15
|
+
|
16
|
+
$ bundle install
|
17
|
+
|
18
|
+
## Usage
|
19
|
+
|
20
|
+
```ruby
|
21
|
+
use Rack::Session::File, :storage => ENV['TEMP'],
|
22
|
+
:expire_after => 1800
|
23
|
+
```
|
24
|
+
|
25
|
+
### Options
|
26
|
+
|
27
|
+
#### File storage directory (`:storage`)
|
28
|
+
|
29
|
+
Default is `Dir.tmpdir`.
|
30
|
+
|
31
|
+
#### Backend serializer (`:driver`)
|
32
|
+
|
33
|
+
You can specify backend serializer via `:driver` option.
|
34
|
+
|
35
|
+
```ruby
|
36
|
+
use Rack::Session::File, :driver => :YAML
|
37
|
+
```
|
38
|
+
|
39
|
+
Bundled drivers are:
|
40
|
+
|
41
|
+
* `:PStore` (default)
|
42
|
+
* `:YAML`
|
43
|
+
* `:Marshal` (discouraged to use)
|
44
|
+
|
45
|
+
Also you can `use` backend driver class explicitly.
|
46
|
+
|
47
|
+
```ruby
|
48
|
+
use Rack::Session::File::YAML
|
49
|
+
```
|
50
|
+
|
51
|
+
## Contributing
|
52
|
+
|
53
|
+
1. Fork it
|
54
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
55
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
56
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
57
|
+
5. Create new Pull Request
|
58
|
+
|
59
|
+
## Acknowledgement
|
60
|
+
|
61
|
+
Most of main code and test codes are derived from
|
62
|
+
titanous's [mongo-store](https://github.com/titanous/mongo-store)
|
63
|
+
and udzura's [rack-session-dbm](https://github.com/udzura/rack-session-dbm).
|
64
|
+
|
65
|
+
## Copyright and license
|
66
|
+
|
67
|
+
Copyright © 2012 ITO Nobuaki.
|
68
|
+
Copyright © 2011 Uchio Kondo. (as the author of
|
69
|
+
[rack-session-dbm](https://github.com/udzura/rack-session-dbm) )
|
70
|
+
Copyright © 2010 Jonathan Rudenberg. (as the author of
|
71
|
+
[mongo-store](https://github.com/titanous/mongo-store) )
|
72
|
+
See LICENSE for details (MIT License).
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/rack/session/file')
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
module Rack
|
4
|
+
module Session
|
5
|
+
module File
|
6
|
+
VERSION = '0.3.0'
|
7
|
+
|
8
|
+
autoload :Marshal, 'rack/session/file/marshal'
|
9
|
+
autoload :PStore, 'rack/session/file/pstore'
|
10
|
+
autoload :YAML, 'rack/session/file/yaml'
|
11
|
+
|
12
|
+
def self.new(app, options = {})
|
13
|
+
mapping = {
|
14
|
+
:pstore => :PStore,
|
15
|
+
:yaml => :YAML,
|
16
|
+
:marshal => :Marshal,
|
17
|
+
}
|
18
|
+
|
19
|
+
driver = options.delete(:driver) || :pstore
|
20
|
+
driver = mapping[driver.to_s.downcase.to_sym] || driver if driver.is_a?(Symbol)
|
21
|
+
if ! driver.is_a?(Class)
|
22
|
+
require autoload?(driver) if autoload?(driver)
|
23
|
+
driver = const_get(driver)
|
24
|
+
end
|
25
|
+
|
26
|
+
return driver.new(app, options)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,168 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
require 'rubygems'
|
3
|
+
require 'rack/session/abstract/id'
|
4
|
+
|
5
|
+
module Rack
|
6
|
+
module Session
|
7
|
+
module File
|
8
|
+
class Abstract < Rack::Session::Abstract::ID
|
9
|
+
DEFAULT_OPTIONS = Rack::Session::Abstract::ID::DEFAULT_OPTIONS.merge({
|
10
|
+
:storage => Dir.tmpdir,
|
11
|
+
:clear_expired_after => 30 * 60,
|
12
|
+
})
|
13
|
+
|
14
|
+
def initialize(app, options = {})
|
15
|
+
super
|
16
|
+
|
17
|
+
@storage = @default_options.delete(:storage)
|
18
|
+
@recheck_expire_period = @default_options.delete(:clear_expired_after).to_i
|
19
|
+
|
20
|
+
@next_expire_period = nil
|
21
|
+
|
22
|
+
@transaction_options = {
|
23
|
+
:storage => @storage,
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
27
|
+
def get_session(env, sid)
|
28
|
+
new_transaction(env).with_lock do |transaction|
|
29
|
+
session = find_session(env, sid) if sid
|
30
|
+
unless sid and session
|
31
|
+
env['rack.errors'].puts("Session '#{sid}' not found, initializing...") if $VERBOSE and not sid.nil?
|
32
|
+
session = {}
|
33
|
+
sid = new_sid(env)
|
34
|
+
transaction.save_session(sid)
|
35
|
+
end
|
36
|
+
session.instance_variable_set('@old', {}.merge(session))
|
37
|
+
session.instance_variable_set('@sid', sid)
|
38
|
+
return [sid, session]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def set_session(env, sid, new_session, options)
|
43
|
+
new_transaction(env).with_lock do |transaction|
|
44
|
+
expires = Time.now + options[:expire_after] if ! options[:expire_after].nil?
|
45
|
+
session = find_session(env, sid) || {}
|
46
|
+
|
47
|
+
if options[:renew] or options[:drop]
|
48
|
+
transaction.delete_session(sid)
|
49
|
+
return false if options[:drop]
|
50
|
+
sid = new_sid(env)
|
51
|
+
#transaction.save_session(sid, session, expires)
|
52
|
+
end
|
53
|
+
|
54
|
+
old_session = new_session.instance_variable_defined?('@old') ? new_session.instance_variable_get('@old') : {}
|
55
|
+
session = merge_sessions(sid, old_session, new_session, session)
|
56
|
+
transaction.save_session(sid, session, expires)
|
57
|
+
return sid
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def destroy_session(env, sid, options)
|
62
|
+
new_transaction(env).with_lock do |transaction|
|
63
|
+
transaction.delete_session(sid)
|
64
|
+
new_sid(env) unless options[:drop]
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def new_transaction(env)
|
71
|
+
raise '#new_transaction not implemented'
|
72
|
+
end
|
73
|
+
|
74
|
+
def new_sid(env)
|
75
|
+
loop do
|
76
|
+
sid = generate_sid()
|
77
|
+
break sid unless find_session(env, sid)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def find_session(env, sid)
|
82
|
+
transaction = new_transaction(env)
|
83
|
+
|
84
|
+
time = Time.now
|
85
|
+
if @recheck_expire_period != -1 && (@next_expire_period.nil? || @next_expire_period < time)
|
86
|
+
@next_expire_period = time + @recheck_expire_period
|
87
|
+
transaction.expire_sessions(time)
|
88
|
+
end
|
89
|
+
|
90
|
+
transaction.find_session(sid)
|
91
|
+
end
|
92
|
+
|
93
|
+
def merge_sessions(sid, old, new, current = nil)
|
94
|
+
current ||= {}
|
95
|
+
unless Hash === old and Hash === new
|
96
|
+
warn 'Bad old or new sessions provided.'
|
97
|
+
return current
|
98
|
+
end
|
99
|
+
|
100
|
+
# delete keys that are not in common
|
101
|
+
deletes = current.keys - (new.keys & current.keys)
|
102
|
+
warn "//@#{sid}: dropping #{deletes*','}" if $DEBUG and not deletes.empty?
|
103
|
+
deletes.each { |k| current.delete(k) }
|
104
|
+
|
105
|
+
updates = new.keys.select { |k| ! current.has_key?(k) || new[k] != current[k] || new[k].kind_of?(Hash) || new[k].kind_of?(Array) }
|
106
|
+
warn "//@#{sid}: updating #{updates*','}" if $DEBUG and not updates.empty?
|
107
|
+
updates.each { |k| current[k] = new[k] }
|
108
|
+
|
109
|
+
return current
|
110
|
+
end
|
111
|
+
|
112
|
+
class Transaction
|
113
|
+
def initialize(env, options = {})
|
114
|
+
@env = env
|
115
|
+
@storage = options[:storage]
|
116
|
+
@mutex = Mutex.new if want_thread_safe?
|
117
|
+
end
|
118
|
+
|
119
|
+
def with_lock
|
120
|
+
@mutex.lock if want_thread_safe?
|
121
|
+
begin
|
122
|
+
yield(self)
|
123
|
+
ensure
|
124
|
+
@mutex.unlock if want_thread_safe?
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def find_session(sid)
|
129
|
+
session = load_session(sid)
|
130
|
+
|
131
|
+
if session && session[:expires] != nil && session[:expires] < Time.now
|
132
|
+
delete_session(sid)
|
133
|
+
session = nil
|
134
|
+
end
|
135
|
+
|
136
|
+
return session ? session[:data] : nil
|
137
|
+
end
|
138
|
+
|
139
|
+
def save_session(sid, session = {}, expires = nil)
|
140
|
+
store_session(sid, { :data => session, :expires => expires })
|
141
|
+
end
|
142
|
+
|
143
|
+
def store_session(sid, data)
|
144
|
+
raise '#store_session not implemented'
|
145
|
+
end
|
146
|
+
|
147
|
+
def load_session(sid)
|
148
|
+
raise '#load_session not implemented'
|
149
|
+
end
|
150
|
+
|
151
|
+
def delete_session(sid)
|
152
|
+
raise '#delete_session not implemented'
|
153
|
+
end
|
154
|
+
|
155
|
+
def expire_sessions(time)
|
156
|
+
raise '#expire_sessions not implemented'
|
157
|
+
end
|
158
|
+
|
159
|
+
private
|
160
|
+
|
161
|
+
def want_thread_safe?
|
162
|
+
@env['rack.multithread']
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
require 'rubygems'
|
3
|
+
require 'fileutils'
|
4
|
+
require File.expand_path(File.dirname(__FILE__) + '/abstract')
|
5
|
+
|
6
|
+
module Rack
|
7
|
+
module Session
|
8
|
+
module File
|
9
|
+
class Marshal < Abstract
|
10
|
+
def new_transaction(env)
|
11
|
+
Rack::Session::File::Marshal::Transaction.new(env, @transaction_options)
|
12
|
+
end
|
13
|
+
|
14
|
+
class Transaction < Rack::Session::File::Abstract::Transaction
|
15
|
+
def store_session(sid, data)
|
16
|
+
open_session_file(sid, 'w') do |file|
|
17
|
+
::Marshal.dump(data, file)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def load_session(sid)
|
22
|
+
begin
|
23
|
+
open_session_file(sid, 'r') do |file|
|
24
|
+
return ::Marshal.load(file)
|
25
|
+
end
|
26
|
+
rescue Errno::ENOENT
|
27
|
+
return nil
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def delete_session(sid)
|
32
|
+
filename = session_file_name(sid)
|
33
|
+
if ::File.exists?(filename)
|
34
|
+
::File.unlink(filename)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def expire_sessions(time)
|
39
|
+
# TODO
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def open_session_file(sid, mode = 'r')
|
45
|
+
ensure_storage_accessible() if mode =~ /^w/
|
46
|
+
if block_given?
|
47
|
+
open(session_file_name(sid), mode) do |file|
|
48
|
+
yield file
|
49
|
+
end
|
50
|
+
else
|
51
|
+
return open(session_file_name(sid), mode)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def session_file_name(sid)
|
56
|
+
return ::File.join(@storage, sid)
|
57
|
+
end
|
58
|
+
|
59
|
+
def ensure_storage_accessible
|
60
|
+
FileUtils.mkdir_p(@storage)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
require 'rubygems'
|
3
|
+
require 'fileutils'
|
4
|
+
require 'pstore'
|
5
|
+
require File.expand_path(File.dirname(__FILE__) + '/abstract')
|
6
|
+
|
7
|
+
module Rack
|
8
|
+
module Session
|
9
|
+
module File
|
10
|
+
class PStore < Abstract
|
11
|
+
def new_transaction(env)
|
12
|
+
Rack::Session::File::PStore::Transaction.new(env, @transaction_options)
|
13
|
+
end
|
14
|
+
|
15
|
+
class Transaction < Rack::Session::File::Abstract::Transaction
|
16
|
+
def store_session(sid, data)
|
17
|
+
ensure_storage_accessible()
|
18
|
+
|
19
|
+
store_for_sid(sid).transaction do |db|
|
20
|
+
data.keys.each do |key|
|
21
|
+
db[key] = data[key]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def load_session(sid)
|
27
|
+
ensure_storage_accessible()
|
28
|
+
|
29
|
+
data = {}
|
30
|
+
store_for_sid(sid).transaction(true) do |db|
|
31
|
+
db.roots.each do |key|
|
32
|
+
data[key] = db[key]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
return data
|
36
|
+
end
|
37
|
+
|
38
|
+
def delete_session(sid)
|
39
|
+
filename = store_for_sid(sid).path
|
40
|
+
if ::File.exists?(filename)
|
41
|
+
::File.unlink(filename)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def expire_sessions(time)
|
46
|
+
# TODO
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def store_for_sid(sid)
|
52
|
+
begin
|
53
|
+
::PStore.new(session_file_name(sid), want_thread_safe?)
|
54
|
+
rescue ArgumentError
|
55
|
+
::PStore.new(session_file_name(sid))
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def session_file_name(sid)
|
60
|
+
return ::File.join(@storage, sid)
|
61
|
+
end
|
62
|
+
|
63
|
+
def ensure_storage_accessible
|
64
|
+
FileUtils.mkdir_p(@storage)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
require 'rubygems'
|
3
|
+
require 'fileutils'
|
4
|
+
require 'yaml/store'
|
5
|
+
require File.expand_path(File.dirname(__FILE__) + '/abstract')
|
6
|
+
|
7
|
+
module Rack
|
8
|
+
module Session
|
9
|
+
module File
|
10
|
+
class YAML < Abstract
|
11
|
+
def initialize(app, options = {})
|
12
|
+
super
|
13
|
+
@transaction_options['yaml'] = @default_options.delete(:yaml) || {}
|
14
|
+
end
|
15
|
+
|
16
|
+
def new_transaction(env)
|
17
|
+
Rack::Session::File::YAML::Transaction.new(env, @transaction_options)
|
18
|
+
end
|
19
|
+
|
20
|
+
class Transaction < Rack::Session::File::Abstract::Transaction
|
21
|
+
def initialize(env, options = {})
|
22
|
+
super
|
23
|
+
@yaml_options = options[:yaml] || {}
|
24
|
+
end
|
25
|
+
|
26
|
+
def store_session(sid, data)
|
27
|
+
ensure_storage_accessible()
|
28
|
+
|
29
|
+
store_for_sid(sid).transaction do |db|
|
30
|
+
data.keys.each do |key|
|
31
|
+
db[key] = data[key]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def load_session(sid)
|
37
|
+
ensure_storage_accessible()
|
38
|
+
|
39
|
+
data = {}
|
40
|
+
store_for_sid(sid).transaction(true) do |db|
|
41
|
+
db.roots.each do |key|
|
42
|
+
data[key] = db[key]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
return data
|
46
|
+
end
|
47
|
+
|
48
|
+
def delete_session(sid)
|
49
|
+
filename = store_for_sid(sid).path
|
50
|
+
if ::File.exists?(filename)
|
51
|
+
::File.unlink(filename)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def expire_sessions(time)
|
56
|
+
# TODO
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def store_for_sid(sid)
|
62
|
+
::YAML::Store.new(session_file_name(sid), @yaml_options)
|
63
|
+
end
|
64
|
+
|
65
|
+
def session_file_name(sid)
|
66
|
+
return ::File.join(@storage, sid)
|
67
|
+
end
|
68
|
+
|
69
|
+
def ensure_storage_accessible
|
70
|
+
FileUtils.mkdir_p(@storage)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |gem|
|
4
|
+
gem.authors = ["ITO Nobuaki"]
|
5
|
+
gem.email = ["daydream.trippers@gmail.com"]
|
6
|
+
gem.description = %q{A rack-based session store on plain file system.}
|
7
|
+
gem.summary = %q{A rack-based session store on plain file system}
|
8
|
+
gem.homepage = ""
|
9
|
+
|
10
|
+
gem.files = [
|
11
|
+
"rack-session-file.gemspec",
|
12
|
+
"Gemfile",
|
13
|
+
"LICENSE",
|
14
|
+
"README.md",
|
15
|
+
"Rakefile",
|
16
|
+
"lib/rack-session-file.rb",
|
17
|
+
"lib/rack/session/file.rb",
|
18
|
+
"lib/rack/session/file/abstract.rb",
|
19
|
+
"lib/rack/session/file/marshal.rb",
|
20
|
+
"lib/rack/session/file/pstore.rb",
|
21
|
+
"lib/rack/session/file/yaml.rb",
|
22
|
+
"spec/common.rb",
|
23
|
+
"spec/rack-session-file_spec.rb",
|
24
|
+
"spec/rack-session-file-marshal_spec.rb",
|
25
|
+
"spec/rack-session-file-pstore_spec.rb",
|
26
|
+
"spec/rack-session-file-yaml_spec.rb",
|
27
|
+
"spec/spec_helper.rb",
|
28
|
+
]
|
29
|
+
|
30
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
31
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
32
|
+
gem.name = "rack-session-file"
|
33
|
+
gem.require_paths = ["lib"]
|
34
|
+
gem.version = '0.3.0'
|
35
|
+
|
36
|
+
if gem.respond_to? :specification_version then
|
37
|
+
gem.specification_version = 3
|
38
|
+
|
39
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
40
|
+
gem.add_runtime_dependency 'rack', '>= 1.1.0'
|
41
|
+
gem.add_development_dependency 'rspec', '>= 1.2.9'
|
42
|
+
else
|
43
|
+
gem.add_dependency 'rack', '>= 1.1.0'
|
44
|
+
gem.add_dependency 'rspec', '>= 1.2.9'
|
45
|
+
end
|
46
|
+
else
|
47
|
+
gem.add_dependency 'rack', '>= 1.1.0'
|
48
|
+
gem.add_dependency 'rspec', '>= 1.2.9'
|
49
|
+
end
|
50
|
+
end
|
data/spec/common.rb
ADDED
@@ -0,0 +1,164 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
require 'fileutils'
|
3
|
+
require 'tempfile'
|
4
|
+
|
5
|
+
shared_examples_for Rack::Session::File do
|
6
|
+
before(:all) do
|
7
|
+
if described_class.constants.include?(:DEFAULT_OPTIONS)
|
8
|
+
@session_key = described_class::DEFAULT_OPTIONS[:key]
|
9
|
+
else
|
10
|
+
require 'rack/session/abstract/id'
|
11
|
+
@session_key = Rack::Session::Abstract::ID::DEFAULT_OPTIONS[:key]
|
12
|
+
end
|
13
|
+
@session_match = /#{@session_key}=[0-9A-Fa-f]+;/
|
14
|
+
|
15
|
+
tf = Tempfile.open("racksess")
|
16
|
+
@storage = tf.path
|
17
|
+
tf.close!
|
18
|
+
|
19
|
+
@increment_mockapp = lambda do |env|
|
20
|
+
env['rack.session']['counter'] ||= 0
|
21
|
+
env['rack.session']['counter'] += 1
|
22
|
+
Rack::Response.new(env['rack.session'].inspect).finish
|
23
|
+
end
|
24
|
+
|
25
|
+
@drop_session_mockapp = lambda do |env|
|
26
|
+
env['rack.session.options'][:drop] = true
|
27
|
+
@increment_mockapp.call(env)
|
28
|
+
end
|
29
|
+
|
30
|
+
@renew_session_mockapp = lambda do |env|
|
31
|
+
env['rack.session.options'][:renew] = true
|
32
|
+
@increment_mockapp.call(env)
|
33
|
+
end
|
34
|
+
|
35
|
+
@defer_session_mockapp = lambda do |env|
|
36
|
+
env['rack.session.options'][:defer] = true
|
37
|
+
@increment_mockapp.call(env)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
after(:all) do
|
42
|
+
FileUtils.remove_dir(@storage)
|
43
|
+
end
|
44
|
+
|
45
|
+
let(:pool) { pool = described_class.new(@increment_mockapp, :storage => @storage) }
|
46
|
+
|
47
|
+
it 'creates a new cookie' do
|
48
|
+
res = Rack::MockRequest.new(pool).get('/')
|
49
|
+
|
50
|
+
res['Set-Cookie'].should match(/#{@session_key}=/)
|
51
|
+
res.body.should == '{"counter"=>1}'
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'determines session from a cookie' do
|
55
|
+
req = Rack::MockRequest.new(pool)
|
56
|
+
|
57
|
+
res = req.get('/')
|
58
|
+
cookie = res['Set-Cookie']
|
59
|
+
|
60
|
+
res = req.get('/', 'HTTP_COOKIE' => cookie)
|
61
|
+
res.body.should == '{"counter"=>2}'
|
62
|
+
|
63
|
+
res = req.get('/', 'HTTP_COOKIE' => cookie)
|
64
|
+
res.body.should == '{"counter"=>3}'
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'survives nonexistent cookies' do
|
68
|
+
bad_cookie = 'rack.session=blarghfasel'
|
69
|
+
res = Rack::MockRequest.new(pool) \
|
70
|
+
.get('/', 'HTTP_COOKIE' => bad_cookie)
|
71
|
+
|
72
|
+
res.body.should == '{"counter"=>1}'
|
73
|
+
cookie = res['Set-Cookie'][@session_match]
|
74
|
+
cookie.should_not match(/#{bad_cookie}/)
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'should maintain freshness' do
|
78
|
+
pool2 = described_class.new(@increment_mockapp, :storage => @storage, :expire_after => 2)
|
79
|
+
expired_time = Time.now + 5
|
80
|
+
res = Rack::MockRequest.new(pool2).get('/')
|
81
|
+
res.body.should include('"counter"=>1')
|
82
|
+
|
83
|
+
cookie = res['Set-Cookie']
|
84
|
+
|
85
|
+
res = Rack::MockRequest.new(pool2).get('/', 'HTTP_COOKIE' => cookie)
|
86
|
+
res['Set-Cookie'].should == cookie
|
87
|
+
res.body.should include('"counter"=>2')
|
88
|
+
|
89
|
+
Time.stub!(:now).and_return(expired_time)
|
90
|
+
|
91
|
+
res = Rack::MockRequest.new(pool2).get('/', 'HTTP_COOKIE' => cookie)
|
92
|
+
res['Set-Cookie'].should_not == cookie
|
93
|
+
res.body.should include('"counter"=>1')
|
94
|
+
end
|
95
|
+
|
96
|
+
it 'deletes cookies with :drop option' do
|
97
|
+
req = Rack::MockRequest.new(pool)
|
98
|
+
drop = Rack::Utils::Context.new(pool, @drop_session_mockapp)
|
99
|
+
dreq = Rack::MockRequest.new(drop)
|
100
|
+
|
101
|
+
res0 = req.get('/')
|
102
|
+
session = (cookie = res0['Set-Cookie'])[@session_match]
|
103
|
+
res0.body.should == '{"counter"=>1}'
|
104
|
+
|
105
|
+
res1 = req.get('/', 'HTTP_COOKIE' => cookie)
|
106
|
+
# res1['Set-Cookie'][@session_match].should == session
|
107
|
+
res1.body.should == '{"counter"=>2}'
|
108
|
+
|
109
|
+
res2 = dreq.get('/', 'HTTP_COOKIE' => cookie)
|
110
|
+
res2['Set-Cookie'].should be_nil
|
111
|
+
res2.body.should == '{"counter"=>3}'
|
112
|
+
|
113
|
+
res3 = req.get('/', 'HTTP_COOKIE' => cookie)
|
114
|
+
res3['Set-Cookie'][@session_match].should_not == session
|
115
|
+
res3.body.should == '{"counter"=>1}'
|
116
|
+
end
|
117
|
+
|
118
|
+
it 'provides new session id with :renew option' do
|
119
|
+
req = Rack::MockRequest.new(pool)
|
120
|
+
renew = Rack::Utils::Context.new(pool, @renew_session_mockapp)
|
121
|
+
rreq = Rack::MockRequest.new(renew)
|
122
|
+
|
123
|
+
res0 = req.get('/')
|
124
|
+
session = (cookie = res0['Set-Cookie'])[@session_match]
|
125
|
+
res0.body.should == '{"counter"=>1}'
|
126
|
+
|
127
|
+
res1 = req.get('/', 'HTTP_COOKIE' => cookie)
|
128
|
+
# res1['Set-Cookie'][@session_match].should == session
|
129
|
+
res1.body.should == '{"counter"=>2}'
|
130
|
+
|
131
|
+
res2 = rreq.get('/', 'HTTP_COOKIE' => cookie)
|
132
|
+
new_cookie = res2['Set-Cookie']
|
133
|
+
new_session = new_cookie[@session_match]
|
134
|
+
new_session.should_not == session
|
135
|
+
res2.body.should == '{"counter"=>3}'
|
136
|
+
|
137
|
+
res3 = req.get('/', 'HTTP_COOKIE' => new_cookie)
|
138
|
+
# res3['Set-Cookie'][@session_match].should == new_session
|
139
|
+
res3.body.should == '{"counter"=>4}'
|
140
|
+
end
|
141
|
+
|
142
|
+
it 'omits cookie with :defer option' do
|
143
|
+
req = Rack::MockRequest.new(pool)
|
144
|
+
defer = Rack::Utils::Context.new(pool, @defer_session_mockapp)
|
145
|
+
dreq = Rack::MockRequest.new(defer)
|
146
|
+
|
147
|
+
res0 = req.get('/')
|
148
|
+
session = (cookie = res0['Set-Cookie'])[@session_match]
|
149
|
+
res0.body.should == '{"counter"=>1}'
|
150
|
+
|
151
|
+
res1 = req.get('/', 'HTTP_COOKIE' => cookie)
|
152
|
+
# res1['Set-Cookie'][@session_match].should == session
|
153
|
+
res1.body.should == '{"counter"=>2}'
|
154
|
+
|
155
|
+
res2 = dreq.get('/', 'HTTP_COOKIE' => cookie)
|
156
|
+
res2['Set-Cookie'].should be_nil
|
157
|
+
res2.body.should == '{"counter"=>3}'
|
158
|
+
|
159
|
+
res3 = req.get('/', 'HTTP_COOKIE' => cookie)
|
160
|
+
# res3['Set-Cookie'][@session_match].should == session
|
161
|
+
res3.body.should == '{"counter"=>4}'
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
3
|
+
require 'rspec'
|
4
|
+
require 'rack-session-file'
|
5
|
+
|
6
|
+
require 'rack/mock'
|
7
|
+
require 'rack/response'
|
8
|
+
require 'thread'
|
9
|
+
|
10
|
+
RSpec.configure do |config|
|
11
|
+
#
|
12
|
+
end
|
metadata
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rack-session-file
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.3.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- ITO Nobuaki
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-05-11 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rack
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 1.1.0
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 1.1.0
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rspec
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 1.2.9
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 1.2.9
|
46
|
+
description: A rack-based session store on plain file system.
|
47
|
+
email:
|
48
|
+
- daydream.trippers@gmail.com
|
49
|
+
executables: []
|
50
|
+
extensions: []
|
51
|
+
extra_rdoc_files: []
|
52
|
+
files:
|
53
|
+
- rack-session-file.gemspec
|
54
|
+
- Gemfile
|
55
|
+
- LICENSE
|
56
|
+
- README.md
|
57
|
+
- Rakefile
|
58
|
+
- lib/rack-session-file.rb
|
59
|
+
- lib/rack/session/file.rb
|
60
|
+
- lib/rack/session/file/abstract.rb
|
61
|
+
- lib/rack/session/file/marshal.rb
|
62
|
+
- lib/rack/session/file/pstore.rb
|
63
|
+
- lib/rack/session/file/yaml.rb
|
64
|
+
- spec/common.rb
|
65
|
+
- spec/rack-session-file_spec.rb
|
66
|
+
- spec/rack-session-file-marshal_spec.rb
|
67
|
+
- spec/rack-session-file-pstore_spec.rb
|
68
|
+
- spec/rack-session-file-yaml_spec.rb
|
69
|
+
- spec/spec_helper.rb
|
70
|
+
homepage: ''
|
71
|
+
licenses: []
|
72
|
+
post_install_message:
|
73
|
+
rdoc_options: []
|
74
|
+
require_paths:
|
75
|
+
- lib
|
76
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
77
|
+
none: false
|
78
|
+
requirements:
|
79
|
+
- - ! '>='
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '0'
|
82
|
+
segments:
|
83
|
+
- 0
|
84
|
+
hash: 3561167824695945220
|
85
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
86
|
+
none: false
|
87
|
+
requirements:
|
88
|
+
- - ! '>='
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '0'
|
91
|
+
segments:
|
92
|
+
- 0
|
93
|
+
hash: 3561167824695945220
|
94
|
+
requirements: []
|
95
|
+
rubyforge_project:
|
96
|
+
rubygems_version: 1.8.23
|
97
|
+
signing_key:
|
98
|
+
specification_version: 3
|
99
|
+
summary: A rack-based session store on plain file system
|
100
|
+
test_files:
|
101
|
+
- spec/common.rb
|
102
|
+
- spec/rack-session-file_spec.rb
|
103
|
+
- spec/rack-session-file-marshal_spec.rb
|
104
|
+
- spec/rack-session-file-pstore_spec.rb
|
105
|
+
- spec/rack-session-file-yaml_spec.rb
|
106
|
+
- spec/spec_helper.rb
|