hayabusa 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,6 +1,6 @@
1
1
  class Hayabusa::Cgi_session
2
2
  attr_accessor :data, :alert_sent
3
- attr_reader :cookie, :get, :headers, :session, :session_id, :session_hash, :hb, :active, :out, :eruby, :browser, :debug, :resp, :page_path, :post, :cgroup, :meta, :httpsession_var, :handler, :working
3
+ attr_reader :cookie, :get, :headers, :session, :session_id, :session_hash, :hb, :active, :out, :eruby, :browser, :debug, :resp, :page_path, :post, :cgroup, :meta, :httpsession_var, :working
4
4
 
5
5
  def initialize(args)
6
6
  @args = args
@@ -104,7 +104,7 @@ class Hayabusa::Cgi_session
104
104
 
105
105
  Timeout.timeout(@hb.config[:timeout]) do
106
106
  if @handlers_cache.key?(@ext)
107
- STDOUT.print "Calling handler.\n" if @debug
107
+ @hb.log_puts "Calling handler." if @debug
108
108
  @handlers_cache[@ext].call(self)
109
109
  else
110
110
  raise "CGI-mode shouldnt serve static files: '#{@page_path}'."
@@ -126,6 +126,29 @@ class Hayabusa::Cgi_session
126
126
  end
127
127
  end
128
128
 
129
+ def handler
130
+ return self
131
+ end
132
+
133
+ #Parses the if-modified-since header and returns it as a Time-object. Returns false is no if-modified-since-header is given or raises an RuntimeError if it cant be parsed.
134
+ def modified_since
135
+ return @modified_since if @modified_since
136
+ return false if !@meta["HTTP_IF_MODIFIED_SINCE"]
137
+
138
+ mod_match = @meta["HTTP_IF_MODIFIED_SINCE"].match(/^([A-z]+),\s+(\d+)\s+([A-z]+)\s+(\d+)\s+(\d+):(\d+):(\d+)\s+(.+)$/)
139
+ raise "Could not parse 'HTTP_IF_MODIFIED_SINCE'." if !mod_match
140
+
141
+ month_no = Datet.month_str_to_no(mod_match[3])
142
+ @modified_since = Time.utc(mod_match[4].to_i, month_no, mod_match[2].to_i, mod_match[5].to_i, mod_match[6].to_i, mod_match[7].to_i)
143
+
144
+ return @modified_since
145
+ end
146
+
147
+ #Forces the content to be the input - nothing else can be added after calling this.
148
+ def force_content(newcont)
149
+ @cgroup.force_content(newcont)
150
+ end
151
+
129
152
  #Creates a new Hayabusa::Binding-object and returns the binding for that object.
130
153
  def create_binding
131
154
  return Hayabusa::Http_session::Page_environment.new(:httpsession => self, :hb => @hb).get_binding
@@ -38,10 +38,7 @@ class Hayabusa::Cgi_tools
38
38
 
39
39
  #This method is used to proxy a request to another FCGI-process, since a single FCGI-process cant handle more requests simultanious.
40
40
  def proxy_request_to(args)
41
- cgi = args[:cgi]
42
- http = args[:http]
43
-
44
- File.open("/tmp/debug_#{Process.pid}_#{Time.new.to_f}.log", "w") do |fp_log|
41
+ cgi, http, fp_log = args[:cgi], args[:http], args[:fp_log]
45
42
 
46
43
  headers = {"Hayabusa_mode" => "proxy"}
47
44
  cgi.env_table.each do |key, val|
@@ -56,6 +53,7 @@ class Hayabusa::Cgi_tools
56
53
  #Make request.
57
54
  uri = Knj::Web.parse_uri(cgi.env_table["REQUEST_URI"])
58
55
  url = File.basename(uri[:path])
56
+ url = url[1, url.length] if url[0] == "/"
59
57
 
60
58
  if cgi.env_table["QUERY_STRING"].to_s.length > 0
61
59
  url << "?#{cgi.env_table["QUERY_STRING"]}"
@@ -64,39 +62,52 @@ class Hayabusa::Cgi_tools
64
62
  #cgi.print "Content-Type: text/html\r\n"
65
63
  #cgi.print "\r\n"
66
64
 
