hayabusa 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (85) hide show
  1. data/.document +5 -0
  2. data/.rspec +1 -0
  3. data/Gemfile +20 -0
  4. data/Gemfile.lock +59 -0
  5. data/LICENSE.txt +20 -0
  6. data/README.rdoc +19 -0
  7. data/Rakefile +49 -0
  8. data/VERSION +1 -0
  9. data/bin/check_running.rb +69 -0
  10. data/bin/hayabusa_benchmark.rb +82 -0
  11. data/bin/hayabusa_cgi.rb +84 -0
  12. data/bin/hayabusa_fcgi.fcgi +159 -0
  13. data/bin/hayabusa_fcgi.rb +159 -0
  14. data/bin/knjappserver_start.rb +42 -0
  15. data/conf/apache2_cgi_rhtml_conf.conf +10 -0
  16. data/conf/apache2_fcgi_rhtml_conf.conf +22 -0
  17. data/hayabusa.gemspec +151 -0
  18. data/lib/hayabusa.rb +518 -0
  19. data/lib/hayabusa_cgi_session.rb +128 -0
  20. data/lib/hayabusa_cgi_tools.rb +102 -0
  21. data/lib/hayabusa_custom_io.rb +22 -0
  22. data/lib/hayabusa_database.rb +125 -0
  23. data/lib/hayabusa_erb_handler.rb +27 -0
  24. data/lib/hayabusa_ext/cleaner.rb +140 -0
  25. data/lib/hayabusa_ext/cmdline.rb +52 -0
  26. data/lib/hayabusa_ext/errors.rb +135 -0
  27. data/lib/hayabusa_ext/logging.rb +404 -0
  28. data/lib/hayabusa_ext/mailing.rb +158 -0
  29. data/lib/hayabusa_ext/sessions.rb +71 -0
  30. data/lib/hayabusa_ext/threadding.rb +96 -0
  31. data/lib/hayabusa_ext/threadding_timeout.rb +101 -0
  32. data/lib/hayabusa_ext/translations.rb +43 -0
  33. data/lib/hayabusa_ext/web.rb +190 -0
  34. data/lib/hayabusa_http_server.rb +102 -0
  35. data/lib/hayabusa_http_session.rb +361 -0
  36. data/lib/hayabusa_http_session_contentgroup.rb +176 -0
  37. data/lib/hayabusa_http_session_page_environment.rb +66 -0
  38. data/lib/hayabusa_http_session_post_multipart.rb +135 -0
  39. data/lib/hayabusa_http_session_request.rb +219 -0
  40. data/lib/hayabusa_http_session_response.rb +144 -0
  41. data/lib/hayabusa_models.rb +8 -0
  42. data/lib/kernel_ext/gettext_methods.rb +22 -0
  43. data/lib/kernel_ext/magic_methods.rb +61 -0
  44. data/lib/models/log.rb +130 -0
  45. data/lib/models/log_access.rb +88 -0
  46. data/lib/models/log_data.rb +27 -0
  47. data/lib/models/log_data_link.rb +3 -0
  48. data/lib/models/log_data_value.rb +21 -0
  49. data/lib/models/log_link.rb +65 -0
  50. data/lib/models/session.rb +35 -0
  51. data/pages/benchmark.rhtml +0 -0
  52. data/pages/benchmark_print.rhtml +14 -0
  53. data/pages/benchmark_simple.rhtml +3 -0
  54. data/pages/benchmark_threadded_content.rhtml +21 -0
  55. data/pages/debug_database_connections.rhtml +46 -0
  56. data/pages/debug_http_sessions.rhtml +40 -0
  57. data/pages/debug_memory_usage.rhtml +16 -0
  58. data/pages/error_notfound.rhtml +12 -0
  59. data/pages/logs_latest.rhtml +57 -0
  60. data/pages/logs_show.rhtml +32 -0
  61. data/pages/spec.rhtml +41 -0
  62. data/pages/spec_post.rhtml +3 -0
  63. data/pages/spec_test_multiple_clients.rhtml +3 -0
  64. data/pages/spec_thread_joins.rhtml +21 -0
  65. data/pages/spec_threadded_content.rhtml +40 -0
  66. data/pages/tests.rhtml +14 -0
  67. data/spec/cgi_spec.rb +47 -0
  68. data/spec/custom_urls_spec.rb +35 -0
  69. data/spec/fcgi_multiple_processes_spec.rb +32 -0
  70. data/spec/fcgi_spec.rb +69 -0
  71. data/spec/hayabusa_spec.rb +194 -0
  72. data/spec/spec_helper.rb +12 -0
  73. data/tests/cgi_test/config_cgi.rb +6 -0
  74. data/tests/cgi_test/threadded_content_test.rhtml +23 -0
  75. data/tests/cgi_test/vars_get_test.rhtml +4 -0
  76. data/tests/cgi_test/vars_header_test.rhtml +3 -0
  77. data/tests/cgi_test/vars_post_test.rhtml +4 -0
  78. data/tests/fcgi_test/config_fcgi.rb +6 -0
  79. data/tests/fcgi_test/index.rhtml +3 -0
  80. data/tests/fcgi_test/sleeper.rhtml +4 -0
  81. data/tests/fcgi_test/threadded_content_test.rhtml +23 -0
  82. data/tests/fcgi_test/vars_get_test.rhtml +4 -0
  83. data/tests/fcgi_test/vars_header_test.rhtml +3 -0
  84. data/tests/fcgi_test/vars_post_test.rhtml +4 -0
  85. metadata +257 -0
