knjappserver 0.0.15 → 0.0.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. data/Gemfile +4 -2
  2. data/Gemfile.lock +24 -10
  3. data/README.rdoc +298 -1
  4. data/VERSION +1 -1
  5. data/bin/check_running.rb +2 -2
  6. data/knjappserver.gemspec +23 -5
  7. data/lib/files/database_schema.rb +124 -111
  8. data/lib/include/class_customio.rb +19 -5
  9. data/lib/include/class_erbhandler.rb +5 -22
  10. data/lib/include/class_httpresp.rb +66 -28
  11. data/lib/include/class_httpserver.rb +27 -14
  12. data/lib/include/class_httpsession.rb +161 -212
  13. data/lib/include/class_httpsession_contentgroup.rb +144 -0
  14. data/lib/include/class_httpsession_knjengine.rb +33 -68
  15. data/lib/include/class_httpsession_mongrel.rb +1 -1
  16. data/lib/include/class_httpsession_webrick.rb +1 -1
  17. data/lib/include/class_knjappserver.rb +105 -130
  18. data/lib/include/class_knjappserver_cleaner.rb +20 -13
  19. data/lib/include/class_knjappserver_cmdline.rb +44 -0
  20. data/lib/include/class_knjappserver_errors.rb +4 -1
  21. data/lib/include/class_knjappserver_logging.rb +48 -8
  22. data/lib/include/class_knjappserver_mailing.rb +36 -14
  23. data/lib/include/class_knjappserver_sessions.rb +78 -0
  24. data/lib/include/class_knjappserver_threadding.rb +18 -45
  25. data/lib/include/class_knjappserver_threadding_timeout.rb +78 -0
  26. data/lib/include/class_knjappserver_translations.rb +30 -0
  27. data/lib/include/class_knjappserver_web.rb +55 -3
  28. data/lib/include/class_log.rb +31 -3
  29. data/lib/include/class_log_access.rb +0 -15
  30. data/lib/include/class_log_data.rb +0 -15
  31. data/lib/include/class_log_data_link.rb +1 -14
  32. data/lib/include/class_log_data_value.rb +5 -17
  33. data/lib/include/class_session.rb +6 -18
  34. data/lib/include/magic_methods.rb +12 -14
  35. data/lib/pages/benchmark.rhtml +0 -0
  36. data/lib/pages/benchmark_print.rhtml +14 -0
  37. data/lib/pages/benchmark_simple.rhtml +3 -0
  38. data/lib/pages/benchmark_threadded_content.rhtml +21 -0
  39. data/lib/pages/spec.rhtml +26 -0
  40. data/lib/pages/spec_test_multiple_clients.rhtml +3 -0
  41. data/lib/pages/spec_threadded_content.rhtml +38 -0
  42. data/lib/scripts/benchmark.rb +65 -0
  43. data/spec/knjappserver_spec.rb +87 -43
  44. metadata +54 -20
@@ -13,7 +13,8 @@ class Knjappserver
13
13
 
14
14
  if @config.has_key?(:restart_when_used_memory) and !@should_restart
15
15
  mbs_used = (Knj::Php.memory_get_usage / 1024) / 1024
16
- #STDOUT.print "Used: #{mbs_used}\n"
16
+ STDOUT.print "Restart when over #{@config[:restart_when_used_memory]}mb\n" if @config[:debug]
17
+ STDOUT.print "Used: #{mbs_used}mb\n" if @config[:debug]
17
18
 
18
19
  if mbs_used.to_i >= @config[:restart_when_used_memory].to_i
19
20
  STDOUT.print "Memory is over #{@config[:restart_when_used_memory]} - restarting.\n"
@@ -25,7 +26,7 @@ class Knjappserver
25
26
  begin
26
27
  @should_restart_runnning = true
27
28
 
28
- #When we begin to restart it should go as fast as possible - so start by flushing out any emails waiting...
29
+ #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...
29
30
  STDOUT.print "Flushing mails.\n"