67
- if cgi.request_method == "POST" and cgi.content_type.to_s.downcase.index("multipart/form-data") != nil
68
- count = 0
69
- http.post_multipart(:url => url, :post => self.convert_fcgi_post(cgi.params),
70
- :default_headers => headers,
71
- :cookies => false,
72
- :on_content => proc{|line|
73
- cgi.print(line) if count > 0
74
- count += 1
75
- }
76
- )
77
- elsif cgi.request_method == "POST"
78
- count = 0
79
- http.post(:url => url, :post => self.convert_fcgi_post(cgi.params),
80
- :default_headers => headers,
81
- :cookies => false,
82
- :on_content => proc{|line|
83
- cgi.print(line) if count > 0
84
- count += 1
85
- }
86
- )
65
+ if args[:timeout]
66
+ ttime = args[:timeout]
87
67
  else
88
- count = 0
89
- http.get(:url => url,
90
- :default_headers => headers,
91
- :cookies => false,
92
- :on_content => proc{|line|
93
- fp_log.puts("Line: '#{line}'.")
94
- cgi.print(line) if count > 0
95
- count += 1
96
- }
97
- )
68
+ ttime = 30
98
69
  end
99
70
 
71
+ fp_log.puts("Proxying URL: '#{url}'.") if fp_log
72
+
73
+ require "timeout"
74
+ Timeout.timeout(ttime) do
75
+ if cgi.request_method == "POST" and cgi.content_type.to_s.downcase.index("multipart/form-data") != nil
76
+ count = 0
77
+ http.post_multipart(
78
+ :url => url,
79
+ :post => self.convert_fcgi_post(cgi.params),
80
+ :default_headers => headers,
81
+ :cookies => false,
82
+ :on_content => proc{|line|
83
+ cgi.print(line) if count > 0
84
+ count += 1
85
+ }
86
+ )
87
+ elsif cgi.request_method == "POST"
88
+ count = 0
89
+ http.post(
90
+ :url => url,
91
+ :post => self.convert_fcgi_post(cgi.params),
92
+ :default_headers => headers,
93
+ :cookies => false,
94
+ :on_content => proc{|line|
95
+ cgi.print(line) if count > 0
96
+ count += 1
97
+ }
98
+ )
99
+ else
100
+ count = 0
101
+ http.get(
102
+ :url => url,
103
+ :default_headers => headers,
104
+ :cookies => false,
105
+ :on_content => proc{|line|
106
+ cgi.print(line) if count > 0
107
+ count += 1
108
+ }
109
+ )
110
+ end
100
111
  end
101
112
  end
102
113
  end
@@ -3,8 +3,8 @@ class Hayabusa
3
3
  @config[:threadding] = {} if !@config.has_key?(:threadding)
4
4
  @config[:threadding][:max_running] = 8 if !@config[:threadding].has_key?(:max_running)
5
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))
6
+ @threadpool = Tpool.new(:threads => @config[:threadding][:max_running])
7
+ @threadpool.on_error(&self.method(:threadpool_on_error))
8
8
  end
9
9
 
10
10
  #Callback for when an error occurs in the threadpool.
@@ -4,7 +4,7 @@ class Hayabusa
4
4
  # print _hb.trans(obj, :title) #=> "Trala"
5
5
  def trans(obj, key, args = {})
6
6
  args[:locale] = self.trans_locale if !args[:locale]
7
- trans_val = @translations.get(obj, key, args).to_s
7
+ trans_val = self.translations.get(obj, key, args).to_s
8
8
  trans_val = @events.call(:trans_no_str, {:obj => obj, :key => key, :args => args}) if trans_val.length <= 0
9
9
  return trans_val
10
10
  end
@@ -30,14 +30,16 @@ class Hayabusa
30
30
  #===Examples
31
31
  # _hb.trans_set(obj, {:title => "Trala"})
32
32
  def trans_set(obj, values, args = {})
33
+ raise "Translations-object now spawned." if !self.translations
33
34
  args[:locale] = self.trans_locale if !args[:locale]
34
- @translations.set(obj, values, args)
35
+ self.translations.set(obj, values, args)
35
36
  end
36
37
 
37
38
  #Deletes all translations for the given object.
38
39
  #===Examples
39
40
  # _hb.trans_del(obj)
40
41
  def trans_del(obj)