@@ -0,0 +1,158 @@
1
+ require "monitor"
2
+
3
+ class Hayabusa
4
+ attr_reader :mails_waiting
5
+
6
+ def initialize_mailing
7
+ require "knj/autoload/ping"
8
+ require "monitor"
9
+
10
+ @mails_waiting = []
11
+ @mails_mutex = Monitor.new
12
+ @mails_queue_mutex = Monitor.new
13
+ @mails_timeout = self.timeout(:time => 30, &self.method(:mail_flush))
14
+ end
15
+
16
+ #Queue a mail for sending. Possible keys are: :subject, :from, :to, :text and :html.
17
+ def mail(mail_args)
18
+ raise "'smtp_args' has not been given for the Hayabusa." if !@config[:smtp_args]
19
+
20
+ @mails_queue_mutex.synchronize do
21
+ count_wait = 0
22
+ while @mails_waiting.length > 100
23
+ if count_wait >= 30
24
+ raise "Could not send email - too many emails was pending and none of them were being sent?"
25
+ end
26
+
27
+ count_wait += 1
28
+ sleep 1
29
+ end
30
+
31
+ mailobj = Hayabusa::Mail.new({:hb => self, :errors => {}, :status => :waiting}.merge(mail_args))
32
+ STDOUT.print "Added mail '#{mailobj.__id__}' to the mail-send-queue.\n" if debug
33
+ @mails_waiting << mailobj
34
+ self.mail_flush if mail_args[:now]
35
+ return mailobj
36
+ end
37
+ end
38
+
39
+ #Sends all queued mails to the respective servers, if we are online.
40
+ def mail_flush
41
+ @mails_mutex.synchronize do
42
+ STDOUT.print "Flushing mails.\n" if @debug
43
+
44
+ if @mails_waiting.length <= 0
45
+ STDOUT.print "No mails to flush - skipping.\n" if @debug
46
+ return false
47
+ end
48
+
49
+ STDOUT.print "Trying to ping Google to figure out if we are online...\n" if @debug
50
+ status = Ping.pingecho("google.dk", 10, 80)
51
+ if !status
52
+ STDOUT.print "We are not online - skipping mail flush.\n"
53
+ 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.
54
+ end
55
+
56
+ begin
57
+ #Use subprocessing to avoid the mail-framework (activesupport and so on, also possible memory leaks in those large frameworks).
58
+ STDOUT.print "Starting subprocess for mailing.\n" if @debug
59
+ require "knj/process_meta"
60
+ subproc = Knj::Process_meta.new("debug" => @debug, "debug_err" => true, "id" => "hayabusa_mailing")
61
+ subproc.static("Object", "require", "rubygems")
62
+ subproc.static("Object", "require", "mail")
63
+ subproc.static("Object", "require", "#{@config[:knjrbfw_path]}knjrbfw")
64
+ subproc.static("Object", "require", "knj/autoload")
65
+
66
+ STDOUT.print "Flushing emails." if @debug
67
+ @mails_waiting.each do |mail|
68
+ begin
69
+ STDOUT.print "Sending email: #{mail.__id__}\n" if @debug
70
+ if mail.send("proc" => subproc)
71
+ STDOUT.print "Email sent: #{mail.__id__}\n" if @debug
72
+ @mails_waiting.delete(mail)
73
+ end
74
+ rescue Timeout::Error
75
+ #ignore -
76
+ rescue => e
77
+ @mails_waiting.delete(mail)
78
+ self.handle_error(e, {:email => false})
79
+ end
80
+
81
+ sleep 1 #sleep so we dont take up too much bandwidth.
82
+ end
83
+ ensure
84
+ subproc.destroy if subproc
85
+ subproc = nil
86
+ end
87
+
88
+ return nil
89
+ end
90
+ end
91
+
92
+ #This class represents the queued mails.
93
+ class Mail
94
+ def initialize(args)
95
+ @args = args
96
+
97
+ raise "No hayabusa-object was given (as :hb)." if !@args[:hb].is_a?(Hayabusa)
98
+ raise "No :to was given." if !@args[:to]
99
+ raise "No content was given (:html or :text)." if !@args[:html] and !@args[:text]
100
+ end
101
+
102
+ #Returns a key from the arguments.
103
+ def [](key)
104
+ return @args[key]
105
+ end
106
+
107
+ #Sends the email to the receiver.
108
+ def send(args = {})
109
+ STDOUT.print "Sending mail '#{__id__}'.\n" if @args[:hb].debug
110
+
111
+ if @args[:from]
112
+ from = @args[:from]
113
+ elsif @args[:hb].config[:error_report_from]
114
+ from = @args[:hb].config[:error_report_from]
115
+ else
116
+ raise "Dont know where to take the 'from'-paramter from - none given in appserver config or mail-method-arguments?"
117
+ end
118
+
119
+ if args["proc"]
120
+ args["proc"].static("Object", "require", "knj/mailobj")
121
+ mail = args["proc"].new("Knj::Mailobj", @args[:hb].config[:smtp_args])
122
+ mail._pm_send_noret("to=", @args[:to])
123
+ mail._pm_send_noret("subject=", @args[:subject]) if @args[:subject]
124
+ mail._pm_send_noret("html=", Knj::Strings.email_str_safe(@args[:html])) if @args[:html]
125
+ mail._pm_send_noret("text=", Knj::Strings.email_str_safe(@args[:text])) if @args[:text]
126
+ mail._pm_send_noret("from=", from)
127
+ mail._pm_send_noret("send")
128
+ else
129
+ require "knj/mailobj"
130
+ mail = Knj::Mailobj.new(@args[:hb].config[:smtp_args])
131
+ mail.to = @args[:to]
132
+ mail.subject = @args[:subject] if @args[:subject]
133
+ mail.html = Knj::Strings.email_str_safe(@args[:html]) if @args[:html]
134
+ mail.text = Knj::Strings.email_str_safe(@args[:text]) if @args[:text]
135
+ mail.from = from
136
+ mail.send
137
+ end
138
+
139
+ @args[:status] = :sent
140
+ STDOUT.print "Sent email #{self.__id__}\n" if @args[:hb].debug
141
+ return true
142
+ rescue => e
143
+ if @args[:hb].debug
144
+ STDOUT.print "Could not send email.\n"
145
+ STDOUT.puts e.inspect
146
+ STDOUT.puts e.backtrace
147
+ end
148
+
149
+ @args[:errors][e.class.name] = {:count => 0} if !@args[:errors].has_key?(e.class.name)
150
+ @args[:errors][e.class.name][:count] += 1
151
+ raise e if @args[:errors][e.class.name][:count] >= 5
152
+ @args[:status] = :error
153
+ @args[:error] = e
154
+
155
+ return false
156
+ end
157
+ end
158
+ end
@@ -0,0 +1,71 @@
1
+ class Hayabusa
2
+ def initialize_sessions
3
+ @sessions = Tsafe::MonHash.new
4
+ end
5
+
6
+ #Returns or adds session based on idhash and meta-data.
7
+ def session_fromid(ip, idhash, meta)
8
+ ip = "bot" if idhash == "bot"
9
+
10
+ if !@sessions.key?(idhash)
11
+ session = @ob.get_by(:Session, {"idhash" => idhash})
12
+ if !session
13
+ session = @ob.add(:Session, {
14
+ :idhash => idhash,
15
+ :user_agent => meta["HTTP_USER_AGENT"],
16
+ :ip => ip
17
+ })
18
+ end
19
+
20
+ hash = {}
21
+ @sessions[idhash] = {
22
+ :dbobj => session,
23
+ :hash => hash
24
+ }
25
+ else
26
+ session = @sessions[idhash][:dbobj]
27
+ hash = @sessions[idhash][:hash]
28
+ end
29
+
30
+ if ip != "bot" and !session.remember? and ip.to_s != session[:ip].to_s
31
+ raise ArgumentError, "Invalid IP."
32
+ end
33
+
34
+ @sessions[idhash][:time_lastused] = Time.now
35
+ return [session, hash]
36
+ end
37
+
38
+ #Generates a new session-ID by the meta data.
39
+ def session_generate_id(meta)
40
+ 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"]}")
41
+ end
42
+
43
+ #Will make the session rememberable for a year. IP wont be checked any more.
44
+ def session_remember
45
+ session = _httpsession.session
46
+ session[:remember] = 1
47
+
48
+ self.cookie(
49
+ "name" => "HayabusaSession",
50
+ "value" => _httpsession.session_id,
51
+ "path" => "/",
52
+ "expires" => Time.now + 32140800 #add around 12 months
53
+ )
54
+ end
55
+
56
+ #Writes all session-data to the database (normally it is cached in memory and not updated on change).
57
+ def sessions_flush
58
+ if @sessions
59
+ @sessions.each do |session_hash, session_data|
60
+ STDOUT.print "Flushing session: #{session_data[:dbobj].id}\n" if @debug
61
+ session_data[:dbobj].flush
62
+ end
63
+ end
64
+ end
65
+
66
+ #Writes all session-data and resets the hash.
67
+ def sessions_reset
68
+ self.sessions_flush
69
+ @sessions = {}
70
+ end
71
+ end
@@ -0,0 +1,96 @@
1
+ class Hayabusa
2
+ def initialize_threadding
3
+ @config[:threadding] = {} if !@config.has_key?(:threadding)
4
+ @config[:threadding][:max_running] = 8 if !@config[:threadding].has_key?(:max_running)
5
+
6
+ @threadpool = Knj::Threadpool.new(:threads => @config[:threadding][:max_running], :sleep => 0.1)
7
+ @threadpool.events.connect(:on_error, &self.method(:threadpool_on_error))
8
+ end
9
+
10
+ #Callback for when an error occurs in the threadpool.
11
+ def threadpool_on_error(event, error)
12
+ self.handle_error(error)
13
+ end
14
+
15
+ #Inits the thread so it has access to the appserver and various magic methods can be used.
16
+ def thread_init(thread = nil)
17
+ thread = Thread.current if thread == nil
18
+ thread[:hayabusa] = {} if !thread[:hayabusa]
19
+ thread[:hayabusa][:hb] = self
20
+ end
21
+
22
+ #Spawns a new thread with access to magic methods, _db-method and various other stuff in the appserver.
23
+ def thread(args = {})
24
+ raise "No block given." if !block_given?
25
+ args[:args] = [] if !args[:args]
26
+
27
+ thread_obj = Hayabusa::Thread_instance.new(
28
+ :running => false,
29
+ :error => false,
30
+ :done => false
31
+ )
32
+
33
+ @threadpool.run_async do
34
+ @ob.db.get_and_register_thread if @ob.db.opts[:threadsafe]
35
+ @db_handler.get_and_register_thread if @db_handler.opts[:threadsafe]
36
+
37
+ Thread.current[:hayabusa] = {
38
+ :hb => self,
39
+ :db => @db_handler
40
+ }
41
+
42
+ begin
43
+ thread_obj.args[:running] = true
44
+ yield(*args[:args])
45
+ rescue => e
46
+ self.handle_error(e)
47
+ thread_obj.args[:error] = true
48
+ thread_obj.args[:error_obj] = e
49
+ ensure
50
+ STDOUT.print "Free thread ob-db.\n" if @debug
51
+ @ob.db.free_thread if @ob.db.opts[:threadsafe]
52
+
53
+ STDOUT.print "Free thread db-handler.\n" if @debug
54
+ @db_handler.free_thread if @db_handler.opts[:threadsafe]
55
+
56
+ STDOUT.print "Set args on thread.\n" if @debug
57
+ thread_obj.args[:running] = false
58
+ thread_obj.args[:done] = true
59
+ end
60
+ end
61
+
62
+ return thread_obj
63
+ end
64
+
65
+ #Runs a proc every number of seconds.
66
+ def timeout(args = {}, &block)
67
+ return Hayabusa::Threadding_timeout.new({
68
+ :hb => self,
69
+ :block => block,
70
+ :args => []
71
+ }.merge(args)).start
72
+ end
73
+
74
+ #Spawns a thread to run the given proc and add the output of that block in the correct order to the HTML.
75
+ def threadded_content(&block)
76
+ _httpsession.threadded_content(block)
77
+ return nil
78
+ end
79
+ end
80
+
81
+ class Hayabusa::Thread_instance
82
+ attr_reader :args
83
+
84
+ def initialize(args)
85
+ @args = args
86
+ end
87
+
88
+ def join
89
+ sleep 0.1 while !@args[:done]
90
+ end
91
+
92
+ def join_error
93
+ self.join
94
+ raise @args[:error_obj] if @args[:error_obj]
95
+ end
96
+ end
@@ -0,0 +1,101 @@
1
+ class Hayabusa::Threadding_timeout
2
+ attr_reader :timeout
3
+
4
+ def initialize(args)
5
+ @args = args
6
+ @hb = @args[:hb]
7
+ raise "No time given." if !@args.key?(:time)
8
+ @mutex = Mutex.new
9
+ @running = false
10
+ end
11
+
12
+ def time=(newtime)
13
+ @args[:time] = newtime.to_s.to_i
14
+ end
15
+
16
+ def time
17
+ return @args[:time]
18
+ end
19
+
20
+ #Starts the timeout.
21
+ def start
22
+ @run = true
23
+ @thread = Thread.new do
24
+ loop do
25
+ begin
26
+ if @args[:counting]
27
+ @timeout = @args[:time]
28
+
29
+ while @timeout > 0
30
+ @timeout += -1
31
+ break if @hb.should_restart or !@run
32
+ sleep 1
33
+ end
34
+ else
35
+ sleep @args[:time]
36
+ end
37
+
38
+ break if @hb.should_restart or !@run
39
+
40
+ @mutex.synchronize do
41
+ @hb.threadpool.run do
42
+ @hb.ob.db.get_and_register_thread if @hb.ob.db.opts[:threadsafe]
43
+ @hb.db_handler.get_and_register_thread if @hb.db_handler.opts[:threadsafe]
44
+
45
+ Thread.current[:hayabusa] = {
46
+ :hb => @hb,
47
+ :db => @hb.db_handler
48
+ }
49
+
50
+ begin
51
+ @running = true
52
+
53
+ if @args.key?(:timeout)
54
+ Timeout.timeout(@args[:timeout]) do
55
+ @args[:block].call(*@args[:args])
56
+ end
57
+ else
58
+ @args[:block].call(*@args[:args])
59
+ end
60
+ ensure
61
+ @running = false
62
+ @hb.ob.db.free_thread if @hb.ob.db.opts[:threadsafe]
63
+ @hb.db_handler.free_thread if @hb.db_handler.opts[:threadsafe]
64
+ end
65
+ end
66
+ end
67
+ rescue => e
68
+ @hb.handle_error(e)
69
+ end
70
+ end
71
+ end
72
+
73
+ return self
74
+ end
75
+
76
+ #Stops the timeout.
77
+ def stop
78
+ @run = false
79
+ @mutex.synchronize do
80
+ @thread.kill if @thread.alive?
81
+ @thread = nil
82
+ end
83
+ end
84
+
85
+ #Returns various data.
86
+ def [](key)
87
+ return @timeout if key == :hayabusa_timeout
88
+ raise "No such key: '#{key}'."
89
+ end
90
+
91
+ #Returns true if the thread is alive or not.
92
+ def alive?
93
+ return @thread.alive? if @thread
94
+ return false
95
+ end
96
+
97
+ #Returns true if the timeout is running or not.
98
+ def running?
99
+ return @running
100
+ end
101
+ end
@@ -0,0 +1,43 @@
1
+ class Hayabusa
2
+ #Translates a given key for a given object.
3
+ #===Examples
4
+ # print _hb.trans(obj, :title) #=> "Trala"
5
+ def trans(obj, key, args = {})
6
+ args[:locale] = self.trans_locale if !args[:locale]
7
+ trans_val = @translations.get(obj, key, args).to_s
8
+ trans_val = @events.call(:trans_no_str, {:obj => obj, :key => key, :args => args}) if trans_val.length <= 0
9
+ return trans_val
10
+ end
11
+
12
+ #Returns the locale for the current thread.
13
+ def trans_locale(args = {})
14
+ if args.is_a?(Hash) and args[:locale]
15
+ return args[:locale]
16
+ elsif _session and _session[:locale]
17
+ return _session[:locale]
18
+ elsif _httpsession and _httpsession.data[:locale]
19
+ return _httpsession.data[:locale]
20
+ elsif Thread.current[:locale]
21
+ return Thread.current[:locale]
22
+ elsif @config[:locale_default]
23
+ return @config[:locale_default]
24
+ end
25
+
26
+ raise "Could not figure out locale."
27
+ end
28
+
29
+ #Sets new translations for the given object.
30
+ #===Examples
31
+ # _hb.trans_set(obj, {:title => "Trala"})
32
+ def trans_set(obj, values, args = {})
33
+ args[:locale] = self.trans_locale if !args[:locale]
34
+ @translations.set(obj, values, args)
35
+ end
36
+
37
+ #Deletes all translations for the given object.
38
+ #===Examples
39
+ # _hb.trans_del(obj)
40
+ def trans_del(obj)
41
+ @translations.delete(obj)
42
+ end
43
+ end