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