class Knjappserver
The class that stands for the whole appserver / webserver.
Examples¶ ↑
appsrv = Knjappserver.new( :locales_root => "/some/path/locales", :locales_gettext_funcs => true, :magic_methods => true ) appsrv.start appsrv.join
Constants
- DATABASE_SCHEMA
Attributes
Public Class Methods
Autoloader for subclasses.
# File lib/include/class_knjappserver.rb, line 24 def self.const_missing(name) require "#{File.dirname(__FILE__)}/class_#{name.to_s.downcase}.rb" return Knjappserver.const_get(name) end
# File lib/scripts/knjappserver_cgi.rb, line 35 def self.convert_fcgi_post(params) post_hash = {} params.each do |key, val| post_hash[key] = val.first end return post_hash end
# File lib/include/class_knjappserver.rb, line 446 def self.data raise "Could not register current thread." if !Thread.current[:knjappserver] return Thread.current[:knjappserver] end
# File lib/scripts/knjappserver_fcgi.rb, line 19 def self.fcgi_start(cgi) raise "No HTTP_KNJAPPSERVER_CGI_CONFIG-header was given." if !cgi.env_table["HTTP_KNJAPPSERVER_CGI_CONFIG"] require cgi.env_table["HTTP_KNJAPPSERVER_CGI_CONFIG"] begin conf = Knjappserver::CGI_CONF rescue NameError raise "No 'Knjappserver::CGI_CONF'-constant was spawned by '#{cgi.env_table["HTTP_KNJAPPSERVER_CGI_CONFIG"]}'." end knjappserver_conf = Knjappserver::CGI_CONF["knjappserver"] knjappserver_conf.merge!( :cmdline => false, :port => 0 #Ruby picks random port and we get the actual port after starting the appserver. ) knjappserver = Knjappserver.new(knjappserver_conf) knjappserver.start port = knjappserver.port http = Http2.new(:host => "localhost", :port => port) return [knjappserver, http] end
# File lib/include/class_knjappserver.rb, line 29 def initialize(config) raise "No arguments given." if !config.is_a?(Hash) @config = { :host => "0.0.0.0", :timeout => 30, :default_page => "index.rhtml", :default_filetype => "text/html", :max_requests_working => 20, :size_send => 1024, :cleaner_timeout => 300 }.merge(config) @config[:smtp_args] = {"smtp_host" => "localhost", "smtp_port" => 25} if !@config[:smtp_args] @config[:timeout] = 30 if !@config.has_key?(:timeout) raise "No ':doc_root' was given in arguments." if !@config.has_key?(:doc_root) #Require gems. gems = %w[datet php4r] gems.each do |gem| puts "Loading gem: '#{gem}'." if @debug require gem end #Setup default handlers if none are given. if !@config.has_key?(:handlers) @erbhandler = Knjappserver::ERBHandler.new @config[:handlers] = [ { :file_ext => "rhtml", :callback => @erbhandler.method(:erb_handler) },{ :path => "/fckeditor", :mount => "/usr/share/fckeditor" } ] end #Add extra handlers if given. @config[:handlers] += @config[:handlers_extra] if @config[:handlers_extra] #Setup cache to make .rhtml-calls faster. @config[:handlers_cache] = {} @config[:handlers].each do |handler_info| next if !handler_info[:file_ext] or !handler_info[:callback] @config[:handlers_cache][handler_info[:file_ext]] = handler_info[:callback] end @debug = @config[:debug] @paused = 0 @paused_mutex = Mutex.new @should_restart = false @mod_events = {} @served = 0 @mod_files = {} @sessions = {} @eruby_cache = {} @httpsessions_ids = {} @path_knjappserver = File.dirname(__FILE__) if @config[:knjrbfw_path] @path_knjrbfw = @config[:knjrbfw_path] else @path_knjrbfw = "" end #If auto-restarting is enabled - start the modified events-module. if @config[:autorestart] paths = [ "#{@path_knjappserver}/../knjappserver.rb", "#{@path_knjappserver}/class_knjappserver.rb", "#{@path_knjappserver}/class_customio.rb" ] print "Auto restarting.\n" if @debug @mod_event = Knj::Event_filemod.new(:wait => 2, :paths => paths, &self.method(:on_event_filemod)) end #Set up default file-types and merge given filetypes into it. @types = { :ico => "image/x-icon", :jpeg => "image/jpeg", :jpg => "image/jpeg", :gif => "image/gif", :png => "image/png", :html => "text/html", :htm => "text/html", :rhtml => "text/html", :css => "text/css", :xml => "text/xml", :js => "text/javascript" } @types.merge!(@config[:filetypes]) if @config.has_key?(:filetypes) #Load various required files from knjrbfw and stuff in the knjappserver-framework. files = [ "#{@path_knjrbfw}knjrbfw.rb", "#{@path_knjappserver}/class_knjappserver_errors.rb", "#{@path_knjappserver}/class_knjappserver_logging.rb", "#{@path_knjappserver}/class_knjappserver_mailing.rb", "#{@path_knjappserver}/class_knjappserver_sessions.rb", "#{@path_knjappserver}/class_knjappserver_translations.rb", "#{@path_knjappserver}/class_knjappserver_web.rb" ] files << "#{@path_knjrbfw}knj/gettext_threadded.rb" if @config[:locales_root] files.each do |file| STDOUT.print "Loading: '#{file}'.\n" if @debug self.loadfile(file) end print "Setting up database.\n" if @debug if @config[:db].is_a?(Knj::Db) @db = @config[:db] elsif @config[:db].is_a?(Hash) @db = Knj::Db.new(@config[:db]) elsif !@config[:db] and @config[:db_args] @db = Knj::Db.new(@config[:db_args]) else raise "Unknown object given as db: '#{@config[:db].class.name}'." end if !@config.key?(:dbrev) or @config[:dbrev] print "Updating database.\n" if @debug dbschemapath = "#{File.dirname(__FILE__)}/../files/database_schema.rb" raise "'#{dbschemapath}' did not exist." if !File.exists?(dbschemapath) require dbschemapath raise "No schema-variable was spawned." if !Knjappserver::DATABASE_SCHEMA dbrev_args = {"schema" => Knjappserver::DATABASE_SCHEMA, "db" => @db} dbrev_args.merge!(@config[:dbrev_args]) if @config.key?(:dbrev_args) Knj::Db::Revision.new.init_db(dbrev_args) dbrev_args = nil end print "Spawning objects.\n" if @debug @ob = Knj::Objects.new( :db => db, :class_path => @path_knjappserver, :module => Knjappserver, :datarow => true, :knjappserver => self ) @ob.events.connect(:no_date, &self.method(:no_date)) if @config[:httpsession_db_args] @db_handler = Knj::Db.new(@config[:httpsession_db_args]) else @db_handler = @db end #Start the Knj::Gettext_threadded- and Knj::Translations modules for translations. print "Loading Gettext and translations.\n" if @debug @translations = Knj::Translations.new(:db => @db) @ob.requireclass(:Translation, {:require => false, :class => Knj::Translations::Translation}) if @config[:locales_root] @gettext = Knj::Gettext_threadded.new("dir" => config[:locales_root]) end require "#{@path_knjappserver}/gettext_funcs" if @config[:locales_gettext_funcs] if @config[:magic_methods] or !@config.has_key?(:magic_methods) print "Loading magic-methods.\n" if @debug require "#{@path_knjappserver}/magic_methods" end if @config[:customio] or !@config.has_key?(:customio) print "Loading custom-io.\n" if @debug if $stdout.class.name != "Knjappserver::CustomIO" require "#{@path_knjappserver}/class_customio.rb" @cio = Knjappserver::CustomIO.new $stdout = @cio end end #Save the PID to the run-file. print "Setting run-file.\n" if @debug tmpdir = "#{Knj::Os.tmpdir}/knjappserver" tmppath = "#{tmpdir}/run_#{@config[:title]}" if !File.exists?(tmpdir) Dir.mkdir(tmpdir) File.chmod(0777, tmpdir) end File.open(tmppath, "w") do |fp| fp.write(Process.pid) end File.chmod(0777, tmppath) #Set up various events for the appserver. if !@config.key?(:events) or @config[:events] print "Loading events.\n" if @debug @events = Knj::Event_handler.new @events.add_event( :name => :check_page_access, :connections_max => 1 ) @events.add_event( :name => :ob, :connections_max => 1 ) @events.add_event( :name => :trans_no_str, :connections_max => 1 ) @events.add_event( :name => :request_done, :connections_max => 1 ) @events.add_event( :name => :request_begin, :connections_max => 1 ) #This event is used if the user himself wants stuff to be cleaned up when the appserver is cleaning up stuff. @events.add_event( :name => :on_clean ) end #Set up the 'vars'-variable that can be used to set custom global variables for web-requests. @vars = Knj::Hash_methods.new @magic_vars = {} @magic_procs = {} #Initialize the various feature-modules. print "Init sessions.\n" if @debug self.initialize_sessions if !@config.key?(:threadding) or @config[:threadding] self.loadfile("#{@path_knjappserver}/class_knjappserver_threadding.rb") self.loadfile("#{@path_knjappserver}/class_knjappserver_threadding_timeout.rb") print "Init threadding.\n" if @debug self.initialize_threadding end print "Init mailing.\n" if @debug self.initialize_mailing print "Init errors.\n" if @debug self.initialize_errors print "Init logging.\n" if @debug self.initialize_logging if !@config.key?(:cleaner) or @config[:cleaner] self.loadfile("#{@path_knjappserver}/class_knjappserver_cleaner.rb") print "Init cleaner.\n" if @debug self.initialize_cleaner end if !@config.key?(:cmdline) or @config[:cmdline] self.loadfile("#{@path_knjappserver}/class_knjappserver_cmdline.rb") print "Init cmdline.\n" if @debug self.initialize_cmdline end #Clear memory at exit. Kernel.at_exit(&self.method(:stop)) print "Appserver spawned.\n" if @debug end
Public Instance Methods
Sends a javascript back to the browser and exits.
# File lib/include/class_knjappserver_web.rb, line 126 def back Knj::Web.back end
# File lib/include/class_knjappserver_cleaner.rb, line 10 def clean self.clean_sessions self.clean_autorestart end
# File lib/include/class_knjappserver_cleaner.rb, line 15 def clean_autorestart begin if @config[:autorestart] time = 1 else time = 15 end loop do sleep time if @config.has_key?(:restart_when_used_memory) and !@should_restart mbs_used = (Php4r.memory_get_usage / 1024) / 1024 STDOUT.print "Restart when over #{@config[:restart_when_used_memory]}mb\n" if @config[:debug] STDOUT.print "Used: #{mbs_used}mb\n" if @config[:debug] if mbs_used.to_i >= @config[:restart_when_used_memory].to_i STDOUT.print "Memory is over #{@config[:restart_when_used_memory]} - restarting.\n" @should_restart = true end end if @should_restart and !@should_restart_done and !@should_restart_runnning begin @should_restart_runnning = true #When we begin to restart it should go as fast as possible - so start by flushing out any emails waiting so it goes faster the last time... STDOUT.print "Flushing mails.\n" self.mail_flush #Lets try to find a time where no thread is working within the next 30 seconds. If we cant - we interrupt after 10 seconds and restart the server. begin Timeout.timeout(30) do loop do working_count = self.httpserv.working_count working = false if working_count and working_count > 0 working = true STDOUT.print "Someone is working - wait two sec and try to restart again!\n" end if !working STDOUT.print "Found window where no sessions were active - restarting!\n" break else sleep 0.2 end STDOUT.print "Trying to find window with no active sessions to restart...\n" end end rescue Timeout::Error STDOUT.print "Could not find a timing window for restarting... Forcing restart!\n" end #Flush emails again if any are pending (while we tried to find a window to restart)... STDOUT.print "Flushing mails.\n" self.mail_flush STDOUT.print "Stopping appserver.\n" self.stop STDOUT.print "Figuring out restart-command.\n" mycmd = @config[:restart_cmd] if !mycmd or mycmd.to_s.strip.length <= 0 fpath = File.realpath("#{File.dirname(__FILE__)}/../knjappserver.rb") mycmd = Knj::Os.executed_cmd STDOUT.print "Previous cmd: #{mycmd}\n" mycmd = mycmd.gsub(/\s+knjappserver.rb/, " #{Knj::Strings.unixsafe(fpath)}") end STDOUT.print "Restarting knjAppServer with command: #{mycmd}\n" @should_restart_done = true print exec(mycmd) exit rescue => e STDOUT.puts e.inspect STDOUT.puts e.backtrace end end end rescue => e self.handle_error(e) end end
This method can be used to clean the appserver. Dont call this from a HTTP-request.
# File lib/include/class_knjappserver_cleaner.rb, line 105 def clean_sessions STDOUT.print "Cleaning sessions on appserver.\n" if @config[:debug] #Clean up various inactive sessions. session_not_ids = [] time_check = Time.now.to_i - 300 newsessions = {} @sessions.each do |session_hash, session_data| session_data[:dbobj].flush if session_data[:time_lastused].to_i > time_check newsessions[session_hash] = session_data session_not_ids << session_data[:dbobj].id end end @sessions = newsessions STDOUT.print "Delete sessions...\n" if @config[:debug] @ob.list(:Session, {"id_not" => session_not_ids, "date_lastused_below" => (Time.now - 5356800)}) do |session| idhash = session[:idhash] STDOUT.print "Deleting session: '#{session.id}'.\n" if @config[:debug] @ob.delete(session) @sessions.delete(idhash) end #Clean database weak references from the tables-module. @db.clean #Clean the object-handler. @ob.clean_all #Call various user-connected methods. @events.call(:on_clean) if @events end
Connects a proc to a specific command in the command-line (key should be a regex).
# File lib/include/class_knjappserver_cmdline.rb, line 48 def cmd_connect(cmd, &block) @cmds[cmd] = [] if !@cmds.key?(cmd) @cmds[cmd] << {:block => block} end
# File lib/include/class_knjappserver_cmdline.rb, line 37 def cmdline_on_restart_cmd(data) print "Restart will begin shortly.\n" self.should_restart = true end
# File lib/include/class_knjappserver_cmdline.rb, line 42 def cmdline_on_stop_cmd(data) print "Stopping appserver.\n" self.stop end
Prints a string with a single file-line-backtrace prepended which is useful for debugging.
# File lib/include/class_knjappserver_errors.rb, line 129 def debugs(str) #Get backtrace. backtrace_str = caller[0] backtrace_match = backtrace_str.match(/^(.+):(\d+):in /) STDOUT.print "#{File.basename(backtrace_match[1])}:#{backtrace_match[2]}: #{str}\n" end
# File lib/include/class_knjappserver.rb, line 486 def define_magic_proc(method_name, &block) raise "No block given." if !block_given? @magic_procs[method_name] = block if !Object.respond_to?(method_name) Object.send(:define_method, method_name) do return Thread.current[:knjappserver][:kas].magic_procs[method_name].call(:kas => self) if Thread.current[:knjappserver] and Thread.current[:knjappserver][:kas] raise "Could not figure out the object: '#{method_name}'." end end end
Defines a variable as a method bound to the threads spawned by this instance of Knjappserver
.
# File lib/include/class_knjappserver.rb, line 475 def define_magic_var(method_name, var) @magic_vars[method_name] = var if !Object.respond_to?(method_name) Object.send(:define_method, method_name) do return Thread.current[:knjappserver][:kas].magic_vars[method_name] if Thread.current[:knjappserver] and Thread.current[:knjappserver][:kas] raise "Could not figure out the object: '#{method_name}'." end end end
Prints a detailed overview of the object in the terminal from where the appserver was started. This can be used for debugging.
# File lib/include/class_knjappserver_errors.rb, line 124 def dprint(obj) STDOUT.print Php4r.print_r(obj, true) end
Writes all queued access-logs to the database.
# File lib/include/class_knjappserver_logging.rb, line 12 def flush_access_log return nil if @logs_access_pending.empty? @logs_mutex.synchronize do ins_arr = @logs_access_pending @logs_access_pending = [] inserts = [] inserts_links = [] ins_arr.each do |ins| gothrough = [{ :col => :get_keys_data_id, :hash => ins[:get], :type => :keys },{ :col => :get_values_data_id, :hash => ins[:get], :type => :values },{ :col => :post_keys_data_id, :hash => ins[:post], :type => :keys },{ :col => :post_values_data_id, :hash => ins[:post], :type => :values },{ :col => :cookie_keys_data_id, :hash => ins[:cookie], :type => :keys },{ :col => :cookie_values_data_id, :hash => ins[:cookie], :type => :values },{ :col => :meta_keys_data_id, :hash => ins[:meta], :type => :keys },{ :col => :meta_values_data_id, :hash => ins[:meta], :type => :values }] ins_hash = { :session_id => ins[:session_id], :date_request => ins[:date_request] } gothrough.each do |data| if data[:type] == :keys hash = Knj::ArrayExt.hash_keys_hash(data[:hash]) else hash = Knj::ArrayExt.hash_values_hash(data[:hash]) end data_id = @ob.static(:Log_data, :by_id_hash, hash) if !data_id data_id = @db.insert(:Log_data, {"id_hash" => hash}, {:return_id => true}) link_count = 0 data[:hash].keys.sort.each do |key| if data[:type] == :keys ins_data = "#{key.to_s}" else ins_data = "#{data[:hash][key]}" end ins_data = ins_data.force_encoding("UTF-8") if ins_data.respond_to?(:force_encoding) data_value_id = @ob.static(:Log_data_value, :force_id, ins_data) inserts_links << {:no => link_count, :data_id => data_id, :value_id => data_value_id} link_count += 1 end end ins_hash[data[:col]] = data_id end hash = Knj::ArrayExt.array_hash(ins[:ips]) data_id = @ob.static(:Log_data, :by_id_hash, hash) if !data_id data_id = @db.insert(:Log_data, {"id_hash" => hash}, {:return_id => true}) link_count = 0 ins[:ips].each do |ip| data_value_id = @ob.static(:Log_data_value, :force_id, ip) inserts_links << {:no => link_count, :data_id => data_id, :value_id => data_value_id} link_count += 1 end end ins_hash[:ip_data_id] = data_id inserts << ins_hash end @db.insert_multi(:Log_access, inserts) @db.insert_multi(:Log_data_link, inserts_links) @ob.unset_class([:Log_access, :Log_data, :Log_data_link, :Log_data_value]) end end
Send error-emails based on error-emails-cache (cached so the same error isnt send out every time it occurrs to prevent spamming).
# File lib/include/class_knjappserver_errors.rb, line 20 def flush_error_emails @error_emails_pending_mutex.synchronize do send_time_older_than = Time.new.to_i - @error_emails_time @error_emails_pending.each do |backtrace_hash, error_email| if send_time_older_than < error_email[:last_time].to_i and error_email[:messages].length < 1000 next end @config[:error_report_emails].each do |email| next if !email or error_email[:messages].length <= 0 if error_email[:messages].length == 1 html = error_email[:messages].first else html = "<b>First time:</b> #{Datet.in(error_email[:first_time]).out}<br />" html << "<b>Last time:</b> #{Datet.in(error_email[:last_time]).out}<br />" html << "<b>Number of errors:</b> #{error_email[:messages].length}<br />" count = 0 error_email[:messages].each do |error_msg| count += 1 if count > 10 html << "<br /><br /><b><i>Limiting to showing 10 out of #{error_email[:messages].length} messages.</i></b>" break end html << "<br /><br />" html << "<b>Message #{count}</b><br />" html << error_msg end end self.mail( :to => email, :subject => error_email[:subject], :html => html, :from => @config[:error_report_from] ) end @error_emails_pending.delete(backtrace_hash) end end end
Hashes with numeric keys will be turned into arrays instead. This is not done automatically because it can wrongly corrupt data if not used correctly.
# File lib/include/class_knjappserver_web.rb, line 153 def get_parse_arrays(arg = nil, ob = nil) arg = _get.clone if !arg #Parses key-numeric-hashes into arrays and converts special model-strings into actual models. if arg.is_a?(Hash) and Knj::ArrayExt.hash_numeric_keys?(arg) arr = [] arg.each do |key, val| arr << val end return self.get_parse_arrays(arr, ob) elsif arg.is_a?(Hash) arg.each do |key, val| arg[key] = self.get_parse_arrays(val, ob) end return arg elsif arg.is_a?(Array) arg.each_index do |key| arg[key] = self.get_parse_arrays(arg[key], ob) end return arg elsif arg.is_a?(String) and match = arg.match(/^#<Model::(.+?)::(\d+)>$/) ob = @ob if !ob return ob.get(match[1], match[2]) else return arg end end
Handels a given error. Sends to the admin-emails.
# File lib/include/class_knjappserver_errors.rb, line 68 def handle_error(e, args = {}) @error_emails_pending_mutex.synchronize do if !Thread.current[:knjappserver] or !Thread.current[:knjappserver][:httpsession] STDOUT.print "#{Knj::Errors.error_str(e)}\n\n" end browser = _httpsession.browser if _httpsession send_email = true send_email = false if !@config[:smtp_args] send_email = false if !@config[:error_report_emails] send_email = false if args.has_key?(:email) and !args[:email] send_email = false if @config.key?(:error_report_bots) and !@config[:error_report_bots] and browser and browser["browser"] == "bot" if send_email backtrace_hash = Knj::ArrayExt.array_hash(e.backtrace) if !@error_emails_pending.has_key?(backtrace_hash) @error_emails_pending[backtrace_hash] = { :first_time => Time.new, :messages => [], :subject => sprintf("Error @ %s", @config[:title]) + " (#{Knj::Strings.shorten(e.message, 100)})" } end html = "An error occurred.<br /><br />" html << "<b>#{Knj::Web.html(e.class.name)}: #{Knj::Web.html(e.message)}</b><br /><br />" e.backtrace.each do |line| html << "#{Knj::Web.html(line)}<br />" end html << "<br /><b>Post:</b><br /><pre>#{Php4r.print_r(_post, true)}</pre>" if _post html << "<br /><b>Get:</b><br /><pre>#{Php4r.print_r(_get, true)}</pre>" if _get html << "<br /><b>Server:</b><br /><pre>#{Php4r.print_r(_server, true).html}</pre>" if _server html << "<br /><b>Cookie:</b><br /><pre>#{Php4r.print_r(_cookie, true).html}</pre>" if _meta html << "<br /><b>Session:</b><br /><pre>#{Php4r.print_r(_session, true).html}</pre>" if _session html << "<br /><b>Session hash:</b><br /><pre>#{Php4r.print_r(_session_hash, true).html}</pre>" if _session_hash error_hash = @error_emails_pending[backtrace_hash] error_hash[:last_time] = Time.new error_hash[:messages] << html end end end
Sends a header to the clients browser.
Examples¶ ↑
_kas.header("Content-Type", "text/javascript")
# File lib/include/class_knjappserver_web.rb, line 56 def header(key, val) raise "No HTTP-session attached to this thread." if !_httpsession raise "HTTP-session not active." if !_httpsession.resp _httpsession.resp.header(key, val) return nil end
Sends a raw header-line to the clients browser.
# File lib/include/class_knjappserver_web.rb, line 64 def header_raw(str) raise "No HTTP-session attached to this thread." if !_httpsession raise "HTTP-session not active." if !_httpsession.resp Php4r.header(str) return nil end
Define the size for when to automatically send headers. If you want to send hundres of kilobytes and then a header, you can use this method to do so.
Examples¶ ↑
Set the size to 200 kb.
_kas.headers_send_size = (1024 * 200)
# File lib/include/class_knjappserver_web.rb, line 83 def headers_send_size=(newsize) raise "The headers are already sent and you cannot modify the send-size any more." if self.headers_sent? _httpsession.size_send = newsize.to_i return nil end
Imports a .rhtml-file and executes it.
Examples¶ ↑
_kas.import("/some/path/page.rhtml")
# File lib/include/class_knjappserver_web.rb, line 5 def import(filepath) if filepath.to_s.index("../proc/self") != nil raise Errno::EACCES, "Possible attempt to hack the appserver." end _httpsession.eruby.import(filepath) end
# File lib/include/class_knjappserver_cleaner.rb, line 2 def initialize_cleaner #This should not be runned via _kas.timeout because timeout wont run when @should_restart is true! - knj Thread.new(&self.method(:clean_autorestart)) #This flushes (writes) all session-data to the server and deletes old unused sessions from the database. self.timeout(:time => @config[:cleaner_timeout], &self.method(:clean_sessions)) end
# File lib/include/class_knjappserver_cmdline.rb, line 2 def initialize_cmdline @cmds = {} Thread.new do begin $stdin.each_line do |line| called = 0 @cmds.each do |key, connects| data = {} if key.is_a?(Regexp) if line.match(key) connects.each do |conn| called += 1 conn[:block].call(data) end end else raise "Unknown class for 'cmd_connect': '#{key.class.name}'." end end if called == 0 print "Unknown command: '#{line.strip}'.\n" end end rescue => e self.handle_error(e) end end self.cmd_connect(/^\s*restart\s*$/i, &self.method(:cmdline_on_restart_cmd)) self.cmd_connect(/^\s*stop\s*$/i, &self.method(:cmdline_on_stop_cmd)) end
# File lib/include/class_knjappserver_errors.rb, line 4 def initialize_errors @error_emails_pending = {} @error_emails_pending_mutex = Mutex.new if @config[:error_emails_time] @error_emails_time = @config[:error_emails_time] elsif @config[:debug] @error_emails_time = 5 else @error_emails_time = 180 end self.timeout(:time => @error_emails_time, &self.method(:flush_error_emails)) end
# File lib/include/class_knjappserver_logging.rb, line 2 def initialize_logging @logs_access_pending = [] @logs_mutex = Mutex.new if @config[:logging] and @config[:logging][:access_db] self.timeout(:time => 30, &self.method(:flush_access_log)) end end
# File lib/include/class_knjappserver_mailing.rb, line 4 def initialize_mailing require "knj/autoload/ping" require "monitor" @mails_waiting = [] @mails_mutex = Monitor.new @mails_queue_mutex = Monitor.new @mails_timeout = self.timeout(:time => 30, &self.method(:mail_flush)) end
# File lib/include/class_knjappserver_sessions.rb, line 2 def initialize_sessions @sessions = Tsafe::MonHash.new end
# File lib/include/class_knjappserver_threadding.rb, line 2 def initialize_threadding @config[:threadding] = {} if !@config.has_key?(:threadding) @config[:threadding][:max_running] = 8 if !@config[:threadding].has_key?(:max_running) @threadpool = Knj::Threadpool.new(:threads => @config[:threadding][:max_running], :sleep => 0.1) @threadpool.events.connect(:on_error, &self.method(:threadpool_on_error)) end
Draw a input in a table.
# File lib/include/class_knjappserver_web.rb, line 131 def inputs(*args) return Knj::Web.inputs(args) end
Sleeps until the server is stopped.
# File lib/include/class_knjappserver.rb, line 452 def join raise "No http-server or http-server not running." if !@httpserv or !@httpserv.thread_accept begin @httpserv.thread_accept.join @httpserv.thread_restart.join if @httpserv and @httpserv.thread_restart rescue Interrupt => e self.stop end if @should_restart loop do if @should_restart_done STDOUT.print "Ending join because the restart is done.\n" break end sleep 1 end end end
If you want to use auto-restart, every file reloaded through loadfile will be watched for changes. When changed the server will do a restart to reflect that.
# File lib/include/class_knjappserver.rb, line 325 def loadfile(fpath) if !@config[:autorestart] require fpath return nil end rpath = File.realpath(fpath) raise "No such filepath: #{fpath}" if !rpath or !File.exists?(rpath) return true if @mod_files[rpath] @mod_event.args[:paths] << rpath @mod_files = rpath require rpath return false end
Writes a custom log to the database.
# File lib/include/class_knjappserver_logging.rb, line 215 def log(msg, objs, args = {}) #This can come in handy if migrating logs to appserver-database. if args[:date_saved] date_saved = args[:date_saved] else date_saved = Time.now end objs = [objs] if !objs.is_a?(Array) @logs_mutex.synchronize do log_value_id = @ob.static(:Log_data_value, :force_id, msg) ins_data = { :date_saved => date_saved, :text_value_id => log_value_id } get_hash = log_hash_ins(_get) if _get if get_hash ins_data[:get_keys_data_id] = get_hash[:keys_data_id] ins_data[:get_values_data_id] = get_hash[:values_data_id] end post_hash = log_hash_ins(_post) if _post if post_hash ins_data[:post_keys_data_id] = post_hash[:keys_data_id] ins_data[:post_values_data_id] = post_hash[:values_data_id] end cookie_hash = log_hash_ins(_cookie) if _cookie if cookie_hash ins_data[:post_keys_data_id] = cookie_hash[:keys_data_id] ins_data[:post_values_data_id] = cookie_hash[:values_data_id] end meta_hash = log_hash_ins(_meta) if _meta if cookie_hash ins_data[:meta_keys_data_id] = meta_hash[:keys_data_id] ins_data[:meta_values_data_id] = meta_hash[:values_data_id] end session_hash = log_hash_ins(_session) if _session if session_hash ins_data[:session_keys_data_id] = session_hash[:keys_data_id] ins_data[:session_values_data_id] = session_hash[:values_data_id] end if args[:tag] tag_value_id = @ob.static(:Log_data_value, :force_id, args[:tag]) ins_data[:tag_data_id] = tag_value_id end if args[:comment] comment_value_id = @ob.static(:Log_data_value, :force_id, args[:comment]) ins_data[:comment_data_id] = comment_value_id end log_id = @db.insert(:Log, ins_data, {:return_id => true}) log_links = [] objs.each do |obj| class_data_id = @ob.static(:Log_data_value, :force_id, obj.class.name) log_links << { :object_class_value_id => class_data_id, :object_id => obj.id, :log_id => log_id } end @db.insert_multi(:Log_link, log_links) end end
# File lib/include/class_knjappserver_logging.rb, line 176 def log_data_hash(keys_id, values_id) begin keys_data_obj = @ob.get(:Log_data, keys_id) values_data_obj = @ob.get(:Log_data, values_id) rescue Errno::ENOENT return {} end sql = " SELECT key_value.value AS `key`, value_value.value AS value FROM Log_data_link AS key_links, Log_data_link AS value_links, Log_data_value AS key_value, Log_data_value AS value_value WHERE key_links.data_id = '#{keys_id}' AND value_links.data_id = '#{values_id}' AND key_links.no = value_links.no AND key_value.id = key_links.value_id AND value_value.id = value_links.value_id ORDER BY key_links.no " hash = {} db.q(sql) do |d_hash| hash[d_hash[:key].to_sym] = d_hash[:value] end return hash end
Handles the hashes that should be logged.
# File lib/include/class_knjappserver_logging.rb, line 130 def log_hash_ins(hash_obj) #Sort out fileuploads - it would simply bee too big to log this. hash_obj = self.log_hash_safe(hash_obj) inserts_links = [] ret = {} [:keys, :values].each do |type| if type == :keys hash = Knj::ArrayExt.hash_keys_hash(hash_obj) else hash = Knj::ArrayExt.hash_values_hash(hash_obj) end data_id = @db.single(:Log_data, {"id_hash" => hash}) data_id = data_id[:id] if data_id if !data_id data_id = @db.insert(:Log_data, {"id_hash" => hash}, {:return_id => true}) link_count = 0 hash_obj.keys.sort.each do |key| if type == :keys ins_data = "#{key.to_s}" else ins_data = "#{hash_obj[key].to_s}" end ins_data = ins_data.force_encoding("UTF-8") if ins_data.respond_to?(:force_encoding) data_value_id = @ob.static(:Log_data_value, :force_id, ins_data) inserts_links << {:no => link_count, :data_id => data_id, :value_id => data_value_id} link_count += 1 end end if type == :keys ret[:keys_data_id] = data_id else ret[:values_data_id] = data_id end end @db.insert_multi(:Log_data_link, inserts_links) return ret end
Converts fileuploads into strings so logging wont be crazy big.
# File lib/include/class_knjappserver_logging.rb, line 114 def log_hash_safe(hash) hash_obj = {} hash.each do |key, val| if val.is_a?(Knjappserver::Httpsession::Post_multipart::File_upload) hash_obj[key] = "<Fileupload>" elsif val.is_a?(Hash) hash_obj[key] = self.log_hash_safe(val) else hash_obj[key] = val end end return hash_obj end
Deletes all logs for an object.
# File lib/include/class_knjappserver_logging.rb, line 291 def logs_delete(obj) @db.q_buffer do |db_buffer| buffer_hash = {:db_buffer => db_buffer} @ob.list(:Log_link, {"object_class" => obj.class.name, "object_id" => obj.id}) do |log_link| log = log_link.log @ob.delete(log_link, buffer_hash) @ob.delete(log, buffer_hash) if log and log.links("count" => true) <= 0 end end end
Removes all logs for objects that have been deleted.
Examples¶ ↑
Remember to pass Knj::Objects-object handler to the method.
appsrv.logs_delete_dead(:ob => ob, :debug => false)
# File lib/include/class_knjappserver_logging.rb, line 365 def logs_delete_dead(args) raise "No :ob-argument given." if !args[:ob] @db.q_buffer do |db_buffer| STDOUT.puts "Starting to look for dead log-links." if @debug or args[:debug] @ob.list(:Log_link, :cloned_ubuf => true) do |log_link| classname = log_link.object_class.to_s.split("::").last obj_exists = args[:ob].exists?(classname, log_link[:object_id]) next if obj_exists log = log_link.log STDOUT.puts "Deleting log-link #{log_link.id} for #{classname}(#{log_link[:object_id]})." if @debug or args[:debug] @ob.delete(log_link, :db_buffer => db_buffer) links_count = log.links("count" => true) if links_count <= 0 STDOUT.puts "Deleting log #{log.id} because it has no more links." if @debug or args[:debug] @ob.delete(log, :db_buffer => db_buffer) end end STDOUT.puts "Starting to look for logs with no links." if @debug or args[:debug] @ob.list(:Log, { [:Log_link, "id"] => {:type => :sqlval, :val => :null}, :cloned_ubuf => true }) do |log| STDOUT.puts "Deleting log #{log.id} because it has no links: '#{log.text}'." if @debug or args[:debug] @ob.delete(log, :db_buffer => db_buffer) end end end
Returns the HTML for a table with logs from a given object.
# File lib/include/class_knjappserver_logging.rb, line 304 def logs_table(obj, args = {}) links = @ob.list(:Log_link, {"object_class" => obj.class.name, "object_id" => obj.id, "limit" => 500, "orderby" => [["id", "desc"]]}) html = "" html << "<table class=\"list knjappserver_log_table\">" html << "<thead>" html << "<tr>" html << "<th>ID</th>" html << "<th>Message</th>" html << "<th style=\"width: 130px;\">Date & time</th>" html << "<th>Tag</th>" html << "<th>Objects</th>" if args[:ob_use] html << "<th>IP</th>" if args[:show_ip] html << "</tr>" html << "</thead>" html << "<tbody>" links.each do |link| log = link.log msg_lines = log.text.split("\n") first_line = msg_lines[0].to_s classes = ["knjappserver_log", "knjappserver_log_#{log.id}"] classes << "knjappserver_log_multiple_lines" if msg_lines.length > 1 html << "<tr class=\"#{classes.join(" ")}\">" html << "<td>#{log.id}</td>" html << "<td>#{first_line.html}</td>" html << "<td>#{log.date_saved_str}</td>" html << "<td>#{log.tag.html}</td>" if args[:ob_use] begin html << "<td>#{log.objects_html(args[:ob_use])}</td>" rescue => e html << "<td>#{e.message.html}</td>" end end html << "<td>#{log.ip}</td>" if args[:show_ip] html << "</tr>" end if links.empty? html << "<tr>" html << "<td colspan=\"2\" class=\"error\">No logs were found for that object.</td>" html << "</tr>" end html << "</tbody>" html << "</table>" return html end
Queue a mail for sending. Possible keys are: :subject, :from, :to, :text and :html.
# File lib/include/class_knjappserver_mailing.rb, line 15 def mail(mail_args) raise "'smtp_args' has not been given for the Knjappserver." if !@config[:smtp_args] @mails_queue_mutex.synchronize do count_wait = 0 while @mails_waiting.length > 100 if count_wait >= 30 raise "Could not send email - too many emails was pending and none of them were being sent?" end count_wait += 1 sleep 1 end mailobj = Knjappserver::Mail.new({:kas => self, :errors => {}, :status => :waiting}.merge(mail_args)) STDOUT.print "Added mail '#{mailobj.__id__}' to the mail-send-queue.\n" if debug @mails_waiting << mailobj self.mail_flush if mail_args[:now] return mailobj end end
Sends all queued mails to the respective servers, if we are online.
# File lib/include/class_knjappserver_mailing.rb, line 38 def mail_flush @mails_mutex.synchronize do STDOUT.print "Flushing mails.\n" if @debug if @mails_waiting.length <= 0 STDOUT.print "No mails to flush - skipping.\n" if @debug return false end STDOUT.print "Trying to ping Google to figure out if we are online...\n" if @debug status = Ping.pingecho("google.dk", 10, 80) if !status STDOUT.print "We are not online - skipping mail flush.\n" return false #Dont run if we dont have a connection to the internet and then properly dont have a connection to the SMTP as well. end begin #Use subprocessing to avoid the mail-framework (activesupport and so on, also possible memory leaks in those large frameworks). STDOUT.print "Starting subprocess for mailing.\n" if @debug require "knj/process_meta" subproc = Knj::Process_meta.new("debug" => @debug, "debug_err" => true, "id" => "knjappserver_mailing") subproc.static("Object", "require", "rubygems") subproc.static("Object", "require", "mail") subproc.static("Object", "require", "#{@config[:knjrbfw_path]}knjrbfw") subproc.static("Object", "require", "knj/autoload") STDOUT.print "Flushing emails." if @debug @mails_waiting.each do |mail| begin STDOUT.print "Sending email: #{mail.__id__}\n" if @debug if mail.send("proc" => subproc) STDOUT.print "Email sent: #{mail.__id__}\n" if @debug @mails_waiting.delete(mail) end rescue Timeout::Error #ignore - rescue => e @mails_waiting.delete(mail) self.handle_error(e, {:email => false}) end sleep 1 #sleep so we dont take up too much bandwidth. end ensure subproc.destroy if subproc subproc = nil end return nil end end
# File lib/include/class_knjappserver.rb, line 314 def no_date(event, classname) return "[no date]" end
Returns a number localized as a string.
# File lib/include/class_knjappserver_web.rb, line 148 def num(*args) return Knj::Locales.number_out(*args) end
Takes a proc and executes it. On error it alerts the error-message with javascript to the server, sends a javascript back and exits.
# File lib/include/class_knjappserver_errors.rb, line 115 def on_error_go_back(&block) begin block.call rescue => e self.alert(e.message).back end end
# File lib/include/class_knjappserver.rb, line 318 def on_event_filemod(event, path) print "File changed - restart server: #{path}\n" @should_restart = true @mod_event.destroy if @mod_event end
Stop running any more HTTP-requests - make them wait.
# File lib/include/class_knjappserver.rb, line 408 def pause @paused += 1 end
Returns true if paued - otherwise false.
# File lib/include/class_knjappserver.rb, line 418 def paused? return true if @paused > 0 return false end
Will stop handeling any more HTTP-requests, run the proc given and return handeling HTTP-requests.
# File lib/include/class_knjappserver.rb, line 424 def paused_exec raise "No block given." if !block_given? self.pause begin sleep 0.2 while @httpserv and @httpserv.working_count and @httpserv.working_count > 0 @paused_mutex.synchronize do Timeout.timeout(15) do yield end end ensure self.unpause end end
Returns the socket-port the appserver is currently running on.
# File lib/include/class_knjappserver_web.rb, line 186 def port raise "Http-server not spawned yet. Call Knjappserver#start to spawn it." if !@httpserv return @httpserv.server.addr[1] end
Redirects to another URL.
Examples¶ ↑
_kas.redirect("someotherpage.rhtml") _kas.redirect("newpage.rhtml", :perm => true)
# File lib/include/class_knjappserver_web.rb, line 17 def redirect(url, args = {}) #Header way if !_httpsession.alert_sent and !self.headers_sent? if args[:perm] _httpsession.resp.status = 301 if !self.headers_sent? else _httpsession.resp.status = 303 if !self.headers_sent? end self.header("Location", url) if !self.headers_sent? end print "<script type=\"text/javascript\">location.href=\"#{url}\";</script>" exit end
Serves the given filepath and enables caching for it. No other content should be written to the page when using this method.
Examples¶ ↑
_kas.header("Content-Type", "text/javascript") _kas.serve_file("somefile.js")
# File lib/include/class_knjappserver_web.rb, line 93 def serve_file(filepath) raise "File doesnt exist: '#{filepath}'." if !File.exists?(filepath) httpsess = _httpsession headers = httpsess.headers resp = httpsess.resp if headers["cache-control"] and headers["cache-control"][0] cache_control = {} headers["cache-control"][0].scan(/(.+)=(.+)/) do |match| cache_control[match[1]] = match[2] end end cache_dont = true if cache_control and cache_control.key?("max-age") and cache_control["max-age"].to_i <= 0 lastmod = File.mtime(filepath) self.header("Last-Modified", lastmod.httpdate) self.header("Expires", (Time.now + 86400).httpdate) #next day. if !cache_dont and headers["if-modified-since"] and headers["if-modified-since"][0] request_mod = Datet.in(headers["if-modified-since"].first).time if request_mod == lastmod resp.status = 304 return nil end end httpsess.force_content(:type => :file, :path => filepath) return nil end
Returns or adds session based on idhash and meta-data.
# File lib/include/class_knjappserver_sessions.rb, line 7 def session_fromid(ip, idhash, meta) ip = "bot" if idhash == "bot" if !@sessions.key?(idhash) session = @ob.get_by(:Session, {"idhash" => idhash}) if !session session = @ob.add(:Session, { :idhash => idhash, :user_agent => meta["HTTP_USER_AGENT"], :ip => ip }) end hash = {} @sessions[idhash] = { :dbobj => session, :hash => hash } else session = @sessions[idhash][:dbobj] hash = @sessions[idhash][:hash] end if ip != "bot" and !session.remember? and ip.to_s != session[:ip].to_s raise ArgumentError, "Invalid IP." end @sessions[idhash][:time_lastused] = Time.now return [session, hash] end
Generates a new session-ID by the meta data.
# File lib/include/class_knjappserver_sessions.rb, line 39 def session_generate_id(meta) return Digest::MD5.hexdigest("#{Time.now.to_f}_#{meta["HTTP_HOST"]}_#{meta["REMOTE_HOST"]}_#{meta["HTTP_X_FORWARDED_SERVER"]}_#{meta["HTTP_X_FORWARDED_FOR"]}_#{meta["HTTP_X_FORWARDED_HOST"]}_#{meta["REMOTE_ADDR"]}_#{meta["HTTP_USER_AGENT"]}") end
Will make the session rememberable for a year. IP wont be checked any more.
# File lib/include/class_knjappserver_sessions.rb, line 44 def session_remember session = _httpsession.session session[:remember] = 1 self.cookie( "name" => "KnjappserverSession", "value" => _httpsession.session_id, "path" => "/", "expires" => Time.now + 32140800 #add around 12 months ) end
Writes all session-data to the database (normally it is cached in memory and not updated on change).
# File lib/include/class_knjappserver_sessions.rb, line 57 def sessions_flush if @sessions @sessions.each do |session_hash, session_data| STDOUT.print "Flushing session: #{session_data[:dbobj].id}\n" if @debug session_data[:dbobj].flush end end end
Writes all session-data and resets the hash.
# File lib/include/class_knjappserver_sessions.rb, line 67 def sessions_reset self.sessions_flush @sessions = {} end
Starts the HTTP-server and threadpool.
# File lib/include/class_knjappserver.rb, line 344 def start #Start the appserver. print "Spawning appserver.\n" if @debug @httpserv = Knjappserver::Httpserver.new(self) #Start Leakproxy-module if defined in config. if @config[:leakproxy] require "#{File.dirname(__FILE__)}/class_knjappserver_leakproxy_server.rb" @leakproxy_server = Knjappserver::Leakproxy_server.new(:kas => self) end STDOUT.print "Starting appserver.\n" if @debug Thread.current[:knjappserver] = {:kas => self} if !Thread.current[:knjappserver] if @config[:autoload] STDOUT.print "Autoloading #{@config[:autoload]}\n" if @debug require @config[:autoload] end begin @threadpool.start if @threadpool STDOUT.print "Threadpool startet.\n" if @debug @httpserv.start STDOUT.print "Appserver startet.\n" if @debug rescue Interrupt => e STDOUT.print "Got interrupt - trying to stop appserver.\n" if @debug self.stop raise e end end
Stops the entire app and releases join.
# File lib/include/class_knjappserver.rb, line 378 def stop return nil if @stop_called @stop_called = true proc_stop = proc{ STDOUT.print "Stopping appserver.\n" if @debug @httpserv.stop if @httpserv and @httpserv.respond_to?(:stop) STDOUT.print "Stopping threadpool.\n" if @debug @threadpool.stop if @threadpool #This should be done first to be sure it finishes (else we have a serious bug). STDOUT.print "Flush out loaded sessions.\n" if @debug self.sessions_flush STDOUT.print "Stopping done...\n" if @debug } #If we cant get a paused-execution in 5 secs - we just force the stop. begin Timeout.timeout(5) do self.paused_exec(&proc_stop) end rescue Timeout::Error, SystemExit, Interrupt STDOUT.print "Forcing stop-appserver - couldnt get timing window.\n" if @debug proc_stop.call end end
Spawns a new thread with access to magic methods, _db-method and various other stuff in the appserver.
# File lib/include/class_knjappserver_threadding.rb, line 23 def thread(args = {}) raise "No block given." if !block_given? args[:args] = [] if !args[:args] thread_obj = Knjappserver::Thread_instance.new( :running => false, :error => false, :done => false ) @threadpool.run_async do @ob.db.get_and_register_thread if @ob.db.opts[:threadsafe] @db_handler.get_and_register_thread if @db_handler.opts[:threadsafe] Thread.current[:knjappserver] = { :kas => self, :db => @db_handler } begin thread_obj.args[:running] = true yield(*args[:args]) rescue => e self.handle_error(e) thread_obj.args[:error] = true thread_obj.args[:error_obj] = e ensure STDOUT.print "Free thread ob-db.\n" if @debug @ob.db.free_thread if @ob.db.opts[:threadsafe] STDOUT.print "Free thread db-handler.\n" if @debug @db_handler.free_thread if @db_handler.opts[:threadsafe] STDOUT.print "Set args on thread.\n" if @debug thread_obj.args[:running] = false thread_obj.args[:done] = true end end return thread_obj end
Inits the thread so it has access to the appserver and various magic methods can be used.
# File lib/include/class_knjappserver_threadding.rb, line 16 def thread_init(thread = nil) thread = Thread.current if thread == nil thread[:knjappserver] = {} if !thread[:knjappserver] thread[:knjappserver][:kas] = self end
Spawns a thread to run the given proc and add the output of that block in the correct order to the HTML.
# File lib/include/class_knjappserver_threadding.rb, line 75 def threadded_content(&block) _httpsession.threadded_content(block) return nil end
Callback for when an error occurs in the threadpool.
# File lib/include/class_knjappserver_threadding.rb, line 11 def threadpool_on_error(event, error) self.handle_error(error) end
Runs a proc every number of seconds.
# File lib/include/class_knjappserver_threadding.rb, line 66 def timeout(args = {}, &block) return Knjappserver::Threadding_timeout.new({ :kas => self, :block => block, :args => [] }.merge(args)).start end
Translates a given key for a given object.
Examples¶ ↑
print _kas.trans(obj, :title) #=> "Trala"
# File lib/include/class_knjappserver_translations.rb, line 5 def trans(obj, key, args = {}) args[:locale] = self.trans_locale if !args[:locale] trans_val = @translations.get(obj, key, args).to_s trans_val = @events.call(:trans_no_str, {:obj => obj, :key => key, :args => args}) if trans_val.length <= 0 return trans_val end
Returns the locale for the current thread.
# File lib/include/class_knjappserver_translations.rb, line 13 def trans_locale(args = {}) if args.is_a?(Hash) and args[:locale] return args[:locale] elsif _session and _session[:locale] return _session[:locale] elsif _httpsession and _httpsession.data[:locale] return _httpsession.data[:locale] elsif Thread.current[:locale] return Thread.current[:locale] elsif @config[:locale_default] return @config[:locale_default] end raise "Could not figure out locale." end
Unpause - start handeling HTTP-requests again.
# File lib/include/class_knjappserver.rb, line 413 def unpause @paused -= 1 end
Urldecodes a string.
# File lib/include/class_knjappserver_web.rb, line 143 def urldec(str) return Knj::Web.urldec(str) end
Returns true if a HTTP-request is working. Otherwise false.
# File lib/include/class_knjappserver.rb, line 441 def working? return true if @httpserv and @httpserv.working_count > 0 return false end