41
- @translations.delete(obj)
42
+ raise "Translations-object now spawned." if !self.translations
43
+ self.translations.delete(obj)
42
44
  end
43
45
  end
@@ -0,0 +1,172 @@
1
+ class Hayabusa::Fcgi
2
+ def initialize
3
+ #Spawn CGI-variable to emulate FCGI part.
4
+ @cgi_tools = Hayabusa::Cgi_tools.new
5
+
6
+ #We cant define the Hayabusa-server untuil we receive the first headers, so wait for the first request.
7
+ @hayabusa = nil
8
+ @hayabusa_fcgi_conf_path = nil
9
+ @fcgi_proxy = nil
10
+ @debug = false
11
+ end
12
+
13
+ def evaluate_mode
14
+ #If this is a FCGI-proxy-instance then the HTTP-connection should be checked if it is working.
15
+ if @fcgi_proxy
16
+ if !@fcgi_proxy[:http].socket_working?
17
+ @fcgi_proxy = nil
18
+ end
19
+ end
20
+
21
+ #Skip the actual check if Hayabusa is spawned or this is a working FCGI-proxy-instance.
22
+ return nil if @hayabusa or @fcgi_proxy
23
+
24
+ #Parse the configuration-header and generate Hayabusa-config-hash.
25
+ raise "No HTTP_HAYABUSA_FCGI_CONFIG-header was given." if !@cgi.env_table["HTTP_HAYABUSA_FCGI_CONFIG"]
26
+ @hayabusa_fcgi_conf_path = @cgi.env_table["HTTP_HAYABUSA_FCGI_CONFIG"]
27
+ require @hayabusa_fcgi_conf_path
28
+ raise "No 'Hayabusa::FCGI_CONF'-constant was spawned by '#{@cgi.env_table["HTTP_HAYABUSA_FCGI_CONFIG"]}'." if !Hayabusa.const_defined?(:FCGI_CONF)
29
+ conf = Hayabusa::FCGI_CONF
30
+
31
+ hayabusa_conf = Hayabusa::FCGI_CONF[:hayabusa]
32
+ hayabusa_conf.merge!(
33
+ :cmdline => false,
34
+ :port => 0 #Ruby picks random port and we get the actual port after starting the appserver.
35
+ )
36
+
37
+ #Figure out if this should be a host-FCGI-process or a proxy-FCGI-process.
38
+ fcgi_config_fp = "#{Knj::Os.tmpdir}/hayabusa_fcgi_#{hayabusa_conf[:title]}_fcgi.conf"
39
+ FileUtils.touch(fcgi_config_fp) if !File.exists?(fcgi_config_fp)
40
+
41
+ File.open(fcgi_config_fp) do |fp|
42
+ fp.flock(File::LOCK_EX)
43
+
44
+ fcgi_config_cont = File.read(fcgi_config_fp)
45
+ if !fcgi_config_cont.empty?
46
+ #Seems like an instance is already running - check PID to be sure.
47
+ fcgi_config = Marshal.load(File.read(fcgi_config_fp))
48
+ pid = fcgi_config[:pid]
49
+
50
+ if Knj::Unix_proc.pid_running?(pid)
51
+ #Set this instance to run in proxy-mode.
52
+ begin
53
+ @fcgi_proxy = fcgi_config
54
+ require "http2"
55
+ @fcgi_proxy[:http] = Http2.new(:host => "localhost", :port => @fcgi_proxy[:port].to_i)
56
+
57
+ if hayabusa_conf[:debug]
58
+ @fcgi_proxy[:fp_log] = File.open("/tmp/hayabusa_#{hayabusa_conf[:hayabusa][:title]}_#{Process.pid}.log", "w")
59
+ @fcgi_proxy[:fp_log].sync = true
60
+ end
61
+ rescue
62
+ @fcgi_proxy = nil
63
+ raise
64
+ end
65
+ end
66
+ end
67
+
68
+ #No instance is already running - start new Hayabusa-instance in both CGI- and socket-mode and write that to the config-file so other instances will register this as the main host-instance.
69
+ if !@fcgi_proxy
70
+ File.open(fcgi_config_fp, "w") do |fp|
71
+ @hayabusa = Hayabusa.new(hayabusa_conf)
72
+
73
+ #Start web-server for proxy-requests.
74
+ @hayabusa.start
75
+
76
+ fp.write(Marshal.dump(
77
+ :pid => Process.pid,
78
+ :port => @hayabusa.port
79
+ ))
80
+ end
81
+ end
82
+ end
83
+ end
84
+
85
+ def fcgi_loop
86
+ $stderr.puts "[hayabusa] Starting FCGI." if @debug
87
+ FCGI.each_cgi do |cgi|
88
+ begin
89
+ #cgi.print "Content-Type: text/html\r\n"
90
+ #cgi.print "\r\n"
91
+
92
+ #Set 'cgi'-variable for CGI-tools.
93
+ @cgi_tools.cgi = cgi
94
+ @cgi = cgi
95
+
96
+ #Evaluate the mode of this instance.
97
+ self.evaluate_mode
98
+
99
+ #Ensure the same FCGI-process isnt active for more than one website.
100
+ raise "Expected 'HTTP_HAYABUSA_FCGI_CONFIG' to be '#{@hayabusa_fcgi_conf_path}' but it wasnt: '#{cgi.env_table["HTTP_HAYABUSA_FCGI_CONFIG"]}'." if @hayabusa_fcgi_conf_path and @hayabusa_fcgi_conf_path != cgi.env_table["HTTP_HAYABUSA_FCGI_CONFIG"]
101
+
102
+ if @fcgi_proxy
103
+ #Proxy request to the host-FCGI-process.
104
+ $stderr.puts "[hayabusa] Proxying request." if @debug
105
+ begin
106
+ @cgi_tools.proxy_request_to(:cgi => cgi, :http => @fcgi_proxy[:http], :fp_log => @fcgi_proxy[:fp_log])
107
+ rescue Errno::ECONNABORTED
108
+ @fcgi_proxy = nil #Force re-evaluate if this process should be host or proxy.
109
+ raise
110
+ end
111
+ else
112
+ self.handle_fcgi_request(:cgi => cgi)
113
+ end
114
+ rescue Exception => e
115
+ cgi.print "Content-Type: text/html\r\n"
116
+ cgi.print "\r\n"
117
+ cgi.print Knj::Errors.error_str(e, :html => true)
118
+
119
+ if @hayabusa
120
+ @hayabusa.log_puts e.inspect
121
+ @hayabusa.log_puts e.backtrace
122
+ else
123
+ STDERR.puts e.inspect
124
+ STDERR.puts e.backtrace
125
+ end
126
+ ensure
127
+ @cgi = nil
128
+ @cgi_tools.cgi = nil
129
+ end
130
+ end
131
+ end
132
+
133
+ def handle_fcgi_request(args)
134
+ #Host the FCGI-process.
135
+ $stderr.puts "[hayabusa] Running request as CGI." if @debug
136
+
137
+ #Enforce $stdout variable.
138
+ $stdout = @hayabusa.cio
139
+
140
+ #The rest is copied from the FCGI-part.
141
+ headers = {}
142
+ @cgi.env_table.each do |key, val|
143
+ if key[0, 5] == "HTTP_" and key != "HTTP_HAYABUSA_FCGI_CONFIG"
144
+ key = key[5, key.length].gsub("_", " ").gsub(" ", "-")
145
+ headers[key] = val
146
+ end
147
+ end
148
+
149
+ meta = @cgi.env_table.to_hash
150
+
151
+ uri = Knj::Web.parse_uri(meta["REQUEST_URI"])
152
+ meta["PATH_TRANSLATED"] = File.basename(uri[:path])
153
+
154
+ cgi_data = {
155
+ :cgi => @cgi,
156
+ :headers => headers,
157
+ :get => Knj::Web.parse_urlquery(@cgi.env_table["QUERY_STRING"], :urldecode => true, :force_utf8 => true),
158
+ :meta => meta
159
+ }
160
+ if @cgi.request_method == "POST"
161
+ cgi_data[:post] = @cgi_tools.convert_fcgi_post(@cgi.params)
162
+ else
163
+ cgi_data[:post] = {}
164
+ end
165
+
166
+ @hayabusa.config[:cgi] = cgi_data
167
+
168
+
169
+ #Handle request.
170
+ @hayabusa.start_cgi_request
171
+ end
172
+ end
@@ -21,12 +21,12 @@ class Hayabusa::Http_server
21
21
 