30
31
  self.mail_flush
31
32
 
@@ -89,19 +90,25 @@ class Knjappserver
89
90
  STDOUT.print "Cleaning sessions on appserver.\n" if @config[:debug]
90
91
 
91
92
  self.paused_exec do
93
+ session_not_ids = []
92
94
  time_check = Time.now.to_i - 300
93
- @sessions.each do |ip, ip_sessions|
94
- ip_sessions.each do |session_hash, session_data|
95
- if session_data[:time_lastused].to_i <= time_check
96
- session_data[:dbobj].flush
97
- @ob.unset(session_data[:dbobj])
98
- session_data[:hash].clear
99
- ip_sessions.delete(session_hash)
100
- session_data.clear
101
- end
102
- end
95
+ newsessions = {}
96
+ @sessions.each do |session_hash, session_data|
97
+ session_data[:dbobj].flush
103
98
 
104
- @sessions.delete(ip) if ip_sessions.empty?
99
+ if session_data[:time_lastused].to_i > time_check
100
+ newsessions[session_hash] = session_data
101
+ session_not_ids << session_data[:dbobj].id
102
+ end
103
+ end
104
+
105
+ @sessions = newsessions
106
+
107
+ STDOUT.print "Delete sessions...\n" if @config[:debug]
108
+ @ob.list(:Session, {"id_not" => session_not_ids, "date_lastused_below" => (Time.now - 5356800)}) do |session|
109
+ @ob.delete(session)
110
+ @sessions.delete(session[:idhash])
111
+ STDOUT.print "Deleted session: #{session.id}\n" if @config[:debug]
105
112
  end
106
113
  end
107
114
  end
@@ -0,0 +1,44 @@
1
+ class Knjappserver
2
+ def initialize_cmdline
3
+ @cmds = {}
4
+
5
+ Knj::Thread.new do
6
+ line = $stdin.gets
7
+
8
+ called = 0
9
+ @cmds.each do |key, connects|
10
+ data = {}
11
+
12
+ if key.is_a?(Regexp)
13
+ if line.match(key)
14
+ connects.each do |conn|
15
+ called += 1
16
+ conn[:block].call(data)
17
+ end
18
+ end
19
+ else
20
+ raise "Unknown class for 'cmd_connect': '#{key.class.name}'."
21
+ end
22
+ end
23
+
24
+ if called == 0
25
+ print "Unknown command: '#{line.strip}'.\n"
26
+ end
27
+ end
28
+
29
+ self.cmd_connect(/^\s*restart\s*$/i) do |data|
30
+ print "Restart will begin shortly.\n"
31
+ self.should_restart = true
32
+ end
33
+
34
+ self.cmd_connect(/^\s*stop\s*$/i) do |data|
35
+ self.stop
36
+ end
37
+ end
38
+
39
+ #Connects a proc to a specific command in the command-line (key should be a regex).
40
+ def cmd_connect(cmd, &block)
41
+ @cmds[cmd] = [] if !@cmds.key?(cmd)
42
+ @cmds[cmd] << {:block => block}
43
+ end
44
+ end
@@ -18,6 +18,7 @@ class Knjappserver
18
18
  end
19
19
  end
20
20
 
21
+ #Send error-emails based on error-emails-cache (cached so the same error isnt send out every time it occurrs to prevent spamming).
21
22
  def flush_error_emails
22
23
  @error_emails_pending_mutex.synchronize do
23
24
  send_time_older_than = Time.new.to_i - @error_emails_time
@@ -65,6 +66,7 @@ class Knjappserver
65
66
  end
66
67
  end
67
68
 
69
+ #Handels a given error. Sends to the admin-emails.
68
70
  def handle_error(e, args = {})
69
71
  @error_emails_pending_mutex.synchronize do
70
72
  if !Thread.current[:knjappserver] or !Thread.current[:knjappserver][:httpsession]
