class SBSM::Session
Constants
- CAP_MAX_THRESHOLD
- DEFAULT_FLAVOR
- DEFAULT_LANGUAGE
- DEFAULT_STATE
- DEFAULT_ZONE
- EXPIRES
- LF_FACTORY
- LOOKANDFEEL
- MAX_STATES
- PERSISTENT_COOKIE_NAME
- SERVER_NAME
- UNKNOWN_USER
Attributes
active_thread[R]
app[RW]
attended_states[R]
cgi[R]
key[R]
mtime[R]
post_content[R]
rack_request[RW]
request_method[R]
request_origin[R]
request_params[R]
request_path[R]
request_post[R]
server_name[R]
server_port[R]
trans_handler[RW]
unsafe_input[R]
user[R]
user_agent[R]
valid_input[R]
validator[RW]
Public Class Methods
new(app:, trans_handler: nil, validator: nil, unknown_user: SBSM::UnknownUser.new, cookie_name: nil, multi_threaded: false)
click to toggle source
Session: It will be initialized indirectly whenever SessionStore
cannot find a session in it cache. The parameters are given via SBSM::App.new
which calls SessionStore.new
optional arguments¶ ↑
-
validator
- A Ruby class overriding theSBSM::Validator
class -
trans_handler
- A Ruby class overriding theSBSM::TransHandler
class -
cookie_name
- The cookie to save persistent user data
Examples¶ ↑
Look at steinwies.ch
-
github.com/zdavatz/steinwies.ch (simple, mostly static files, one form, no persistence layer)
# File lib/sbsm/session.rb, line 109 def initialize(app:, trans_handler: nil, validator: nil, unknown_user: SBSM::UnknownUser.new, cookie_name: nil, multi_threaded: false) SBSM.info "initialize th #{trans_handler} validator #{validator} app #{app.class} multi_threaded #{multi_threaded}" @app = app @unknown_user = unknown_user.is_a?(Class) ? unknown_user.new : unknown_user @validator = validator if validator.is_a?(SBSM::Validator) @validator ||= (validator && validator.new) || Validator.new fail "invalid validator #{@validator}" unless @validator.is_a?(SBSM::Validator) @trans_handler = trans_handler || TransHandler.instance fail "invalid trans_handler #{@trans_handler}" unless @trans_handler.is_a?(SBSM::TransHandler) @persistent_cookie_name = cookie_name @persistent_cookie_name ||= self.class::PERSISTENT_COOKIE_NAME @attended_states = {} @persistent_user_input = {} touch() reset_input() reset_cookie() @user = SBSM::UnknownUser.new @variables = {} @cgi = CGI.initialize_without_offline_prompt('html4') @multi_threaded = multi_threaded @mutex = multi_threaded ? Mutex.new: @@mutex @active_thread = nil SBSM.debug "session initialized #{self} with @cgi #{@cgi} multi_threaded #{multi_threaded} app #{app.object_id} and user #{@user.class} @unknown_user #{@unknown_user.class}" end
reset_stats()
click to toggle source
# File lib/sbsm/session.rb, line 58 def Session.reset_stats @@stats = {} end
show_stats(ptrn=@@stats_ptrn)
click to toggle source
# File lib/sbsm/session.rb, line 63 def Session.show_stats ptrn=@@stats_ptrn if ptrn.is_a?(String) ptrn = /#{ptrn}/i end puts sprintf("%8s %8s %8s %6s %10s Request-Path", "Min", "Max", "Avg", "Num", "Total") grand_total = requests = all_max = all_min = 0 @@stats.collect do |path, times| total = times.inject do |a, b| a + b end grand_total += total size = times.size requests += size max = times.max all_max = max > all_max ? max : all_max min = times.min all_min = min < all_min ? min : all_min [min, max, total / size, size, total, path] end.sort.each do |data| line = sprintf("%8.2f %8.2f %8.2f %6i %10.2f %s", *data) if ptrn.match(line) puts line end end puts sprintf("%8s %8s %8s %6s %10s Request-Path", "Min", "Max", "Avg", "Num", "Total") puts sprintf("%8.2f %8.2f %8.2f %6i %10.2f", all_min, all_max, requests > 0 ? grand_total / requests : 0, requests, grand_total) '' end
Public Instance Methods
<=>(other)
click to toggle source
# File lib/sbsm/session.rb, line 613 def <=>(other) self.weighted_mtime <=> other.weighted_mtime end
==(other)
click to toggle source
Calls superclass method
# File lib/sbsm/session.rb, line 610 def ==(other) super end
[](key)
click to toggle source
# File lib/sbsm/session.rb, line 616 def [](key) @variables[key] end
[]=(key, val)
click to toggle source
# File lib/sbsm/session.rb, line 619 def []=(key, val) @variables[key] = val end
__checkout()
click to toggle source
# File lib/sbsm/session.rb, line 167 def __checkout @attended_states.each_value { |state| state.__checkout } @attended_states.clear flavor = @persistent_user_input[:flavor] lang = @persistent_user_input[:language] @persistent_user_input.clear @persistent_user_input.store(:flavor, flavor) @persistent_user_input.store(:language, lang) @valid_input.clear @unsafe_input.clear true end
age(now=Time.now)
click to toggle source
# File lib/sbsm/session.rb, line 150 def age(now=Time.now) now - @mtime end
async(&block)
click to toggle source
# File lib/sbsm/session.rb, line 622 def async(&block) @@async.add(Thread.new(&block)) end
cap_max_states()
click to toggle source
# File lib/sbsm/session.rb, line 153 def cap_max_states if(@attended_states.size > self::class::CAP_MAX_THRESHOLD) SBSM.info "too many states in session! Keeping only #{self::class::MAX_STATES}" sorted = @attended_states.values.sort sorted[0...(-self::class::MAX_STATES)].each { |state| state.__checkout @attended_states.delete(state.object_id) } @attended_states.size end end
client_activex?()
click to toggle source
# File lib/sbsm/session.rb, line 181 def client_activex? (ua = user_agent) && @@msie_ptrn.match(ua) && @@win_ptrn.match(ua) end
client_nt5?()
click to toggle source
# File lib/sbsm/session.rb, line 185 def client_nt5? (ua = user_agent) \ && (match = @@nt5_ptrn.match(user_agent)) \ && (match[1].to_f >= 5) end
close()
click to toggle source
# File lib/sbsm/session.rb, line 594 def close #@unix_socket.stop_service # nothing end
default_language()
click to toggle source
# File lib/sbsm/session.rb, line 200 def default_language self::class::DEFAULT_LANGUAGE end
delete()
click to toggle source
# File lib/sbsm/session.rb, line 598 def delete @app.delete_session(@key) end
direct_event()
click to toggle source
# File lib/sbsm/session.rb, line 203 def direct_event # used when @state.direct_event end
error(key)
click to toggle source
# File lib/sbsm/session.rb, line 284 def error(key) @state.error(key) if @state.respond_to?(:error) end
error?()
click to toggle source
# File lib/sbsm/session.rb, line 290 def error? @state.error? if @state.respond_to?(:error?) end
errors()
click to toggle source
# File lib/sbsm/session.rb, line 287 def errors @state.errors.values if @state.respond_to?(:errors) end
event()
click to toggle source
# File lib/sbsm/session.rb, line 293 def event @valid_input[:event] end
event_bound_user_input(key)
click to toggle source
# File lib/sbsm/session.rb, line 296 def event_bound_user_input(key) @event_user_input ||= {} evt = state.direct_event @event_user_input[evt] ||= {} if(val = user_input(key)) @event_user_input[evt][key] = val else @event_user_input[evt][key] end end
expired?(now=Time.now)
click to toggle source
# File lib/sbsm/session.rb, line 306 def expired?(now=Time.now) age(now) > EXPIRES end
flavor()
click to toggle source
# File lib/sbsm/session.rb, line 433 def flavor @flavor ||= begin user_input = persistent_user_input(:flavor) user_input ||= @valid_input[:default_flavor] lf_factory = self::class::LF_FACTORY if(lf_factory && lf_factory.include?(user_input)) user_input else self::class::DEFAULT_FLAVOR end end end
force_login(user)
click to toggle source
# File lib/sbsm/session.rb, line 309 def force_login(user) @user = user end
get_passthru()
click to toggle source
# File lib/sbsm/session.rb, line 477 def get_passthru @passthru ? [@passthru, @disposition] : [] end
http_headers()
click to toggle source
# File lib/sbsm/session.rb, line 458 def http_headers @state.http_headers rescue NameError, StandardError => err SBSM.info "NameError, StandardError: #@request_path" {'Content-Type' => 'text/plain'} end
http_protocol()
click to toggle source
# File lib/sbsm/session.rb, line 464 def http_protocol @http_protocol ||= if(@server_port && @server_port.to_i == 443) || ENV['SERVER_PORT'] 'https' else 'http' end end
import_user_input(rack_req)
click to toggle source
# File lib/sbsm/session.rb, line 334 def import_user_input(rack_req) return if(@user_input_imported) hash = rack_req.env.merge rack_req.params hash.merge! rack_req.POST if rack_req.POST hash.delete('rack.request.form_hash') # SBSM.debug "hash has #{hash.size } items #{hash.keys}" hash.each do |key, value| next if /^rack\./.match(key) index = nil @unsafe_input.push([key.to_s.dup, value.to_s.dup]) unless(key.nil? || key.empty?) if value.is_a?(Hash) key_sym = key.to_sym if @validator.validate(key_sym, value) @valid_input[key_sym] ||= {} value.each{ |k, v| @valid_input[key_sym][k] = v } end next end # Next for if match = @@hash_ptrn.match(key) key = match[1] index = match[2] # puts "key #{key} index #{index} value #{value}" end key = key.intern if(key == :confirm_pass) pass = rack_req.params["pass"] SBSM.debug "pass:#{pass} - confirm:#{value}" @valid_input[key] = @valid_input[:set_pass] \ = @validator.set_pass(pass, value) else valid = @validator.validate(key, value) # SBSM.debug "Checking #{key} -> #{value} valid #{valid.inspect} index #{index.inspect}" if(index) target = (@valid_input[key] ||= {}) indices = [] index.scan(@@index_ptrn) { |idx| indices.push idx } last = indices.pop indices.each { |idx| target = (target[idx] ||= {}) } target.store(last, valid) else @valid_input[key] = valid end end end end @user_input_imported = true end
info?()
click to toggle source
# File lib/sbsm/session.rb, line 392 def info? @state.info? if @state.respond_to?(:info?) end
infos()
click to toggle source
# File lib/sbsm/session.rb, line 389 def infos @state.infos if @state.respond_to?(:infos) end
input_keys()
click to toggle source
# File lib/sbsm/session.rb, line 471 def input_keys @valid_input.keys end
is_crawler?()
click to toggle source
# File lib/sbsm/session.rb, line 395 def is_crawler? crawler_pattern = /archiver|slurp|bot|crawler|jeeves|spider|\.{6}/i !!(@rack_request && crawler_pattern.match(@rack_request.user_agent)) end
key=(key)
click to toggle source
# File lib/sbsm/session.rb, line 164 def key=(key) @key = key end
language()
click to toggle source
# File lib/sbsm/session.rb, line 399 def language cookie_set_or_get(:language) || default_language end
logged_in?()
click to toggle source
# File lib/sbsm/session.rb, line 402 def logged_in? !@user.is_a?(SBSM::UnknownUser) end
login()
click to toggle source
# File lib/sbsm/session.rb, line 405 def login if(user = (@app && @app.respond_to?(:login) && @app.login(self))) SBSM.debug "user is #{user.class} #{request_path.inspect}" @user = user else SBSM.debug "login no user #{request_path.inspect}" end end
logout()
click to toggle source
# File lib/sbsm/session.rb, line 413 def logout __checkout @user = @unknown_user @active_state = @state = self::class::DEFAULT_STATE.new(self, @user) SBSM.debug "logout #{request_path.inspect} setting @state #{@state.object_id} #{@state.class} remember #{persistent_user_input(:remember).inspect} #{@user.class}" @state.init @attended_states.store(@state.object_id, @state) end
lookandfeel()
click to toggle source
# File lib/sbsm/session.rb, line 421 def lookandfeel if(@lookandfeel.nil? \ || (@lookandfeel.flavor != flavor) \ || (@lookandfeel.language != persistent_user_input(:language))) @lookandfeel = if self::class::LF_FACTORY self::class::LF_FACTORY.create(self) else self::class::LOOKANDFEEL.new(self) end end @lookandfeel end
method_missing(symbol, *args, &block)
click to toggle source
# File lib/sbsm/session.rb, line 138 def method_missing(symbol, *args, &block) # Replaces old dispatch to DRb @app.send(symbol, *args, &block) rescue => error puts error puts error.backtrace.join("\n") raise error end
passthru(path, disposition='attachment')
click to toggle source
# File lib/sbsm/session.rb, line 480 def passthru(path, disposition='attachment') # the variable @passthru is set by a trusted source @passthru = path @disposition = disposition '' end
persistent_user_input(key)
click to toggle source
# File lib/sbsm/session.rb, line 486 def persistent_user_input(key) if(value = user_input(key)) @persistent_user_input.store(key, value) else @persistent_user_input[key] end end
process_rack(rack_request:)
click to toggle source
# File lib/sbsm/session.rb, line 207 def process_rack(rack_request:) start = Time.now html = @mutex.synchronize do begin @passthru = false @disposition = false @rack_request = rack_request @request_path ||= rack_request.path @post_content = nil if rack_request.request_method.eql?('POST') rack_request.params.each do |k, v| # needed to test POST requests generated by curl (first parameter) or ARC (second parameter) if /xml/i.match(k) @post_content = "#{k} #{v}" break end end begin # needed for request generated by https://github.com/wiztools/rest-client rack_request.body.rewind # just to be sure @post_content = rack_request.body.read end unless @post_content if @post_content SBSM.debug "@post_content is #{@post_content}" else SBSM.debug "rack_request is #{rack_request}" end end rack_request.params.each { |key, val| @cgi.params.store(key, val) } @trans_handler.translate_uri(rack_request) @request_method =rack_request.request_method @request_path = rack_request.path if rack_request.env @request_origin = 'http' if (proto = rack_request.env['SERVER_PROTOCOL']) @request_origin = proto.downcase.match(/^\w+/)[0] end @request_origin += '://' @request_origin += rack_request.env['REMOTE_ADDR'] if rack_request.env['REMOTE_ADDR'] @remote_ip = rack_request.env['HTTP_X_FORWARDED_FOR'] end @server_name = rack_request.env['SERVER_NAME'] @server_port = rack_request.env['SERVER_PORT'] @request_params = rack_request.params logout unless @active_state validator.reset_errors() if validator && validator.respond_to?(:reset_errors) import_cookies(rack_request) import_user_input(rack_request) @state = active_state.trigger(event()) SBSM.debug "active_state.trigger state #{@state.object_id} #{@state.class} remember #{persistent_user_input(:remember).inspect}" #FIXME: is there a better way to distinguish returning states? # ... we could simply refuse to init if event == :sort, but that # would not solve the problem cleanly, I think. unless(@state.request_path) @state.request_path = @request_path @state.init end unless @state.volatile? SBSM.debug "Changing from #{@active_state.object_id} to state #{@state.class} #{@state.object_id} remember #{persistent_user_input(:remember).inspect} #{@user.class}" @active_state = @state @attended_states.store(@state.object_id, @state) else SBSM.debug "Stay in volatile state #{@state.object_id} #{@state.class}" end @zone = @active_state.zone @active_state.touch cap_max_states self.process_late() if self.respond_to?(:process_late) # needed for ODDB.org limit_request ensure @user_input_imported = false end to_html end (@@stats[@request_path] ||= []).push(Time.now - start) html end
remote_addr()
click to toggle source
# File lib/sbsm/session.rb, line 511 def remote_addr @remote_addr ||= if @request.respond_to?(:remote_addr) @request.remote_addr end end
remote_ip()
click to toggle source
# File lib/sbsm/session.rb, line 516 def remote_ip @remote_ip ||= if(@request.respond_to?(:remote_host)) @request.remote_host end if @remote_ip.to_s.index(',') saved = @remote_ip.clone @remote_ip = @remote_ip.first if @remote_ip.is_a?(Array) @remote_ip = @remote_ip.split(',').first.gsub(/[\[\]]/, '') if @remote_ip.is_a?(String) SBSM.info("remote_ip is weird #{saved.inspect} => #{@remote_ip.inspect}") end @remote_ip end
reset()
click to toggle source
# File lib/sbsm/session.rb, line 493 def reset if @redirected SBSM.debug "reached Session::reset" @redirected = false else reset_input() end end
reset_input()
click to toggle source
# File lib/sbsm/session.rb, line 504 def reset_input @valid_input = {} @processing_errors = {} @http_protocol = nil @flavor = nil @unsafe_input = [] end
restore()
click to toggle source
CGI::SessionHandler compatibility ruby-doc.org/stdlib-2.3.1/libdoc/cgi/rdoc/CGI.html should restore the values from a file, we return simply nothing
# File lib/sbsm/session.rb, line 588 def restore {} end
state(event=nil)
click to toggle source
# File lib/sbsm/session.rb, line 539 def state(event=nil) @active_state end
to_html()
click to toggle source
# File lib/sbsm/session.rb, line 546 def to_html @state.to_html(cgi) end
touch()
click to toggle source
# File lib/sbsm/session.rb, line 542 def touch @mtime = Time.now self end
unknown_user()
click to toggle source
# File lib/sbsm/session.rb, line 145 def unknown_user @unknown_user || SBSM::UnknownUser.new puts "unknown_user set to #{@unknown_user} class #{ @unknown_user.is_a?(Class)}" @unknown_user = @unknown_user.new if @unknown_user.is_a?(Class) end
update()
click to toggle source
# File lib/sbsm/session.rb, line 591 def update # nothing end
user_input(*keys)
click to toggle source
# File lib/sbsm/session.rb, line 553 def user_input(*keys) if(keys.size == 1) index = nil key = keys.first.to_s if match = @@input_ptrn.match(key) key = match[1] index = match[2] end key_sym = key.to_sym valid = @valid_input[key_sym] if(index && valid.respond_to?(:[])) valid[index] else valid end else keys.inject({}) { |inj, key| inj.store(key, user_input(key)) inj } end end
valid_values(key)
click to toggle source
# File lib/sbsm/session.rb, line 575 def valid_values(key) vals = @validator.valid_values(key) unless @validator.nil? vals || [] end
warning?()
click to toggle source
# File lib/sbsm/session.rb, line 582 def warning? @state.warning? if @state.respond_to?(:warning?) end
warnings()
click to toggle source
# File lib/sbsm/session.rb, line 579 def warnings @state.warnings if @state.respond_to?(:warnings) end
zone()
click to toggle source
# File lib/sbsm/session.rb, line 601 def zone @valid_input[:zone] || (@state && @state.zone) || self::class::DEFAULT_ZONE end
zones()
click to toggle source
# File lib/sbsm/session.rb, line 604 def zones @active_state.zones end
Protected Instance Methods
weighted_mtime()
click to toggle source
# File lib/sbsm/session.rb, line 633 def weighted_mtime @mtime + @user.session_weight end
Private Instance Methods
active_state()
click to toggle source
# File lib/sbsm/session.rb, line 626 def active_state if(state_id = @valid_input[:state_id]) @attended_states[state_id] end || @active_state end