sbsm 1.3.4 → 1.3.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/History.txt +5 -0
- data/lib/sbsm/app.rb +39 -34
- data/lib/sbsm/cgi.rb +0 -1
- data/lib/sbsm/session.rb +85 -75
- data/lib/sbsm/session_store.rb +179 -0
- data/lib/sbsm/state.rb +1 -0
- data/lib/sbsm/validator.rb +4 -3
- data/lib/sbsm/version.rb +1 -1
- data/lib/sbsm/viralstate.rb +1 -1
- data/test/config.ru +11 -0
- data/test/simple_sbsm.rb +17 -15
- data/test/test_application.rb +55 -44
- data/test/test_customized_app.rb +232 -0
- data/test/test_logger.rb +0 -0
- data/test/test_session.rb +94 -61
- data/test/wrk_visit.lua +12 -0
- metadata +9 -7
- data/lib/cgi/drbsession.rb +0 -39
- data/lib/sbsm/drb.rb +0 -22
- data/lib/sbsm/drbserver.rb +0 -167
- data/test/test_drbserver.rb +0 -84
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 098df1b1f3f9be3383dc1adbc44087a8662ac7e7
|
4
|
+
data.tar.gz: 3836d50af930294d0fa5bdacec1bcc04085880a4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cefada511b938ff19624f587ccc16feca6a33ac34b8875f24d9881c9504bd7e6a87df46bef3492d18b3ed9bb2d409d68c7230f83d5d9f38e060ce20888bdd3af
|
7
|
+
data.tar.gz: ccf6801e741324e0fc11df4f53a103e43085996a1793d6ad9a5207843440621c1f4fd778aeb3146ca1f2d81e92e2b55cdc9699db0132d2ab95a69e8206efae35
|
data/History.txt
CHANGED
@@ -1,3 +1,8 @@
|
|
1
|
+
=== 1.3.5 /14.12.2016
|
2
|
+
* Refactored sbsm to work without DRb
|
3
|
+
* Added test/config.ru to be able to run tests via wrk
|
4
|
+
* The interface to Session.new changed and must be adapted by client where the SBSM::Session class is overridden.
|
5
|
+
|
1
6
|
=== 1.3.4 /12.12.2016
|
2
7
|
* As we quite often got errors about recycled objects, I added a GC.start in the call method of sbsm/app
|
3
8
|
It introduces a delay of 12 to 30 ms. Therefore we should look for a better solution later
|
data/lib/sbsm/app.rb
CHANGED
@@ -26,50 +26,59 @@
|
|
26
26
|
require 'cgi'
|
27
27
|
require 'cgi/session'
|
28
28
|
require 'sbsm/cgi'
|
29
|
-
require '
|
30
|
-
require 'sbsm/
|
29
|
+
require 'sbsm/session_store'
|
30
|
+
require 'sbsm/trans_handler'
|
31
|
+
require 'sbsm/validator'
|
31
32
|
require 'mimemagic'
|
32
33
|
|
33
34
|
module SBSM
|
34
35
|
###
|
35
36
|
# App a base class for Webrick server
|
36
|
-
class App
|
37
|
-
include DRbUndumped
|
38
|
-
attr_reader :sbsm, :my_self, :trans_handler, :validator, :drb_uri
|
37
|
+
class App
|
39
38
|
|
40
|
-
OPTIONS = [ :app, :config_file, :trans_handler, :validator, :persistence_layer, :server_uri, :
|
39
|
+
OPTIONS = [ :app, :config_file, :trans_handler, :validator, :persistence_layer, :server_uri, :unknown_user]
|
41
40
|
OPTIONS.each{ |opt| eval "attr_reader :#{opt}" }
|
42
41
|
|
43
42
|
# Base class for a SBSM based WebRick HTTP server
|
44
|
-
# * offers a start_server() method to launch a DRB server for handling the DRB-requests
|
45
43
|
# * offer a call(env) method form handling the WebRick requests
|
46
44
|
# This is all what is needed to be compatible with WebRick
|
47
45
|
#
|
48
|
-
# === arguments
|
46
|
+
# === optional arguments
|
49
47
|
#
|
50
|
-
# * +app+ - The app we should handle requests for
|
51
48
|
# * +validator+ - A Ruby class overriding the SBSM::Validator class
|
52
49
|
# * +trans_handler+ - A Ruby class overriding the SBSM::TransHandler class
|
50
|
+
# * +session_class+ - A Ruby class overriding the SBSM::Session class
|
51
|
+
# * +unknown_user+ - A Ruby class overriding the SBSM::UnknownUser class
|
53
52
|
# * +persistence_layer+ - Persistence Layer to use
|
54
53
|
# * +cookie_name+ - The cookie to save persistent user data
|
55
|
-
# * +drb_uri+ - URI for DRB-Server of app
|
56
54
|
#
|
57
55
|
# === Examples
|
58
56
|
# Look at steinwies.ch
|
59
57
|
# * https://github.com/zdavatz/steinwies.ch (simple, mostly static files, one form, no persistence layer)
|
60
58
|
#
|
61
|
-
def initialize(
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
59
|
+
def initialize(validator: nil,
|
60
|
+
trans_handler: nil,
|
61
|
+
session_class: nil,
|
62
|
+
persistence_layer: nil,
|
63
|
+
unknown_user: nil,
|
64
|
+
cookie_name: nil)
|
65
|
+
@@last_session = nil
|
66
|
+
SBSM.info "initialize validator #{validator} th #{trans_handler} cookie #{cookie_name} session #{session_class}"
|
67
|
+
@session_store = SessionStore.new(persistence_layer: persistence_layer,
|
68
|
+
trans_handler: trans_handler,
|
69
|
+
session_class: session_class,
|
70
|
+
cookie_name: cookie_name,
|
71
|
+
unknown_user: unknown_user,
|
72
|
+
app: self,
|
73
|
+
validator: validator)
|
69
74
|
end
|
70
75
|
|
71
76
|
SESSION_ID = '_session_id'
|
72
77
|
|
78
|
+
def last_session
|
79
|
+
@@last_session
|
80
|
+
end
|
81
|
+
|
73
82
|
def call(env) ## mimick sbsm/lib/app.rb
|
74
83
|
request = Rack::Request.new(env)
|
75
84
|
response = Rack::Response.new
|
@@ -88,35 +97,31 @@ module SBSM
|
|
88
97
|
end
|
89
98
|
|
90
99
|
return [400, {}, []] if /favicon.ico/i.match(request.path)
|
91
|
-
|
100
|
+
# https://www.tutorialspoint.com/ruby/ruby_cgi_sessions.htm
|
92
101
|
args = {
|
93
|
-
'database_manager' =>
|
94
|
-
'drbsession_uri' => @drb_uri,
|
102
|
+
'database_manager' => @session_store,
|
95
103
|
'session_path' => '/',
|
96
104
|
@cookie_name => session_id,
|
97
105
|
}
|
98
|
-
|
106
|
+
session = @session_store[session_id]
|
107
|
+
session.app ||= self
|
108
|
+
SBSM.debug "starting session_id #{session_id} session #{session.class} #{request.path}: cookies #{@cookie_name} are #{request.cookies} @cgi #{@cgi.class}"
|
99
109
|
@cgi = CGI.initialize_without_offline_prompt('html4') unless @cgi
|
100
|
-
|
101
|
-
|
102
|
-
@start_time = Time.now.to_f
|
103
|
-
GC.start
|
104
|
-
SBSM.debug "GC.start took #{((Time.now.to_f)- @start_time)*1000.0} milliseconds"
|
105
|
-
@proxy = DRbObject.new(saved, server_uri) unless @proxy.is_a?(DRbObject)
|
106
|
-
@proxy.trans_handler = @trans_handler
|
107
|
-
@proxy.app = @app unless @proxy.app
|
108
|
-
res = @proxy.drb_process(self, request)
|
110
|
+
session = CGI::Session.new(@cgi, args) unless session
|
111
|
+
res = session.process_rack(rack_request: request)
|
109
112
|
response.write res
|
110
113
|
response.headers['Content-Type'] ||= 'text/html; charset=utf-8'
|
111
|
-
response.headers.merge!(
|
114
|
+
response.headers.merge!(session.http_headers)
|
112
115
|
if (result = response.headers.find { |k,v| /status/i.match(k) })
|
113
116
|
response.status = result.last.to_i
|
114
117
|
response.headers.delete(result.first)
|
115
118
|
end
|
116
|
-
response.set_cookie(
|
119
|
+
response.set_cookie(session.cookie_name, :value => session.cookie_input)
|
117
120
|
response.set_cookie(SESSION_ID, :value => session_id)
|
118
|
-
|
121
|
+
@@last_session = session
|
122
|
+
SBSM.debug "finish session_id #{session_id}: header with cookies #{response.headers} from #{session.cookie_input}"
|
119
123
|
response.finish
|
120
124
|
end
|
125
|
+
|
121
126
|
end
|
122
127
|
end
|
data/lib/sbsm/cgi.rb
CHANGED
data/lib/sbsm/session.rb
CHANGED
@@ -28,30 +28,28 @@
|
|
28
28
|
|
29
29
|
require 'cgi'
|
30
30
|
require 'sbsm/cgi'
|
31
|
-
require 'sbsm/drb'
|
32
31
|
require 'sbsm/state'
|
32
|
+
require 'sbsm/user'
|
33
33
|
require 'sbsm/lookandfeelfactory'
|
34
34
|
require 'delegate'
|
35
|
-
require 'sbsm/trans_handler'
|
36
35
|
|
37
36
|
module SBSM
|
38
|
-
class Session
|
39
|
-
attr_reader :user, :active_thread, :key, :cookie_input,
|
40
|
-
:unsafe_input, :valid_input, :request_path, :cgi
|
41
|
-
|
42
|
-
include DRbUndumped
|
37
|
+
class Session
|
38
|
+
attr_reader :user, :active_thread, :key, :cookie_input, :cookie_name,
|
39
|
+
:unsafe_input, :valid_input, :request_path, :cgi, :attended_states
|
40
|
+
attr_accessor :validator, :trans_handler, :app
|
43
41
|
PERSISTENT_COOKIE_NAME = "sbsm-persistent-cookie"
|
44
42
|
DEFAULT_FLAVOR = 'sbsm'
|
45
43
|
DEFAULT_LANGUAGE = 'en'
|
46
44
|
DEFAULT_STATE = State
|
47
45
|
DEFAULT_ZONE = nil
|
48
|
-
DRB_LOAD_LIMIT = 255 * 102400
|
49
46
|
EXPIRES = 60 * 60
|
50
47
|
LF_FACTORY = nil
|
51
48
|
LOOKANDFEEL = Lookandfeel
|
52
49
|
CAP_MAX_THRESHOLD = 8
|
53
50
|
MAX_STATES = 4
|
54
51
|
SERVER_NAME = nil
|
52
|
+
UNKNOWN_USER = UnknownUser
|
55
53
|
def Session.reset_stats
|
56
54
|
@@stats = {}
|
57
55
|
end
|
@@ -88,24 +86,54 @@ module SBSM
|
|
88
86
|
requests, grand_total)
|
89
87
|
''
|
90
88
|
end
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
89
|
+
|
90
|
+
# Session: It will be initialized indirectly whenever SessionStore cannot
|
91
|
+
# find a session in it cache. The parameters are given via SBSM::App.new
|
92
|
+
# which calls SessionStore.new
|
93
|
+
#
|
94
|
+
# === optional arguments
|
95
|
+
#
|
96
|
+
# * +validator+ - A Ruby class overriding the SBSM::Validator class
|
97
|
+
# * +trans_handler+ - A Ruby class overriding the SBSM::TransHandler class
|
98
|
+
# * +unknown_user+ - A Ruby class overriding the SBSM::UnknownUser class
|
99
|
+
# * +cookie_name+ - The cookie to save persistent user data
|
100
|
+
#
|
101
|
+
# === Examples
|
102
|
+
# Look at steinwies.ch
|
103
|
+
# * https://github.com/zdavatz/steinwies.ch (simple, mostly static files, one form, no persistence layer)
|
104
|
+
#
|
105
|
+
def initialize(app:,
|
106
|
+
trans_handler: nil,
|
107
|
+
validator: nil,
|
108
|
+
unknown_user: nil,
|
109
|
+
cookie_name: nil)
|
110
|
+
SBSM.info "initialize th #{trans_handler} validator #{validator}"
|
97
111
|
@app = app
|
98
|
-
@
|
112
|
+
@unknown_user = unknown_user
|
113
|
+
@unknown_user ||= self.class::UNKNOWN_USER
|
99
114
|
@validator = validator
|
115
|
+
@validator ||= Validator.new
|
116
|
+
fail "invalid validator #{@validator}" unless @validator.is_a?(SBSM::Validator)
|
117
|
+
@trans_handler = trans_handler
|
118
|
+
@trans_handler ||= TransHandler.instance
|
119
|
+
fail "invalid trans_handler #{@trans_handler}" unless @trans_handler.is_a?(SBSM::TransHandler)
|
120
|
+
@cookie_name = cookie_name
|
121
|
+
@cookie_name ||= self.class::PERSISTENT_COOKIE_NAME
|
100
122
|
@attended_states = {}
|
101
123
|
@persistent_user_input = {}
|
102
|
-
|
103
|
-
|
124
|
+
touch()
|
125
|
+
reset_input()
|
126
|
+
reset_cookie()
|
127
|
+
@user = @unknown_user
|
128
|
+
@unknown_user_class
|
129
|
+
@unknown_user_class = @unknown_user.class
|
104
130
|
@variables = {}
|
105
131
|
@mutex = Mutex.new
|
106
132
|
@cgi = CGI.initialize_without_offline_prompt('html4')
|
107
|
-
SBSM.debug "session initialized #{self}
|
108
|
-
|
133
|
+
SBSM.debug "session initialized #{self} with @cgi #{@cgi}"
|
134
|
+
end
|
135
|
+
def unknown_user
|
136
|
+
@unknown_user_class.new
|
109
137
|
end
|
110
138
|
def age(now=Time.now)
|
111
139
|
now - @mtime
|
@@ -155,9 +183,6 @@ module SBSM
|
|
155
183
|
def get_cookie_input(key)
|
156
184
|
@cookie_input[key]
|
157
185
|
end
|
158
|
-
def cookie_name
|
159
|
-
self::class::PERSISTENT_COOKIE_NAME
|
160
|
-
end
|
161
186
|
def default_language
|
162
187
|
self::class::DEFAULT_LANGUAGE
|
163
188
|
end
|
@@ -165,19 +190,47 @@ module SBSM
|
|
165
190
|
# used when
|
166
191
|
@state.direct_event
|
167
192
|
end
|
168
|
-
def
|
193
|
+
def process_rack(rack_request:)
|
169
194
|
start = Time.now
|
170
195
|
@request_path ||= rack_request.path
|
171
196
|
rack_request.params.each { |key, val| @cgi.params.store(key, val) }
|
172
197
|
@trans_handler.translate_uri(rack_request)
|
173
198
|
html = @mutex.synchronize do
|
174
|
-
|
199
|
+
begin
|
200
|
+
@request_method =rack_request.request_method
|
201
|
+
@request_path = rack_request.path
|
202
|
+
logout unless @active_state
|
203
|
+
validator.reset_errors() if validator && validator.respond_to?(:reset_errors)
|
204
|
+
import_user_input(rack_request)
|
205
|
+
import_cookies(rack_request)
|
206
|
+
@state = active_state.trigger(event())
|
207
|
+
SBSM.debug "active_state.trigger state #{@state.object_id} remember #{persistent_user_input(:remember).inspect}"
|
208
|
+
#FIXME: is there a better way to distinguish returning states?
|
209
|
+
# ... we could simply refuse to init if event == :sort, but that
|
210
|
+
# would not solve the problem cleanly, I think.
|
211
|
+
unless(@state.request_path)
|
212
|
+
@state.request_path = @request_path
|
213
|
+
@state.init
|
214
|
+
end
|
215
|
+
unless @state.volatile?
|
216
|
+
SBSM.debug "Changing from #{@active_state.object_id} to state #{@state.class} #{@state.object_id} remember #{persistent_user_input(:remember).inspect}"
|
217
|
+
@active_state = @state
|
218
|
+
@attended_states.store(@state.object_id, @state)
|
219
|
+
else
|
220
|
+
SBSM.debug "Stay in volatile state #{@state.object_id}"
|
221
|
+
end
|
222
|
+
@zone = @active_state.zone
|
223
|
+
@active_state.touch
|
224
|
+
cap_max_states
|
225
|
+
ensure
|
226
|
+
@user_input_imported = false
|
227
|
+
end
|
175
228
|
to_html
|
176
229
|
end
|
177
230
|
(@@stats[@request_path] ||= []).push(Time.now - start)
|
178
231
|
html
|
179
232
|
rescue => err
|
180
|
-
SBSM.info "Error in
|
233
|
+
SBSM.info "Error in process_rack #{err.backtrace[0..5].join("\n")}"
|
181
234
|
raise err
|
182
235
|
end
|
183
236
|
def error(key)
|
@@ -206,11 +259,11 @@ module SBSM
|
|
206
259
|
age(now) > EXPIRES
|
207
260
|
end
|
208
261
|
def force_login(user)
|
262
|
+
binding.pry
|
209
263
|
@user = user
|
210
264
|
end
|
211
265
|
def import_cookies(request)
|
212
266
|
reset_cookie()
|
213
|
-
return if request.cookies.is_a?(DRb::DRbUnknown)
|
214
267
|
if(cuki_str = request.cookies[self::class::PERSISTENT_COOKIE_NAME])
|
215
268
|
SBSM.debug "cuki_str #{self::class::PERSISTENT_COOKIE_NAME} #{cuki_str}"
|
216
269
|
eval(cuki_str).each { |key, val|
|
@@ -224,8 +277,6 @@ module SBSM
|
|
224
277
|
@@hash_ptrn = /([^\[]+)((\[[^\]]+\])+)/
|
225
278
|
@@index_ptrn = /[^\[\]]+/
|
226
279
|
def import_user_input(rack_req)
|
227
|
-
# attempting to read the cgi-params more than once results in a
|
228
|
-
# DRbConnectionRefused Exception. Therefore, do it only once...
|
229
280
|
return if(@user_input_imported)
|
230
281
|
hash = rack_req.env.merge rack_req.params
|
231
282
|
hash.merge! rack_req.POST if rack_req.POST
|
@@ -298,7 +349,7 @@ module SBSM
|
|
298
349
|
!@user.is_a?(@unknown_user_class)
|
299
350
|
end
|
300
351
|
def login
|
301
|
-
if(user = @app.login(self))
|
352
|
+
if(user = (@app && @app.respond_to?(:login) && @app.login(self)))
|
302
353
|
SBSM.debug "user is #{user} #{request_path.inspect}"
|
303
354
|
@user = user
|
304
355
|
else
|
@@ -307,8 +358,8 @@ module SBSM
|
|
307
358
|
end
|
308
359
|
def logout
|
309
360
|
__checkout
|
310
|
-
@user =
|
311
|
-
|
361
|
+
@user = unknown_user()
|
362
|
+
@active_state = @state = self::class::DEFAULT_STATE.new(self, @user)
|
312
363
|
SBSM.debug "logout #{request_path.inspect} setting @state #{@state.object_id} #{@state.class} remember #{persistent_user_input(:remember).inspect}"
|
313
364
|
@state.init
|
314
365
|
@attended_states.store(@state.object_id, @state)
|
@@ -339,8 +390,6 @@ module SBSM
|
|
339
390
|
end
|
340
391
|
def http_headers
|
341
392
|
@state.http_headers
|
342
|
-
rescue DRb::DRbConnError
|
343
|
-
raise
|
344
393
|
rescue NameError, StandardError => err
|
345
394
|
SBSM.info "NameError, StandardError: #@request_path"
|
346
395
|
{'Content-Type' => 'text/plain'}
|
@@ -369,40 +418,6 @@ module SBSM
|
|
369
418
|
@persistent_user_input[key]
|
370
419
|
end
|
371
420
|
end
|
372
|
-
def process(rack_request)
|
373
|
-
begin
|
374
|
-
@request_method =rack_request.request_method
|
375
|
-
@request = rack_request
|
376
|
-
@request_method ||= @request.request_method
|
377
|
-
@request_path = @request.path
|
378
|
-
@validator.reset_errors() if @validator && @validator.respond_to?(:reset_errors)
|
379
|
-
import_user_input(rack_request)
|
380
|
-
import_cookies(rack_request)
|
381
|
-
@state = active_state.trigger(event())
|
382
|
-
SBSM.debug "active_state.trigger state #{@state.object_id} remember #{persistent_user_input(:remember).inspect}"
|
383
|
-
#FIXME: is there a better way to distinguish returning states?
|
384
|
-
# ... we could simply refuse to init if event == :sort, but that
|
385
|
-
# would not solve the problem cleanly, I think.
|
386
|
-
unless(@state.request_path)
|
387
|
-
@state.request_path = @request_path
|
388
|
-
@state.init
|
389
|
-
end
|
390
|
-
unless @state.volatile?
|
391
|
-
SBSM.debug "Changing from #{@active_state.object_id} to state #{@state.object_id} remember #{persistent_user_input(:remember).inspect}"
|
392
|
-
@active_state = @state
|
393
|
-
@attended_states.store(@state.object_id, @state)
|
394
|
-
else
|
395
|
-
SBSM.debug "Stay in volatile state #{@state.object_id}"
|
396
|
-
end
|
397
|
-
@zone = @active_state.zone
|
398
|
-
@active_state.touch
|
399
|
-
cap_max_states
|
400
|
-
rescue DRb::DRbConnError
|
401
|
-
raise
|
402
|
-
ensure
|
403
|
-
@user_input_imported = false
|
404
|
-
end
|
405
|
-
end
|
406
421
|
def reset
|
407
422
|
if @redirected
|
408
423
|
SBSM.debug "reached Session::reset"
|
@@ -441,8 +456,6 @@ module SBSM
|
|
441
456
|
else
|
442
457
|
self::class::SERVER_NAME
|
443
458
|
end
|
444
|
-
rescue DRb::DRbConnError
|
445
|
-
@server_name = self::class::SERVER_NAME
|
446
459
|
end
|
447
460
|
def state(event=nil)
|
448
461
|
@active_state
|
@@ -453,8 +466,6 @@ module SBSM
|
|
453
466
|
end
|
454
467
|
def to_html
|
455
468
|
@state.to_html(cgi)
|
456
|
-
rescue DRb::DRbConnError
|
457
|
-
raise
|
458
469
|
end
|
459
470
|
def user_agent
|
460
471
|
@user_agent ||= (@request.user_agent if @request.respond_to?(:user_agent))
|
@@ -493,11 +504,10 @@ module SBSM
|
|
493
504
|
@state.warning? if @state.respond_to?(:warning?)
|
494
505
|
end
|
495
506
|
# CGI::SessionHandler compatibility
|
507
|
+
# https://ruby-doc.org/stdlib-2.3.1/libdoc/cgi/rdoc/CGI.html
|
508
|
+
# should restore the values from a file, we return simply nothing
|
496
509
|
def restore
|
497
|
-
|
498
|
-
:proxy => self,
|
499
|
-
}
|
500
|
-
hash
|
510
|
+
{}
|
501
511
|
end
|
502
512
|
def update
|
503
513
|
# nothing
|
@@ -0,0 +1,179 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# encoding: utf-8
|
3
|
+
#--
|
4
|
+
#
|
5
|
+
# State Based Session Management
|
6
|
+
# Copyright (C) 2004 Hannes Wyss
|
7
|
+
#
|
8
|
+
# This library is free software; you can redistribute it and/or
|
9
|
+
# modify it under the terms of the GNU Lesser General Public
|
10
|
+
# License as published by the Free Software Foundation; either
|
11
|
+
# version 2.1 of the License, or (at your option) any later version.
|
12
|
+
#
|
13
|
+
# This library is distributed in the hope that it will be useful,
|
14
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
15
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
16
|
+
# Lesser General Public License for more details.
|
17
|
+
#
|
18
|
+
# You should have received a copy of the GNU Lesser General Public
|
19
|
+
# License along with this library; if not, write to the Free Software
|
20
|
+
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
21
|
+
#
|
22
|
+
# ywesee - intellectual capital connected, Winterthurerstrasse 52, CH-8006 Zürich, Switzerland
|
23
|
+
# hwyss@ywesee.com
|
24
|
+
#
|
25
|
+
# SessionStore -- sbsm -- niger@ywesee.com
|
26
|
+
# 2016: ported this code from the old DrbServer
|
27
|
+
#++
|
28
|
+
|
29
|
+
require 'delegate'
|
30
|
+
require 'sbsm/session'
|
31
|
+
require 'sbsm/user'
|
32
|
+
require 'thread'
|
33
|
+
require 'digest/md5'
|
34
|
+
require 'sbsm/logger'
|
35
|
+
require 'sbsm/validator'
|
36
|
+
|
37
|
+
module SBSM
|
38
|
+
#
|
39
|
+
# SessionStore manages session, which are kept in memory
|
40
|
+
# Sessions are limited and expire after some time
|
41
|
+
# The maximal amount of concurrent session is given via CAP_MAX_THRESHOLD
|
42
|
+
#
|
43
|
+
class SessionStore
|
44
|
+
CLEANING_INTERVAL = 30
|
45
|
+
CAP_MAX_THRESHOLD = 120
|
46
|
+
ENABLE_ADMIN = false
|
47
|
+
MAX_SESSIONS = 100
|
48
|
+
RUN_CLEANER = true
|
49
|
+
SESSION = Session
|
50
|
+
UNKNOWN_USER = UnknownUser
|
51
|
+
VALIDATOR = nil
|
52
|
+
attr_reader :cleaner, :updater, :persistence_layer
|
53
|
+
def initialize(app:,
|
54
|
+
persistence_layer: nil,
|
55
|
+
trans_handler: nil,
|
56
|
+
session_class: nil,
|
57
|
+
validator: nil,
|
58
|
+
cookie_name: nil,
|
59
|
+
unknown_user: nil)
|
60
|
+
fail "You must specify an app!" unless app
|
61
|
+
@sessions = {}
|
62
|
+
@mutex = Mutex.new
|
63
|
+
@cleaner = run_cleaner if(self.class.const_get(:RUN_CLEANER))
|
64
|
+
@admin_threads = ThreadGroup.new
|
65
|
+
@async = ThreadGroup.new
|
66
|
+
@system = persistence_layer
|
67
|
+
@persistence_layer = persistence_layer
|
68
|
+
@cookie_name = cookie_name
|
69
|
+
@trans_handler = trans_handler
|
70
|
+
@trans_handler ||= TransHandler.instance
|
71
|
+
@session_class = session_class
|
72
|
+
@session_class ||= SBSM::Session
|
73
|
+
@unknown_user = unknown_user
|
74
|
+
@unknown_user ||= UNKNOWN_USER
|
75
|
+
@validator = validator
|
76
|
+
end
|
77
|
+
def _admin(src, result, priority=0)
|
78
|
+
raise "admin interface disabled" unless(self::class::ENABLE_ADMIN)
|
79
|
+
t = Thread.new {
|
80
|
+
Thread.current.abort_on_exception = false
|
81
|
+
result << begin
|
82
|
+
response = begin
|
83
|
+
instance_eval(src)
|
84
|
+
rescue NameError => e
|
85
|
+
e
|
86
|
+
end
|
87
|
+
str = response.to_s
|
88
|
+
if(str.length > 200)
|
89
|
+
response.class
|
90
|
+
else
|
91
|
+
str
|
92
|
+
end
|
93
|
+
rescue StandardError => e
|
94
|
+
e.message
|
95
|
+
end.to_s
|
96
|
+
}
|
97
|
+
t[:source] = src
|
98
|
+
t.priority = priority
|
99
|
+
@admin_threads.add(t)
|
100
|
+
t
|
101
|
+
end
|
102
|
+
def async(&block)
|
103
|
+
@async.add(Thread.new(&block))
|
104
|
+
end
|
105
|
+
def cap_max_sessions(now = Time.now)
|
106
|
+
if(@sessions.size > self::class::CAP_MAX_THRESHOLD)
|
107
|
+
SBSM.info "too many sessions! Keeping only #{self::class::MAX_SESSIONS}"
|
108
|
+
sess = nil
|
109
|
+
sorted = @sessions.values.sort
|
110
|
+
sorted[0...(-self::class::MAX_SESSIONS)].each { |sess|
|
111
|
+
sess.__checkout
|
112
|
+
@sessions.delete(sess.key)
|
113
|
+
}
|
114
|
+
if(sess)
|
115
|
+
age = sess.age(now)
|
116
|
+
SBSM.info sprintf("deleted all sessions that had not been accessed for more than %im %is", age / 60, age % 60)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
def clean
|
121
|
+
now = Time.now
|
122
|
+
@sessions.delete_if { |key, s|
|
123
|
+
begin
|
124
|
+
if s.respond_to?(:expired?)
|
125
|
+
if s.expired?(now)
|
126
|
+
s.__checkout
|
127
|
+
true
|
128
|
+
else
|
129
|
+
s.cap_max_states
|
130
|
+
false
|
131
|
+
end
|
132
|
+
else
|
133
|
+
true
|
134
|
+
end
|
135
|
+
rescue
|
136
|
+
true
|
137
|
+
end
|
138
|
+
}
|
139
|
+
#cap_max_sessions(now)
|
140
|
+
end
|
141
|
+
def clear
|
142
|
+
@sessions.each_value { |sess| sess.__checkout }
|
143
|
+
@sessions.clear
|
144
|
+
end
|
145
|
+
def delete_session(key)
|
146
|
+
if(sess = @sessions.delete(key))
|
147
|
+
sess.__checkout
|
148
|
+
end
|
149
|
+
end
|
150
|
+
def reset
|
151
|
+
@mutex.synchronize {
|
152
|
+
@sessions.clear
|
153
|
+
}
|
154
|
+
end
|
155
|
+
def run_cleaner
|
156
|
+
# puts "running cleaner thread"
|
157
|
+
Thread.new do
|
158
|
+
Thread.current.abort_on_exception = true
|
159
|
+
#Thread.current.priority = 1
|
160
|
+
loop do
|
161
|
+
sleep self::class::CLEANING_INTERVAL
|
162
|
+
@mutex.synchronize do
|
163
|
+
clean()
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
def [](key)
|
169
|
+
@mutex.synchronize do
|
170
|
+
unless((s = @sessions[key]) && !s.expired?)
|
171
|
+
s = @sessions[key] = @session_class.new(app: @app, cookie_name: @cookie_name, trans_handler: @trans_handler, validator: @validator, unknown_user: @unknown_user)
|
172
|
+
end
|
173
|
+
s.reset()
|
174
|
+
s.touch()
|
175
|
+
s
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|