@@ -82,7 +84,7 @@ class Knjappserver
82
84
  @error_emails_pending[backtrace_hash] = {
83
85
  :first_time => Time.new,
84
86
  :messages => [],
85
- :subject => sprintf("Error @ %s", @config[:title]) + " (#{e.message})"
87
+ :subject => sprintf("Error @ %s", @config[:title]) + " (#{Knj::Strings.shorten(e.message, 100)})"
86
88
  }
87
89
  end
88
90
 
@@ -107,6 +109,7 @@ class Knjappserver
107
109
  end
108
110
  end
109
111
 
112
+ #Takes a proc and executes it. On error it alerts the error-message with javascript to the server, sends a javascript back and exits.
110
113
  def on_error_go_back(&block)
111
114
  begin
112
115
  block.call
@@ -12,6 +12,7 @@ class Knjappserver
12
12
  end
13
13
  end
14
14
 
15
+ #Writes all queued access-logs to the database.
15
16
  def flush_access_log
16
17
  @logs_mutex.synchronize do
17
18
  ins_arr = @logs_access_pending
@@ -155,8 +156,12 @@ class Knjappserver
155
156
  end
156
157
 
157
158
  def log_data_hash(keys_id, values_id)
158
- keys_data_obj = @ob.get(:Log_data, keys_id)
159
- values_data_obj = @ob.get(:Log_data, values_id)
159
+ begin
160
+ keys_data_obj = @ob.get(:Log_data, keys_id)
161
+ values_data_obj = @ob.get(:Log_data, values_id)
162
+ rescue Knj::Errors::NotFound
163
+ return {}
164
+ end
160
165
 
161
166
  sql = "
162
167
  SELECT
@@ -181,20 +186,20 @@ class Knjappserver
181
186
  "
182
187
 
183
188
  hash = {}
184
- q_hash = db.query(sql)
185
- while d_hash = q_hash.fetch
189
+ db.q(sql) do |d_hash|
186
190
  hash[d_hash[:key].to_sym] = d_hash[:value]
187
191
  end
188
192
 
189
193
  return hash
190
194
  end
191
195
 
192
- def log(msg, objs)
196
+ #Writes a custom log to the database.
197
+ def log(msg, objs, args = {})
193
198
  @logs_mutex.synchronize do
194
199
  objs = [objs] if !objs.is_a?(Array)
195
200
  log_value_id = @ob.static(:Log_data_value, :force_id, msg)
196
201
  ins_data = {
197
- :date_saved => Time.new,
202
+ :date_saved => Time.now,
198
203
  :text_value_id => log_value_id
199
204
  }
200
205
 
@@ -210,6 +215,28 @@ class Knjappserver
210
215
  ins_data[:post_values_data_id] = post_hash[:values_data_id]
211
216
  end
212
217
 
218
+ cookie_hash = log_hash_ins(_cookie) if _cookie
219
+ if cookie_hash
220
+ ins_data[:post_keys_data_id] = cookie_hash[:keys_data_id]
221
+ ins_data[:post_values_data_id] = cookie_hash[:values_data_id]
222
+ end
223
+
224
+ meta_hash = log_hash_ins(_meta) if _meta
225
+ if cookie_hash
226
+ ins_data[:meta_keys_data_id] = meta_hash[:keys_data_id]
227
+ ins_data[:meta_values_data_id] = meta_hash[:values_data_id]
228
+ end
229
+
230
+ if args[:tag]
231
+ tag_value_id = @ob.static(:Log_data_value, :force_id, args[:tag])
232
+ ins_data[:tag_data_id] = tag_value_id
233
+ end
234
+
235
+ if args[:comment]
236
+ comment_value_id = @ob.static(:Log_data_value, :force_id, args[:comment])
237
+ ins_data[:comment_data_id] = comment_value_id
238
+ end
239
+
213
240
  log_id = @db.insert(:Log, ins_data, {:return_id => true})
214
241
 
