merb-core 0.9.5 → 0.9.6
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/CHANGELOG +925 -0
- data/CONTRIBUTORS +93 -0
- data/PUBLIC_CHANGELOG +85 -0
- data/Rakefile +18 -28
- data/bin/merb +34 -5
- data/lib/merb-core/autoload.rb +2 -3
- data/lib/merb-core/bootloader.rb +60 -66
- data/lib/merb-core/config.rb +7 -1
- data/lib/merb-core/controller/abstract_controller.rb +35 -21
- data/lib/merb-core/controller/merb_controller.rb +15 -42
- data/lib/merb-core/controller/mixins/authentication.rb +42 -6
- data/lib/merb-core/controller/mixins/conditional_get.rb +83 -0
- data/lib/merb-core/controller/mixins/render.rb +3 -3
- data/lib/merb-core/core_ext/kernel.rb +6 -19
- data/lib/merb-core/dispatch/cookies.rb +96 -80
- data/lib/merb-core/dispatch/default_exception/views/index.html.erb +2 -0
- data/lib/merb-core/dispatch/request.rb +18 -16
- data/lib/merb-core/dispatch/router/route.rb +6 -0
- data/lib/merb-core/dispatch/router.rb +4 -1
- data/lib/merb-core/dispatch/session/container.rb +64 -0
- data/lib/merb-core/dispatch/session/cookie.rb +91 -101
- data/lib/merb-core/dispatch/session/memcached.rb +38 -174
- data/lib/merb-core/dispatch/session/memory.rb +62 -208
- data/lib/merb-core/dispatch/session/store_container.rb +145 -0
- data/lib/merb-core/dispatch/session.rb +174 -48
- data/lib/merb-core/rack/middleware/conditional_get.rb +14 -8
- data/lib/merb-core/rack/middleware/csrf.rb +73 -0
- data/lib/merb-core/rack.rb +1 -0
- data/lib/merb-core/script.rb +112 -0
- data/lib/merb-core/server.rb +2 -0
- data/lib/merb-core/tasks/merb_rake_helper.rb +25 -0
- data/lib/merb-core/test/helpers/request_helper.rb +40 -3
- data/lib/merb-core/test/run_specs.rb +4 -3
- data/lib/merb-core/vendor/facets/inflect.rb +7 -10
- data/lib/merb-core/version.rb +1 -1
- data/lib/merb-core.rb +11 -40
- data/spec/private/core_ext/kernel_spec.rb +0 -11
- data/spec/private/dispatch/fixture/log/merb_test.log +893 -0
- data/spec/private/router/fixture/log/merb_test.log +12 -1728
- data/spec/private/router/route_spec.rb +4 -0
- data/spec/private/router/router_spec.rb +8 -0
- data/spec/private/vendor/facets/plural_spec.rb +1 -1
- data/spec/private/vendor/facets/singular_spec.rb +1 -1
- data/spec/public/abstract_controller/controllers/display.rb +8 -2
- data/spec/public/abstract_controller/controllers/filters.rb +18 -0
- data/spec/public/abstract_controller/display_spec.rb +6 -2
- data/spec/public/abstract_controller/filter_spec.rb +4 -0
- data/spec/public/controller/authentication_spec.rb +114 -43
- data/spec/public/controller/base_spec.rb +8 -0
- data/spec/public/controller/conditional_get_spec.rb +100 -0
- data/spec/public/controller/config/init.rb +1 -1
- data/spec/public/controller/controllers/authentication.rb +29 -0
- data/spec/public/controller/controllers/base.rb +13 -0
- data/spec/public/controller/controllers/conditional_get.rb +35 -0
- data/spec/public/controller/controllers/cookies.rb +10 -1
- data/spec/public/controller/cookies_spec.rb +38 -9
- data/spec/public/controller/spec_helper.rb +1 -0
- data/spec/public/controller/url_spec.rb +70 -1
- data/spec/public/directory_structure/directory/log/merb_test.log +461 -0
- data/spec/public/rack/conditinal_get_middleware_spec.rb +77 -89
- data/spec/public/rack/csrf_middleware_spec.rb +70 -0
- data/spec/public/reloading/directory/log/merb_test.log +52 -0
- data/spec/public/request/request_spec.rb +19 -1
- data/spec/public/router/fixation_spec.rb +26 -4
- data/spec/public/router/fixture/log/merb_test.log +234 -30332
- data/spec/public/session/controllers/sessions.rb +52 -0
- data/spec/public/session/cookie_session_spec.rb +73 -0
- data/spec/public/session/memcached_session_spec.rb +31 -0
- data/spec/public/session/memory_session_spec.rb +28 -0
- data/spec/public/session/multiple_sessions_spec.rb +74 -0
- data/spec/public/session/no_session_spec.rb +12 -0
- data/spec/public/session/session_spec.rb +91 -0
- data/spec/public/test/controllers/spec_helper_controller.rb +2 -1
- data/spec/public/test/request_helper_spec.rb +15 -0
- data/spec/spec_helper.rb +2 -2
- metadata +23 -5
- data/spec/private/dispatch/cookies_spec.rb +0 -219
- data/spec/private/dispatch/session_mixin_spec.rb +0 -47
@@ -1,184 +1,48 @@
|
|
1
1
|
module Merb
|
2
2
|
|
3
|
-
module SessionMixin
|
4
|
-
|
5
|
-
# Adds a before and after dispatch hook for setting up the memcached
|
6
|
-
# session store.
|
7
|
-
#
|
8
|
-
# ==== Parameters
|
9
|
-
# base<Class>:: The class to which the SessionMixin is mixed into.
|
10
|
-
def setup_session
|
11
|
-
orig_key = cookies[_session_id_key]
|
12
|
-
session, key = Merb::MemCacheSession.persist(orig_key)
|
13
|
-
request.session = session
|
14
|
-
@_fingerprint = Marshal.dump(request.session.data).hash
|
15
|
-
if key != orig_key
|
16
|
-
set_session_id_cookie(key)
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
# Finalizes the session by storing the session ID in a cookie, if the
|
21
|
-
# session has changed.
|
22
|
-
def finalize_session
|
23
|
-
if @_fingerprint != Marshal.dump(request.session.data).hash
|
24
|
-
begin
|
25
|
-
CACHE.set("session:#{request.session.session_id}", request.session.data)
|
26
|
-
rescue => err
|
27
|
-
Merb.logger.debug("MemCache Error: #{err.message}")
|
28
|
-
Merb::SessionMixin::finalize_session_exception_callbacks.each {|x| x.call(err) }
|
29
|
-
end
|
30
|
-
end
|
31
|
-
if request.session.needs_new_cookie or @_new_cookie
|
32
|
-
set_session_id_cookie(request.session.session_id)
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
# ==== Returns
|
37
|
-
# String:: The session store type, i.e. "memcache".
|
38
|
-
def session_store_type
|
39
|
-
"memcache"
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
##
|
44
3
|
# Sessions stored in memcached.
|
45
4
|
#
|
46
5
|
# Requires setup in your +init.rb+.
|
47
6
|
#
|
48
|
-
#
|
49
|
-
#
|
50
|
-
#
|
51
|
-
#
|
52
|
-
#
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
class MemCacheSession
|
61
|
-
|
62
|
-
attr_accessor :session_id
|
63
|
-
attr_accessor :data
|
64
|
-
attr_accessor :needs_new_cookie
|
65
|
-
|
66
|
-
# ==== Parameters
|
67
|
-
# session_id<String>:: A unique identifier for this session.
|
68
|
-
def initialize(session_id)
|
69
|
-
@session_id = session_id
|
70
|
-
@data = {}
|
71
|
-
end
|
72
|
-
|
73
|
-
class << self
|
74
|
-
|
75
|
-
# Generates a new session ID and creates a new session.
|
76
|
-
#
|
77
|
-
# ==== Returns
|
78
|
-
# MemCacheSession:: The new session.
|
79
|
-
def generate
|
80
|
-
sid = Merb::SessionMixin::rand_uuid
|
81
|
-
new(sid)
|
82
|
-
end
|
83
|
-
|
84
|
-
# ==== Parameters
|
85
|
-
# session_id<String:: The ID of the session to retrieve.
|
86
|
-
#
|
87
|
-
# ==== Returns
|
88
|
-
# Array::
|
89
|
-
# A pair consisting of a MemCacheSession and the session's ID. If no
|
90
|
-
# sessions matched session_id, a new MemCacheSession will be generated.
|
91
|
-
#
|
92
|
-
# ==== Notes
|
93
|
-
# If there are persiste exceptions callbacks to execute, they all get executed
|
94
|
-
# when Memcache library raises an exception.
|
95
|
-
def persist(session_id)
|
96
|
-
unless session_id.blank?
|
97
|
-
begin
|
98
|
-
session = CACHE.get("session:#{session_id}")
|
99
|
-
rescue => err
|
100
|
-
Merb.logger.warn!("Could not persist session to MemCache: #{err.message}")
|
101
|
-
Merb::SessionMixin::persist_exception_callbacks.each {|x| x.call(err) }
|
102
|
-
end
|
103
|
-
if session.nil?
|
104
|
-
# Not in memcached, but assume that cookie exists
|
105
|
-
session = new(session_id)
|
106
|
-
end
|
107
|
-
else
|
108
|
-
# No cookie...make a new session_id
|
109
|
-
session = generate
|
110
|
-
end
|
111
|
-
if session.is_a?(MemCacheSession)
|
112
|
-
[session, session.session_id]
|
113
|
-
else
|
114
|
-
# recreate using the rails session as the data
|
115
|
-
session_object = MemCacheSession.new(session_id)
|
116
|
-
session_object.data = session
|
117
|
-
[session_object, session_object.session_id]
|
118
|
-
end
|
119
|
-
end
|
120
|
-
|
121
|
-
# Don't try to reload in dev mode.
|
122
|
-
def reloadable?
|
123
|
-
false
|
124
|
-
end
|
125
|
-
|
126
|
-
end
|
127
|
-
|
128
|
-
# Regenerate the session ID.
|
129
|
-
def regenerate
|
130
|
-
@session_id = Merb::SessionMixin::rand_uuid
|
131
|
-
self.needs_new_cookie=true
|
132
|
-
end
|
133
|
-
|
134
|
-
# Recreates the cookie with the default expiration time. Useful during log
|
135
|
-
# in for pushing back the expiration date.
|
136
|
-
def refresh_expiration
|
137
|
-
self.needs_new_cookie=true
|
138
|
-
end
|
139
|
-
|
140
|
-
# Deletes the session by emptying stored data.
|
141
|
-
def delete
|
142
|
-
@data = {}
|
143
|
-
end
|
144
|
-
|
145
|
-
# ==== Returns
|
146
|
-
# Boolean:: True if session has been loaded already.
|
147
|
-
def loaded?
|
148
|
-
!! @data
|
149
|
-
end
|
150
|
-
|
151
|
-
# ==== Parameters
|
152
|
-
# k<~to_s>:: The key of the session parameter to set.
|
153
|
-
# v<~to_s>:: The value of the session parameter to set.
|
154
|
-
def []=(k, v)
|
155
|
-
@data[k] = v
|
156
|
-
end
|
157
|
-
|
158
|
-
# ==== Parameters
|
159
|
-
# k<~to_s>:: The key of the session parameter to retrieve.
|
160
|
-
#
|
161
|
-
# ==== Returns
|
162
|
-
# String:: The value of the session parameter.
|
163
|
-
def [](k)
|
164
|
-
@data[k]
|
165
|
-
end
|
166
|
-
|
167
|
-
# Yields the session data to an each block.
|
168
|
-
#
|
169
|
-
# ==== Parameter
|
170
|
-
# &b:: The block to pass to each.
|
171
|
-
def each(&b)
|
172
|
-
@data.each(&b)
|
173
|
-
end
|
174
|
-
|
175
|
-
private
|
7
|
+
# Merb::BootLoader.after_app_loads do
|
8
|
+
# require 'memcached'
|
9
|
+
# Merb::MemcacheSession.store =
|
10
|
+
# Memcached.new('127.0.0.1:11211', { :namespace => 'my_app' })
|
11
|
+
# end
|
12
|
+
|
13
|
+
class MemcacheSession < SessionStoreContainer
|
14
|
+
|
15
|
+
# The session store type
|
16
|
+
self.session_store_type = :memcache
|
17
|
+
|
18
|
+
end
|
176
19
|
|
177
|
-
|
178
|
-
def method_missing(name, *args, &block)
|
179
|
-
@data.send(name, *args, &block)
|
180
|
-
end
|
20
|
+
end
|
181
21
|
|
22
|
+
class Memcached
|
23
|
+
|
24
|
+
# Make the Memcached gem conform to the SessionStoreContainer interface
|
25
|
+
|
26
|
+
# ==== Parameters
|
27
|
+
# session_id<String>:: ID of the session to retrieve.
|
28
|
+
#
|
29
|
+
# ==== Returns
|
30
|
+
# ContainerSession:: The session corresponding to the ID.
|
31
|
+
def retrieve_session(session_id)
|
32
|
+
get("session:#{session_id}")
|
182
33
|
end
|
183
|
-
|
34
|
+
|
35
|
+
# ==== Parameters
|
36
|
+
# session_id<String>:: ID of the session to set.
|
37
|
+
# data<ContainerSession>:: The session to set.
|
38
|
+
def store_session(session_id, data)
|
39
|
+
set("session:#{session_id}", data)
|
40
|
+
end
|
41
|
+
|
42
|
+
# ==== Parameters
|
43
|
+
# session_id<String>:: ID of the session to delete.
|
44
|
+
def delete_session(session_id)
|
45
|
+
delete("session:#{session_id}")
|
46
|
+
end
|
47
|
+
|
184
48
|
end
|
@@ -1,37 +1,5 @@
|
|
1
1
|
module Merb
|
2
|
-
|
3
|
-
module SessionMixin
|
4
|
-
|
5
|
-
# Adds a before and after dispatch hook for setting up the memory session
|
6
|
-
# store.
|
7
|
-
#
|
8
|
-
# ==== Parameters
|
9
|
-
# base<Class>:: The class to which the SessionMixin is mixed into.
|
10
|
-
def setup_session
|
11
|
-
orig_key = cookies[_session_id_key]
|
12
|
-
session, key = Merb::MemorySession.persist(orig_key)
|
13
|
-
request.session = session
|
14
|
-
if key != orig_key
|
15
|
-
set_session_id_cookie(key)
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
# Finalizes the session by storing the session ID in a cookie, if the
|
20
|
-
# session has changed.
|
21
|
-
def finalize_session
|
22
|
-
if request.session.needs_new_cookie or @_new_cookie
|
23
|
-
set_session_id_cookie(request.session.session_id)
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
# ==== Returns
|
28
|
-
# String:: The session store type, i.e. "memory".
|
29
|
-
def session_store_type
|
30
|
-
"memory"
|
31
|
-
end
|
32
|
-
end
|
33
2
|
|
34
|
-
##
|
35
3
|
# Sessions stored in memory.
|
36
4
|
#
|
37
5
|
# Set it up by adding the following to your init file:
|
@@ -43,199 +11,85 @@ module Merb
|
|
43
11
|
#
|
44
12
|
# Sessions will remain in memory until the server is stopped or the time
|
45
13
|
# as set in :memory_session_ttl expires.
|
46
|
-
class MemorySession
|
47
|
-
|
48
|
-
attr_accessor :session_id
|
49
|
-
attr_accessor :data
|
50
|
-
attr_accessor :needs_new_cookie
|
51
|
-
|
52
|
-
# ==== Parameters
|
53
|
-
# session_id<String>:: A unique identifier for this session.
|
54
|
-
def initialize(session_id)
|
55
|
-
@session_id = session_id
|
56
|
-
@data = {}
|
57
|
-
end
|
58
|
-
|
59
|
-
class << self
|
60
|
-
|
61
|
-
# Generates a new session ID and creates a new session.
|
62
|
-
#
|
63
|
-
# ==== Returns
|
64
|
-
# MemorySession:: The new session.
|
65
|
-
def generate
|
66
|
-
sid = Merb::SessionMixin::rand_uuid
|
67
|
-
MemorySessionContainer[sid] = new(sid)
|
68
|
-
end
|
69
|
-
|
70
|
-
# ==== Parameters
|
71
|
-
# session_id<String:: The ID of the session to retrieve.
|
72
|
-
#
|
73
|
-
# ==== Returns
|
74
|
-
# Array::
|
75
|
-
# A pair consisting of a MemorySession and the session's ID. If no
|
76
|
-
# sessions matched session_id, a new MemorySession will be generated.
|
77
|
-
def persist(session_id)
|
78
|
-
if session_id
|
79
|
-
session = MemorySessionContainer[session_id]
|
80
|
-
end
|
81
|
-
unless session
|
82
|
-
session = generate
|
83
|
-
end
|
84
|
-
[session, session.session_id]
|
85
|
-
end
|
86
|
-
|
87
|
-
end
|
88
|
-
|
89
|
-
# Regenerate the Session ID
|
90
|
-
def regenerate
|
91
|
-
new_sid = Merb::SessionMixin::rand_uuid
|
92
|
-
old_sid = @session_id
|
93
|
-
MemorySessionContainer[new_sid] = MemorySessionContainer[old_sid]
|
94
|
-
@session_id = new_sid
|
95
|
-
MemorySessionContainer.delete(old_sid)
|
96
|
-
self.needs_new_cookie=true
|
97
|
-
end
|
98
|
-
|
99
|
-
# Recreates the cookie with the default expiration time. Useful during log
|
100
|
-
# in for pushing back the expiration date.
|
101
|
-
def refresh_expiration
|
102
|
-
self.needs_new_cookie=true
|
103
|
-
end
|
14
|
+
class MemorySession < SessionStoreContainer
|
104
15
|
|
105
|
-
#
|
106
|
-
|
107
|
-
|
16
|
+
# The session store type
|
17
|
+
self.session_store_type = :memory
|
18
|
+
|
19
|
+
# Bypass normal implicit class attribute reader - see below.
|
20
|
+
def store
|
21
|
+
self.class.store
|
108
22
|
end
|
109
|
-
|
110
|
-
#
|
111
|
-
|
112
|
-
|
113
|
-
!! @data
|
23
|
+
|
24
|
+
# Lazy load/setup of MemorySessionStore.
|
25
|
+
def self.store
|
26
|
+
@_store ||= MemorySessionStore.new(Merb::Config[:memory_session_ttl])
|
114
27
|
end
|
115
28
|
|
29
|
+
end
|
30
|
+
|
31
|
+
# Used for handling multiple sessions stored in memory.
|
32
|
+
class MemorySessionStore
|
33
|
+
|
116
34
|
# ==== Parameters
|
117
|
-
#
|
118
|
-
|
119
|
-
|
120
|
-
@
|
35
|
+
# ttl<Fixnum>:: Session validity time in seconds. Defaults to 1 hour.
|
36
|
+
def initialize(ttl=nil)
|
37
|
+
@sessions = Hash.new
|
38
|
+
@timestamps = Hash.new
|
39
|
+
@mutex = Mutex.new
|
40
|
+
@session_ttl = ttl || 60*60 # default 1 hour
|
41
|
+
start_timer
|
121
42
|
end
|
122
|
-
|
43
|
+
|
123
44
|
# ==== Parameters
|
124
|
-
#
|
45
|
+
# session_id<String>:: ID of the session to retrieve.
|
125
46
|
#
|
126
47
|
# ==== Returns
|
127
|
-
#
|
128
|
-
def
|
129
|
-
@
|
48
|
+
# ContainerSession:: The session corresponding to the ID.
|
49
|
+
def retrieve_session(session_id)
|
50
|
+
@mutex.synchronize {
|
51
|
+
@timestamps[session_id] = Time.now
|
52
|
+
@sessions[session_id]
|
53
|
+
}
|
130
54
|
end
|
131
55
|
|
132
|
-
#
|
133
|
-
#
|
134
|
-
#
|
135
|
-
|
136
|
-
|
137
|
-
|
56
|
+
# ==== Parameters
|
57
|
+
# session_id<String>:: ID of the session to set.
|
58
|
+
# data<ContainerSession>:: The session to set.
|
59
|
+
def store_session(session_id, data)
|
60
|
+
@mutex.synchronize {
|
61
|
+
@timestamps[session_id] = Time.now
|
62
|
+
@sessions[session_id] = data
|
63
|
+
}
|
138
64
|
end
|
139
|
-
|
140
|
-
private
|
141
65
|
|
142
|
-
#
|
143
|
-
|
144
|
-
|
66
|
+
# ==== Parameters
|
67
|
+
# session_id<String>:: ID of the session to delete.
|
68
|
+
def delete_session(session_id)
|
69
|
+
@mutex.synchronize {
|
70
|
+
@timestamps.delete(session_id)
|
71
|
+
@sessions.delete(session_id)
|
72
|
+
}
|
145
73
|
end
|
146
74
|
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
class << self
|
152
|
-
|
153
|
-
# ==== Parameters
|
154
|
-
# ttl<Fixnum>:: Session validity time in seconds. Defaults to 1 hour.
|
155
|
-
#
|
156
|
-
# ==== Returns
|
157
|
-
# MemorySessionContainer:: The new session container.
|
158
|
-
def setup(ttl=nil)
|
159
|
-
@sessions = Hash.new
|
160
|
-
@timestamps = Hash.new
|
161
|
-
@mutex = Mutex.new
|
162
|
-
@session_ttl = ttl || 60*60 # default 1 hour
|
163
|
-
start_timer
|
164
|
-
self
|
165
|
-
end
|
166
|
-
|
167
|
-
# Creates a new session based on the options.
|
168
|
-
#
|
169
|
-
# ==== Parameters
|
170
|
-
# opts<Hash>:: The session options (see below).
|
171
|
-
#
|
172
|
-
# ==== Options (opts)
|
173
|
-
# :session_id<String>:: ID of the session to create in the container.
|
174
|
-
# :data<MemorySession>:: The session to create in the container.
|
175
|
-
def create(opts={})
|
176
|
-
self[opts[:session_id]] = opts[:data]
|
177
|
-
end
|
178
|
-
|
179
|
-
# ==== Parameters
|
180
|
-
# key<String>:: ID of the session to retrieve.
|
181
|
-
#
|
182
|
-
# ==== Returns
|
183
|
-
# MemorySession:: The session corresponding to the ID.
|
184
|
-
def [](key)
|
185
|
-
@mutex.synchronize {
|
186
|
-
@timestamps[key] = Time.now
|
187
|
-
@sessions[key]
|
188
|
-
}
|
75
|
+
# Deletes any sessions that have reached their maximum validity.
|
76
|
+
def reap_old_sessions
|
77
|
+
@timestamps.each do |session_id,stamp|
|
78
|
+
delete_session(session_id) if (stamp + @session_ttl) < Time.now
|
189
79
|
end
|
80
|
+
GC.start
|
81
|
+
end
|
190
82
|
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
@sessions[key] = val
|
83
|
+
# Starts the timer that will eventually reap outdated sessions.
|
84
|
+
def start_timer
|
85
|
+
Thread.new do
|
86
|
+
loop {
|
87
|
+
sleep @session_ttl
|
88
|
+
reap_old_sessions
|
198
89
|
}
|
199
|
-
end
|
200
|
-
|
201
|
-
# ==== Parameters
|
202
|
-
# key<String>:: ID of the session to delete.
|
203
|
-
def delete(key)
|
204
|
-
@mutex.synchronize {
|
205
|
-
@sessions.delete(key)
|
206
|
-
@timestamps.delete(key)
|
207
|
-
}
|
208
|
-
end
|
209
|
-
|
210
|
-
# Deletes any sessions that have reached their maximum validity.
|
211
|
-
def reap_old_sessions
|
212
|
-
@timestamps.each do |key,stamp|
|
213
|
-
if stamp + @session_ttl < Time.now
|
214
|
-
delete(key)
|
215
|
-
end
|
216
|
-
end
|
217
|
-
GC.start
|
218
|
-
end
|
219
|
-
|
220
|
-
# Starts the timer that will eventually reap outdated sessions.
|
221
|
-
def start_timer
|
222
|
-
Thread.new do
|
223
|
-
loop {
|
224
|
-
sleep @session_ttl
|
225
|
-
reap_old_sessions
|
226
|
-
}
|
227
|
-
end
|
228
|
-
end
|
229
|
-
|
230
|
-
# ==== Returns
|
231
|
-
# Array:: The sessions stored in this container.
|
232
|
-
def sessions
|
233
|
-
@sessions
|
234
90
|
end
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
end # end MemorySessionContainer
|
239
|
-
end
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
240
94
|
|
241
|
-
|
95
|
+
end
|
@@ -0,0 +1,145 @@
|
|
1
|
+
module Merb
|
2
|
+
|
3
|
+
class SessionStoreContainer < SessionContainer
|
4
|
+
|
5
|
+
class_inheritable_accessor :store
|
6
|
+
attr_accessor :_fingerprint
|
7
|
+
|
8
|
+
# The class attribute :store holds a reference to an object that implements
|
9
|
+
# the following interface (either as class or instance methods):
|
10
|
+
#
|
11
|
+
# - retrieve_session(session_id) # returns data as Hash
|
12
|
+
# - store_session(session_id, data) # data should be a Hash
|
13
|
+
# - delete_session(session_id)
|
14
|
+
#
|
15
|
+
# You can use this session store directly by assigning to :store in your
|
16
|
+
# config/init.rb after_app_loads step, for example:
|
17
|
+
#
|
18
|
+
# Merb::BootLoader.after_app_loads do
|
19
|
+
# SessionStoreContainer.store = BarSession.new(:option => 'value')
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# Or you can inherit from SessionStoreContainer to create a SessionContainer
|
23
|
+
# that delegates to its 'store' attribute.
|
24
|
+
#
|
25
|
+
# class FooSession < SessionStoreContainer
|
26
|
+
#
|
27
|
+
# self.store = FooContainer
|
28
|
+
#
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# class FooContainer
|
32
|
+
#
|
33
|
+
# def self.retrieve_session(session_id)
|
34
|
+
# ...
|
35
|
+
# end
|
36
|
+
#
|
37
|
+
# def self.store_session(session_id, data)
|
38
|
+
# ...
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
# def self.delete_session(session_id)
|
42
|
+
# ...
|
43
|
+
# end
|
44
|
+
#
|
45
|
+
# end
|
46
|
+
|
47
|
+
# When used directly, report as :store store
|
48
|
+
self.session_store_type = :store
|
49
|
+
|
50
|
+
class << self
|
51
|
+
|
52
|
+
# Generates a new session ID and creates a new session.
|
53
|
+
#
|
54
|
+
# ==== Returns
|
55
|
+
# SessionStoreContainer:: The new session.
|
56
|
+
def generate
|
57
|
+
session = new(Merb::SessionMixin.rand_uuid)
|
58
|
+
session.needs_new_cookie = true
|
59
|
+
session
|
60
|
+
end
|
61
|
+
|
62
|
+
# Setup a new session.
|
63
|
+
#
|
64
|
+
# ==== Parameters
|
65
|
+
# request<Merb::Request>:: The Merb::Request that came in from Rack.
|
66
|
+
#
|
67
|
+
# ==== Returns
|
68
|
+
# SessionContainer:: a SessionContainer. If no sessions were found,
|
69
|
+
# a new SessionContainer will be generated.
|
70
|
+
def setup(request)
|
71
|
+
session = retrieve(request.session_id)
|
72
|
+
request.session = session
|
73
|
+
# TODO Marshal.dump is slow - needs optimization
|
74
|
+
session._fingerprint = Marshal.dump(request.session.to_hash).hash
|
75
|
+
session
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
# ==== Parameters
|
81
|
+
# session_id<String:: The ID of the session to retrieve.
|
82
|
+
#
|
83
|
+
# ==== Returns
|
84
|
+
# SessionStoreContainer:: SessionStoreContainer instance with the session data. If no
|
85
|
+
# sessions matched session_id, a new SessionStoreContainer will be generated.
|
86
|
+
#
|
87
|
+
# ==== Notes
|
88
|
+
# If there are persisted exceptions callbacks to execute, they all get executed
|
89
|
+
# when Memcache library raises an exception.
|
90
|
+
def retrieve(session_id)
|
91
|
+
unless session_id.blank?
|
92
|
+
begin
|
93
|
+
session_data = store.retrieve_session(session_id)
|
94
|
+
rescue => err
|
95
|
+
Merb.logger.warn!("Could not retrieve session from #{self.name}: #{err.message}")
|
96
|
+
end
|
97
|
+
# Not in container, but assume that cookie exists
|
98
|
+
session_data = new(session_id) if session_data.nil?
|
99
|
+
else
|
100
|
+
# No cookie...make a new session_id
|
101
|
+
session_data = generate
|
102
|
+
end
|
103
|
+
if session_data.is_a?(self)
|
104
|
+
session_data
|
105
|
+
else
|
106
|
+
# Recreate using the existing session as the data, when switching
|
107
|
+
# from another session type for example, eg. cookie to memcached
|
108
|
+
# or when the data is just a hash
|
109
|
+
new(session_id).update(session_data)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
114
|
+
|
115
|
+
# Teardown and/or persist the current session.
|
116
|
+
#
|
117
|
+
# ==== Parameters
|
118
|
+
# request<Merb::Request>:: The Merb::Request that came in from Rack.
|
119
|
+
#
|
120
|
+
# ==== Notes
|
121
|
+
# The data (self) is converted to a Hash first, since a container might
|
122
|
+
# choose to do a full Marshal on the data, which would make it persist
|
123
|
+
# attributes like 'needs_new_cookie', which it shouldn't.
|
124
|
+
def finalize(request)
|
125
|
+
if _fingerprint != Marshal.dump(data = self.to_hash).hash
|
126
|
+
begin
|
127
|
+
store.store_session(request.session(self.class.session_store_type).session_id, data)
|
128
|
+
rescue => err
|
129
|
+
Merb.logger.warn!("Could not persist session to #{self.class.name}: #{err.message}")
|
130
|
+
end
|
131
|
+
end
|
132
|
+
if needs_new_cookie || Merb::SessionMixin.needs_new_cookie?
|
133
|
+
request.set_session_id_cookie(session_id)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# Regenerate the session ID.
|
138
|
+
def regenerate
|
139
|
+
store.delete_session(self.session_id)
|
140
|
+
self.session_id = Merb::SessionMixin.rand_uuid
|
141
|
+
store.store_session(self.session_id, self)
|
142
|
+
end
|
143
|
+
|
144
|
+
end
|
145
|
+
end
|