knjappserver 0.0.15 → 0.0.16

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