215
242
  log_links = []
@@ -227,6 +254,7 @@ class Knjappserver
227
254
  end
228
255
  end
229
256
 
257
+ #Returns the HTML for a table with logs from a given object.
230
258
  def logs_table(obj, args = {})
231
259
  links = @ob.list(:Log_link, {"object_class" => obj.class.name, "object_id" => obj.id, "limit" => 500, "orderby" => [["id", "desc"]]})
232
260
 
@@ -235,8 +263,10 @@ class Knjappserver
235
263
  html += "<tr>"
236
264
  html += "<th>ID</th>"
237
265
  html += "<th>Message</th>"
238
- html += "<th>Date &amp; time</th>"
266
+ html += "<th style=\"width: 130px;\">Date &amp; time</th>"
267
+ html += "<th>Tag</th>"
239
268
  html += "<th>Objects</th>" if args[:ob_use]
269
+ html += "<th>IP</th>" if args[:show_ip]
240
270
  html += "</tr>"
241
271
  html += "</thead>"
242
272
  html += "<tbody>"
@@ -254,7 +284,17 @@ class Knjappserver
254
284
  html += "<td>#{log.id}</td>"
255
285
  html += "<td>#{first_line.html}</td>"
256
286
  html += "<td>#{log.date_saved_str}</td>"
257
- html += "<td>#{log.objects_html(args[:ob_use])}</td>" if args[:ob_use]
287
+ html += "<td>#{log.tag.html}</td>"
288
+
289
+ if args[:ob_use]
290
+ begin
291
+ html += "<td>#{log.objects_html(args[:ob_use])}</td>"
292
+ rescue => e
293
+ html += "<td>#{e.message.html}</td>"
294
+ end
295
+ end
296
+
297
+ html += "<td>#{log.ip}</td>" if args[:show_ip]
258
298
  html += "</tr>"
259
299
  end
260
300
 
@@ -2,6 +2,9 @@ class Knjappserver
2
2
  attr_reader :mails_waiting
3
3
 
4
4
  def initialize_mailing
5
+ STDOUT.print "Loading mail.\n" if @config[:debug]
6
+ require "mail" if !@config.has_key?(:mail_require) or @config[:mail_require]
7
+
5
8
  @mails_waiting = []
6
9
  @mails_mutex = Mutex.new
7
10
  @mails_queue_mutex = Mutex.new
@@ -10,6 +13,7 @@ class Knjappserver
10
13
  end
11
14
  end
12
15
 
16
+ #Queue a mail for sending. Possible keys are: :subject, :from, :to, :text and :html.
13
17
  def mail(mail_args)
14
18
  @mails_queue_mutex.synchronize do
15
19
  count_wait = 0
@@ -23,11 +27,13 @@ class Knjappserver
23
27
  end
24
28
 
25
29
  mailobj = Knjappserver::Mail.new({:kas => self, :errors => {}, :status => :waiting}.merge(mail_args))
30
+ STDOUT.print "Added mail '#{mailobj.__id__}' to the mail-send-queue.\n" if debug
26
31
  @mails_waiting << mailobj
27
32
  return mailobj
28
33
  end
29
34
  end
30
35
 
36
+ #Sends all queued mails to the respective servers, if we are online.
31
37
  def mail_flush
32
38
  @mails_mutex.synchronize do
33
39
  return false if @mails_waiting.length <= 0
@@ -55,41 +61,57 @@ class Knjappserver
55
61
  end
56
62
  end
57
63
 
64
+ #This class represents the queued mails.
58
65
  class Mail
59
66
  def initialize(args)
60
67
  @args = args
61
68
 
62
69
  raise "No knjappserver-object was given (as :kas)." if !@args[:kas].is_a?(Knjappserver)
63
70
  raise "No :to was given." if !@args[:to]
64
- raise "No content was given (:html)." if !@args[:html]
71
+ raise "No content was given (:html or :text)." if !@args[:html] and !@args[:text]
65
72
  end