22
22
  @thread_accept = Thread.new do
23
23
  loop do
24
- if !@server or @server.closed?
25
- STDOUT.puts "Starting TCPServer." if @debug
26
- @server = TCPServer.new(@hb.config[:host], @hb.config[:port])
27
- end
28
-
29
24
  begin
25
+ if !@server or @server.closed?
26
+ STDOUT.puts "Starting TCPServer." if @debug
27
+ @server = TCPServer.new(@hb.config[:host], @hb.config[:port])
28
+ end
29
+
30
30
  STDOUT.puts "Trying to spawn new HTTP-session from socket-accept." if @debug
31
31
  self.spawn_httpsession(@server.accept)
32
32
  STDOUT.puts "Starting new HTTP-request." if @debug
@@ -55,7 +55,7 @@ class Hayabusa::Http_session
55
55
 
56
56
  Dir.chdir(@config[:doc_root])
57
57
  ObjectSpace.define_finalizer(self, self.class.method(:finalize).to_proc) if @debug
58
- STDOUT.print "New httpsession #{self.__id__} (total: #{@httpserver.http_sessions.count}).\n" if @debug
58
+ @hb.log_puts "New httpsession #{self.__id__} (total: #{@httpserver.http_sessions.count})." if @debug
59
59
 
60
60
  @thread_request = Thread.new(&self.method(:thread_request_run))