66
73
 
74
+ #Returns a key from the arguments.
67
75
  def [](key)
68
76
  return @args[key]
69
77
  end
70
78
 
79
+ #Sends the email to the receiver.
71
80
  def send
72
- mail = Knj::Mailobj.new(@args[:kas].config[:smtp_args])
73
- mail.to = @args[:to]
74
- mail.subject = @args[:subject] if @args[:subject]
75
- mail.html = @args[:html] if @args[:html]
76
-
77
- if @args[:from]
78
- mail.from = @args[:from]
79
- else
80
- mail.from = @args[:kas].config[:error_report_from]
81
- end
82
-
83
- begin
81
+ STDOUT.print "Sending mail '#{__id__}'.\n" if @args[:kas].debug
82
+
83
+ begin
84
+ mail = Knj::Mailobj.new(@args[:kas].config[:smtp_args])
85
+ mail.to = @args[:to]
86
+ mail.subject = @args[:subject] if @args[:subject]
87
+ mail.html = @args[:html] if @args[:html]
88
+ mail.text = @args[:text] if @args[:text]
89
+
90
+ if @args[:from]
91
+ mail.from = @args[:from]
92
+ elsif @args[:kas].config[:error_report_from]
93
+ mail.from = @args[:kas].config[:error_report_from]
94
+ else
95
+ raise "Dont know where to take the 'from'-paramter from - none given in appserver config or mail-method-arguments?"
96
+ end
97
+
84
98
  mail.send
85
99
  @args[:status] = :sent
100
+ STDOUT.print "Sent email #{self.__id__}\n" if @args[:kas].debug
86
101
  return true
87
- rescue SocketError => e
102
+ rescue Exception => e
103
+ if @args[:kas].debug
104
+ STDOUT.print "Could not send email.\n"
105
+ STDOUT.puts e.inspect
106
+ STDOUT.puts e.backtrace
107
+ end
108
+
88
109
  @args[:errors][e.class.name] = {:count => 0} if !@args[:errors].has_key?(e.class.name)
89
110
  @args[:errors][e.class.name][:count] += 1
90
111
  raise e if @args[:errors][e.class.name][:count] >= 5
91
112
  @args[:status] = :error
92
113
  @args[:error] = e
114
+
93
115
  return false
94
116
  end
95
117
  end
@@ -0,0 +1,78 @@
1
+ class Knjappserver
2
+ def initialize_sessions
3
+ @sessions = {}
4
+ end
5
+
6
+ #Returns or adds session based on idhash and meta-data.
7
+ def session_fromid(args)
8
+ ip = args[:ip].to_s
9
+ idhash = args[:idhash].to_s
10
+ ip = "bot" if idhash == "bot"
11
+
12
+ if !@sessions.key?(idhash)
13
+ session = @ob.get_by(:Session, {"idhash" => idhash})
14
+ if !session
15
+ session = @ob.add(:Session, {
16
+ :idhash => idhash,
17
+ :user_agent => args[:meta]["HTTP_USER_AGENT"],
18
+ :ip => ip
19
+ })
20
+ end
21
+
22
+ @sessions[idhash] = {
23
+ :dbobj => session,
24
+ :hash => {}
25
+ }
26
+ else
27
+ session = @sessions[idhash][:dbobj]
28
+ end
29
+
30
+ =begin
31
+ if ip != "bot"
32
+ if session[:user_agent] != args[:meta]["HTTP_USER_AGENT"]
33
+ STDOUT.print "Invalid user-agent!\n"
34
+ STDOUT.print Knj::Php.print_r(session, true)
35
+
36
+ raise Knj::Errors::InvalidData, "Invalid user-agent."
37
+ elsif !session.remember? and ip.to_s != session[:ip].to_s
38
+ STDOUT.print "Invalid IP!\n"
39
+ STDOUT.print Knj::Php.print_r(session, true)
40
+
41
+ raise Knj::Errors::InvalidData, "Invalid IP."
42
+ end
43
+ end
44
+ =end
45
+
46
+ @sessions[idhash][:time_lastused] = Time.now
47
+ return @sessions[idhash]
48
+ end
49
+
50
+ #Generates a new session-ID by the meta data.
51
+ def session_generate_id(args)
52
+ meta = args[:meta]
53
+ 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"]}")
54
+ end
55
+
56
+ #Will make the session rememberable for a year. IP wont be checked any more.
57
+ def session_remember
58
+ session = _httpsession.session
59
+ session[:remember] = 1
60
+
61
+ self.cookie(
62
+ "name" => "KnjappserverSession",
63
+ "value" => _httpsession.session_id,
64
+ "path" => "/",
65
+ "expires" => Time.now + 32140800 #add around 12 months
66
+ )
67
+ end
68
+
69
+ #Writes all session-data to the database (normally it is cached in memory and not updated on change).
70
+ def sessions_flush
71
+ if @sessions
72
+ @sessions.each do |session_hash, session_data|
73
+ STDOUT.print "Flushing session: #{session_data[:dbobj].id}\n" if @debug
74
+ session_data[:dbobj].flush
75
+ end
76
+ end
77
+ end
78
+ end
@@ -10,6 +10,13 @@ class Knjappserver
10
10
  end