61
61
  end
@@ -80,15 +80,15 @@ class Hayabusa::Http_session
80
80
  @working = false
81
81
  break if @hb.should_restart
82
82
 
83
- STDOUT.print "#{__id__} - Waiting to parse from socket.\n" if @debug
83
+ @hb.log_puts "#{__id__} - Waiting to parse from socket." if @debug
84
84
  Timeout.timeout(1800) do
85
85
  @handler.socket_parse(@socket)
86
86
  end
87
87
 
88
- STDOUT.print "#{__id__} - Done parsing from socket.\n" if @debug
88
+ @hb.log_puts "#{__id__} - Done parsing from socket." if @debug
89
89
 
90
90
  while @hb.paused? #Check if we should be waiting with executing the pending request.
91
- STDOUT.print "#{__id__} - Paused! (#{@hb.paused}) - sleeping.\n" if @debug
91
+ @hb.log_puts "#{__id__} - Paused! (#{@hb.paused}) - sleeping." if @debug
92
92
  sleep 0.1
93
93
  end
94
94
 
@@ -96,7 +96,7 @@ class Hayabusa::Http_session
96
96
 
97
97
  if max_requests_working and @httpserver
98
98
  while @httpserver.working_count.to_i >= max_requests_working
99
- STDOUT.print "#{__id__} - Maximum amounts of requests are working (#{@httpserver.working_count}, #{max_requests_working}) - sleeping.\n" if @debug
99
+ @hb.log_puts "#{__id__} - Maximum amounts of requests are working (#{@httpserver.working_count}, #{max_requests_working}) - sleeping." if @debug
100
100
  sleep 0.1
101
101
  end
102
102
  end
@@ -106,13 +106,13 @@ class Hayabusa::Http_session
106
106
  @hb.ob.db.get_and_register_thread if @hb.ob.db.opts[:threadsafe]
107
107
 
108
108
  @working = true
109
- STDOUT.print "#{__id__} - Serving.\n" if @debug
109
+ @hb.log_puts "#{__id__} - Serving." if @debug
110
110
 
111
111
  @httpserver.count_block do
112
112
  self.serve
113
113
  end
114
114
  ensure
115
- STDOUT.print "#{__id__} - Closing request.\n" if @debug
115
+ @hb.log_puts "#{__id__} - Closing request." if @debug
116
116
  @working = false
117
117
 
118
118
  #Free reserved database-connections.
@@ -121,13 +121,14 @@ class Hayabusa::Http_session
121
121
  end
122
122
  end
123
123
  rescue Timeout::Error
124
- STDOUT.print "#{__id__} - Closing httpsession because of timeout.\n" if @debug
124
+ @hb.log_puts "#{__id__} - Closing httpsession because of timeout." if @debug
125
125
  rescue Errno::ECONNRESET, Errno::ENOTCONN, Errno::EPIPE => e
126
- STDOUT.print "#{__id__} - Connection error (#{e.inspect})...\n" if @debug
126
+ @hb.log_puts "#{__id__} - Connection error (#{e.inspect})..." if @debug
127
+ @hb.log_puts e.backtrace
127
128
  rescue Interrupt => e
128
129
  raise e
129
130
  rescue Exception => e
130
- STDOUT.puts Knj::Errors.error_str(e)
131
+ @hb.log_puts Knj::Errors.error_str(e)
131
132
  ensure
132
133
  self.destruct
133
134
  end
@@ -181,11 +182,11 @@ class Hayabusa::Http_session
181
182
  end
182
183
 
183
184
  def self.finalize(id)
184
- STDOUT.print "Http_session finalize #{id}.\n" if @debug
185
+ @hb.log_puts "Http_session finalize #{id}." if @debug
185
186
  end
186
187
 
187
188
  def destruct
188
- STDOUT.print "Http_session destruct (#{@httpserver.http_sessions.length})\n" if @debug and @httpserver and @httpserver.http_sessions
189
+ @hb.log_puts "Http_session destruct (#{@httpserver.http_sessions.length})" if @debug and @httpserver and @httpserver.http_sessions
189
190
 
190
191
  begin
191
192
  @socket.close if !@socket.closed?
@@ -207,7 +208,7 @@ class Hayabusa::Http_session
207
208
  end
208
209
 
209
210
  def serve
210
- STDOUT.print "Generating meta, cookie, get, post and headers.\n" if @debug
211
+ @hb.log_puts "Generating meta, cookie, get, post and headers." if @debug
211
212
  @meta = @handler.meta.merge(@socket_meta)
212
213
  @cookie = @handler.cookie
213
214
  @get = @handler.get
@@ -244,7 +245,7 @@ class Hayabusa::Http_session
244
245
  raise "Could not figure out the IP of the session."
245
246
  end
246
247
 
247
- STDOUT.print "Figuring out session-ID, session-object and more.\n" if @debug
248
+ @hb.log_puts "Figuring out session-ID, session-object and more." if @debug
248
249
  if @cookie["HayabusaSession"].to_s.length > 0
249
250
  @session_id = @cookie["HayabusaSession"]
250
251
  elsif @browser["browser"] == "bot"
@@ -273,7 +274,7 @@ class Hayabusa::Http_session
273
274
  end
274
275
 
275
276
  if @config.key?(:logging) and @config[:logging][:access_db]
276
- STDOUT.print "Doing access-logging.\n" if @debug
277
+ @hb.log_puts "Doing access-logging." if @debug
277
278
  @ips = [@meta["REMOTE_ADDR"]]
278
279
  @ips << @meta["HTTP_X_FORWARDED_FOR"].split(",")[0].strip if @meta["HTTP_X_FORWARDED_FOR"]
279
280
  @hb.logs_access_pending << {
@@ -287,7 +288,7 @@ class Hayabusa::Http_session
287
288
  }
288
289
  end
289
290
 
290
- STDOUT.print "Initializing thread and content-group.\n" if @debug
291
+ @hb.log_puts "Initializing thread and content-group." if @debug
291
292
  self.init_thread
292
293
  Thread.current[:hayabusa][:contentgroup] = @cgroup
293
294
  time_start = Time.now.to_f if @debug
@@ -297,7 +298,7 @@ class Hayabusa::Http_session
297
298
 
298
299
  Timeout.timeout(@hb.config[:timeout]) do
299
300
  if @handlers_cache.key?(@ext)
300
- STDOUT.print "Calling handler.\n" if @debug
301
+ @hb.log_puts "Calling handler." if @debug
301
302
  @handlers_cache[@ext].call(self)
302
303
  else
303
304
  #check if we should use a handler for this request.
@@ -350,7 +351,7 @@ class Hayabusa::Http_session
350
351
 
351
352
  @cgroup.mark_done
352
353
  @cgroup.write_output
353
- STDOUT.print "#{__id__} - Served '#{@meta["REQUEST_URI"]}' in #{Time.now.to_f - time_start} secs (#{@resp.status}).\n" if @debug
354
+ @hb.log_puts "#{__id__} - Served '#{@meta["REQUEST_URI"]}' in #{Time.now.to_f - time_start} secs (#{@resp.status})." if @debug
354
355
  @cgroup.join
355
356
 
356
357
  @hb.events.call(:request_done, {