11
11
  end
12
12
 
13
+ #Inits the thread so it has access to the appserver and various magic methods can be used.
14
+ def thread_init(thread)
15
+ thread[:knjappserver] = {} if !thread[:knjappserver]
16
+ thread[:knjappserver][:kas] = self
17
+ end
18
+
19
+ #Spawns a new thread with access to magic methods, _db-method and various other stuff in the appserver.
13
20
  def thread(args = {})
14
21
  raise "No block given." if !block_given?
15
22
  args[:args] = [] if !args[:args]
@@ -34,53 +41,19 @@ class Knjappserver
34
41
  end
35
42
  end
36
43
 
37
- def timeout(args = {})
38
- raise "No time given." if !args.has_key?(:time)
39
- raise "No block given." if !block_given?
40
- args[:args] = [] if !args[:args]
41
-
42
- thread = Thread.new do
43
- loop do
44
- begin
45
- if args[:counting]
46
- Thread.current[:knjappserver_timeout] = args[:time].to_s.to_i
47
-
48
- while Thread.current[:knjappserver_timeout] > 0
49
- Thread.current[:knjappserver_timeout] += -1
50
- break if @should_restart
51
- sleep 1
52
- end
53
- else
54
- sleep args[:time]
55
- end
56
-
57
- break if @should_restart
58
-
59
- @threadpool.run do
60
- @ob.db.get_and_register_thread if @ob.db.opts[:threadsafe]
61
- @db_handler.get_and_register_thread if @db_handler.opts[:threadsafe]
62
-
63
- Thread.current[:knjappserver] = {
64
- :kas => self,
65
- :db => @db_handler
66
- }
67
-
68
- begin
69
- yield(*args[:args])
70
- ensure
71
- @ob.db.free_thread if @ob.db.opts[:threadsafe]
72
- @db_handler.free_thread if @db_handler.opts[:threadsafe]
73
- end
74
- end
75
- rescue Exception => e
76
- handle_error(e)
77
- end
78
- end
79
- end
80
-
81
- return thread
44
+ #Runs a proc every number of seconds.
45
+ def timeout(args = {}, &block)
46
+ to = Knjappserver::Threadding_timeout.new(
47
+ :kas => self,
48
+ :block => block,
49
+ :args => args
50
+ )
51
+ to.start
52
+
53
+ return to
82
54
  end
83
55
 
56
+ #Spawns a thread to run the given proc and add the output of that block in the correct order to the HTML.
84
57
  def threadded_content(&block)
85
58
  _httpsession.threadded_content(block)
86
59
  end