knjappserver 0.0.6

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 (47) hide show
  1. data/.document +5 -0
  2. data/.rspec +1 -0
  3. data/Gemfile +18 -0
  4. data/Gemfile.lock +39 -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 +71 -0
  10. data/bin/knjappserver_start.rb +50 -0
  11. data/knjappserver.gemspec +107 -0
  12. data/lib/conf/README +1 -0
  13. data/lib/conf/conf_example.rb +109 -0
  14. data/lib/conf/conf_vars_example.rb +3 -0
  15. data/lib/files/database_schema.rb +111 -0
  16. data/lib/files/run/README +1 -0
  17. data/lib/include/class_customio.rb +21 -0
  18. data/lib/include/class_erbhandler.rb +36 -0
  19. data/lib/include/class_httpresp.rb +91 -0
  20. data/lib/include/class_httpserver.rb +91 -0
  21. data/lib/include/class_httpsession.rb +350 -0
  22. data/lib/include/class_httpsession_knjengine.rb +189 -0
  23. data/lib/include/class_httpsession_mongrel.rb +75 -0
  24. data/lib/include/class_httpsession_webrick.rb +75 -0
  25. data/lib/include/class_knjappserver.rb +455 -0
  26. data/lib/include/class_knjappserver_cleaner.rb +109 -0
  27. data/lib/include/class_knjappserver_errors.rb +117 -0
  28. data/lib/include/class_knjappserver_logging.rb +272 -0
  29. data/lib/include/class_knjappserver_mailing.rb +97 -0
  30. data/lib/include/class_knjappserver_threadding.rb +87 -0
  31. data/lib/include/class_knjappserver_web.rb +23 -0
  32. data/lib/include/class_log.rb +81 -0
  33. data/lib/include/class_log_access.rb +103 -0
  34. data/lib/include/class_log_data.rb +42 -0
  35. data/lib/include/class_log_data_link.rb +16 -0
  36. data/lib/include/class_log_data_value.rb +34 -0
  37. data/lib/include/class_log_link.rb +51 -0
  38. data/lib/include/class_session.rb +43 -0
  39. data/lib/include/gettext_funcs.rb +10 -0
  40. data/lib/include/magic_methods.rb +59 -0
  41. data/lib/knjappserver.rb +1 -0
  42. data/lib/pages/logs_latest.rhtml +57 -0
  43. data/lib/pages/logs_show.rhtml +32 -0
  44. data/lib/pages/spec.rhtml +10 -0
  45. data/spec/knjappserver_spec.rb +110 -0
  46. data/spec/spec_helper.rb +12 -0
  47. metadata +188 -0
@@ -0,0 +1,36 @@
1
+ class Knjappserver::ERBHandler
2
+ def initialize
3
+ @connected = {}
4
+ end
5
+
6
+ def erb_handler(data)
7
+ #Hack the Knj::Thread to accept data - this is how get, post and etc. are set.
8
+ Thread.current[:knjappserver] = data
9
+ eruby = data[:httpsession].eruby
10
+
11
+ if !@connected[eruby.__id__]
12
+ eruby.connect("error") do |e|
13
+ _kas.handle_error(e)
14
+ end
15
+
16
+ @connected[eruby.__id__] = true
17
+ end
18
+
19
+ cont = eruby.load_return(data[:filepath], {
20
+ :with_headers => false,
21
+ :custom_io => true
22
+ })
23
+ headers = eruby.headers
24
+ eruby.reset_headers
25
+
26
+ headers_ret = {}
27
+ headers.each do |header|
28
+ headers_ret[header[0]] = [header[1]]
29
+ end
30
+
31
+ Thread.current[:knjappserver].clear
32
+ Thread.current[:knjappserver] = nil
33
+
34
+ return {:headers => headers}
35
+ end
36
+ end
@@ -0,0 +1,91 @@
1
+ require "time"
2
+
3
+ class Knjappserver::Httpresp
4
+ attr_accessor :body, :nl, :status
5
+
6
+ STATUS_CODES = {
7
+ 100 => "Continue",
8
+ 200 => "OK",
9
+ 201 => "Created",
10
+ 202 => "Accepted",
11
+ 204 => "No Content",
12
+ 205 => "Reset Content",
13
+ 206 => "Partial Content",
14
+ 301 => "Moved Permanently",
15
+ 302 => "Found",
16
+ 303 => "See Other",
17
+ 304 => "Not Modified",
18
+ 307 => "Temporary Redirect",
19
+ 400 => "Bad Request",
20
+ 401 => "Unauthorized",
21
+ 403 => "Forbidden",
22
+ 404 => "Not Found",
23
+ 500 => "Internal Server Error"
24
+ }
25
+ NL = "\r\n"
26
+
27
+ def initialize
28
+ @status = 200
29
+ @headers = {
30
+ "Content-Type" => "text/html",
31
+ "Date" => Time.now.httpdate,
32
+ "Connection" => "Keep-Alive",
33
+ "Transfer-Encoding" => "chunked",
34
+ "Keep-Alive" => "timeout=30, max=100"
35
+ }
36
+ @cookies = []
37
+ end
38
+
39
+ def header(key, val)
40
+ @headers[key] = val
41
+ end
42
+
43
+ def cookie(cookie)
44
+ @cookies << cookie
45
+ end
46
+
47
+ def header_str
48
+ res = "HTTP/1.1 #{@status}"
49
+ code = STATUS_CODES[@status]
50
+ res += " #{code}" if code
51
+ res += NL
52
+ #res += "Content-Length: #{@body.length}#{NL}"
53
+
54
+ @headers.each do |key, val|
55
+ res += "#{key}: #{val}#{NL}"
56
+ end
57
+
58
+ @cookies.each do |cookie|
59
+ res += "Set-Cookie: #{cookie}#{NL}"
60
+ end
61
+
62
+ res += NL
63
+
64
+ return res
65
+ end
66
+
67
+ def write_chunked(socket)
68
+ socket.write(self.header_str)
69
+
70
+ @body.each do |part|
71
+ while buf = part.read(1024)
72
+ next if buf.empty?
73
+ socket.write("#{format("%x", buf.bytesize)}#{NL}#{buf}#{NL}")
74
+ end
75
+ end
76
+
77
+ socket.write("0#{NL}#{NL}")
78
+ end
79
+
80
+ def content
81
+ str = self.header_str + @body.string + "\n\n"
82
+ end
83
+
84
+ def destroy
85
+ @status = nil
86
+ @status_codes = nil
87
+ @body = nil
88
+ @cookies = nil
89
+ @headers = nil
90
+ end
91
+ end
@@ -0,0 +1,91 @@
1
+ class Knjappserver::Httpserver
2
+ attr_accessor :working_count
3
+ attr_reader :kas, :http_sessions, :thread_accept, :thread_restart, :server
4
+
5
+ def initialize(kas)
6
+ @kas = kas
7
+ @http_sessions = []
8
+ @working_count = 0
9
+ end
10
+
11
+ def start
12
+ @server = TCPServer.new(@kas.config[:host], @kas.config[:port])
13
+
14
+ @thread_accept = Knj::Thread.new do
15
+ loop do
16
+ if !@server or @server.closed?
17
+ sleep 1
18
+ next
19
+ end
20
+
21
+ begin
22
+ self.spawn_httpsession(@server.accept)
23
+ STDOUT.print "Starting new HTTP-request.\n" if @kas.config[:debug]
24
+ rescue => e
25
+ STDOUT.puts e.inspect
26
+ STDOUT.puts e.backtrace
27
+ STDOUT.print "\n"
28
+ STDOUT.print "Could not accept HTTP-request - waiting 0.5 sec and then trying again.\n"
29
+ sleep 0.5
30
+ end
31
+ end
32
+ end
33
+
34
+ @thread_restart = Knj::Thread.new do
35
+ loop do
36
+ sleep 10
37
+ break if @kas.should_restart and @kas.should_restart_done
38
+
39
+ if !@kas.should_restart and (!@server or @server.closed?)
40
+ STDOUT.print "Socket does not exist or is closed - restarting HTTP-server!\n"
41
+ @server = TCPServer.new(@kas.config[:host], @kas.config[:port])
42
+ STDOUT.print "Done.\n"
43
+ end
44
+ end
45
+ end
46
+ end
47
+
48
+ def stop
49
+ begin
50
+ STDOUT.print "Stopping accept-thread.\n" if @kas.config[:debug]
51
+ @thread_accept.kill if @thread_accept and @thread_accept.alive?
52
+ @thread_restart.kill if @thread_restart and @thread_restart.alive?
53
+ rescue => e
54
+ STDOUT.print "Could not stop accept-thread.\n" if @kas.config[:debug]
55
+ STDOUT.puts e.inspect
56
+ STDOUT.puts e.backtrace
57
+ end
58
+
59
+ STDOUT.print "Stopping all HTTP sessions.\n" if @kas.config[:debug]
60
+ @http_sessions.each do |httpsession|
61
+ httpsession.destruct
62
+ end
63
+
64
+ begin
65
+ STDOUT.print "Stopping TCPServer.\n" if @kas.config[:debug]
66
+ @server.close if @server and !@server.closed?
67
+ STDOUT.print "TCPServer was closed.\n" if @kas.config[:debug]
68
+ rescue Timeout::Error
69
+ raise "Could not close TCPserver.\n"
70
+ rescue IOError => e
71
+ if e.message == "closed stream"
72
+ #ignore - it should be closed.
73
+ else
74
+ raise e
75
+ end
76
+ end
77
+ end
78
+
79
+ def spawn_httpsession(socket)
80
+ @http_sessions << Knjappserver::Httpsession.new(self, socket)
81
+ end
82
+
83
+ def handle_request(&block)
84
+ @working_count += 1
85
+ begin
86
+ block.call
87
+ ensure
88
+ @working_count -= 1
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,350 @@
1
+ require "digest"
2
+
3
+ class Knjappserver::Httpsession
4
+ attr_accessor :data
5
+ attr_reader :session, :session_id, :session_hash, :kas, :active, :out, :eruby, :browser, :debug
6
+
7
+ def initialize(httpserver, socket)
8
+ @data = {}
9
+ @socket = socket
10
+ @httpserver = httpserver
11
+ @kas = httpserver.kas
12
+ @active = true
13
+ @eruby = Knj::Eruby.new(:cache_hash => @kas.eruby_cache)
14
+ @debug = @kas.config[:debug]
15
+ self.reset
16
+
17
+ if @kas.config[:engine_webrick]
18
+ require "#{File.dirname(__FILE__)}/class_httpsession_webrick"
19
+ @handler = Knjappserver::Httpsession::Webrick.new(:kas => @kas)
20
+ elsif @kas.config[:engine_mongrel]
21
+ require "#{File.dirname(__FILE__)}/class_httpsession_mongrel"
22
+ @handler = Knjappserver::Httpsession::Mongrel.new(:kas => @kas)
23
+ elsif @kas.config[:engine_knjengine]
24
+ require "#{File.dirname(__FILE__)}/class_httpsession_knjengine"
25
+ @handler = Knjappserver::Httpsession::Knjengine.new(:kas => @kas)
26
+ else
27
+ raise "Unknown handler."
28
+ end
29
+
30
+ Dir.chdir(@kas.config[:doc_root])
31
+ ObjectSpace.define_finalizer(self, self.class.method(:finalize).to_proc) if @debug
32
+ STDOUT.print "New httpsession #{self.__id__} (total: #{@httpserver.http_sessions.count}).\n" if @debug
33
+
34
+ @thread_request = Knj::Thread.new do
35
+ @kas.db_handler.get_and_register_thread if @kas.db_handler.opts[:threadsafe]
36
+ @kas.ob.db.get_and_register_thread if @kas.ob.db.opts[:threadsafe]
37
+
38
+ begin
39
+ while @active
40
+ begin
41
+ Timeout.timeout(30) do
42
+ @handler.socket_parse(@socket)
43
+ end
44
+
45
+ sleep 0.1 while @kas.paused? #Check if we should be waiting with executing the pending request.
46
+
47
+ if @kas.config[:max_requests_working]
48
+ while @httpserver.working_count >= @kas.config[:max_requests_working]
49
+ STDOUT.print "Maximum amounts of requests are working (#{@httpserver.working_count}, #{@kas.config[:max_requests_working]}) - sleeping.\n" if @debug
50
+ sleep 0.1
51
+ end
52
+ end
53
+
54
+ @httpserver.handle_request do
55
+ self.serve
56
+ end
57
+ ensure
58
+ @kas.served += 1 if @kas
59
+ self.reset
60
+ end
61
+ end
62
+ rescue WEBrick::HTTPStatus::RequestTimeout, WEBrick::HTTPStatus::EOFError, Errno::ECONNRESET, Errno::EPIPE, Timeout::Error => e
63
+ #Ignore - the user probaly left.
64
+ #STDOUT.puts e.inspect
65
+ #STDOUT.puts e.backtrace
66
+ rescue SystemExit, Interrupt => e
67
+ raise e
68
+ rescue RuntimeError, Exception => e
69
+ first = e.backtrace.first
70
+
71
+ if first.index("webrick/httprequest.rb") != nil or first.index("webrick/httpresponse.rb") != nil
72
+ if debug
73
+ STDOUT.print "Notice: Webrick error - properly faulty request - ignoring!\n"
74
+ STDOUT.puts e.inspect
75
+ STDOUT.puts e.backtrace
76
+ end
77
+ else
78
+ STDOUT.puts e.inspect
79
+ STDOUT.puts e.backtrace
80
+ end
81
+ ensure
82
+ @kas.db_handler.free_thread if @kas and @kas.db_handler.opts[:threadsafe]
83
+ @kas.ob.db.free_thread if @kas and @kas.ob.db.opts[:threadsafe]
84
+ self.destruct
85
+ end
86
+ end
87
+ end
88
+
89
+ def threadded_content(block)
90
+ raise "No block was given." if !block
91
+ @out = StringIO.new
92
+
93
+ thread_out = StringIO.new
94
+ thread = Thread.new(Thread.current[:knjappserver].clone) do |data|
95
+ Thread.current[:knjappserver] = data
96
+ Thread.current[:knjappserver][:stringio] = thread_out
97
+ Thread.current[:knjappserver][:db] = @kas.db_handler
98
+
99
+ @kas.db_handler.get_and_register_thread if @kas.db_handler.opts[:threadsafe]
100
+ @kas.ob.db.get_and_register_thread if @kas.ob.db.opts[:threadsafe]
101
+
102
+ begin
103
+ block.call
104
+ ensure
105
+ @kas.ob.db.free_thread if @kas.ob.db.opts[:threadsafe]
106
+ @kas.db_handler.free_thread if @kas.db_handler.opts[:threadsafe]
107
+ end
108
+ end
109
+
110
+ @parts << {
111
+ :thread => thread,
112
+ :stringio => thread_out
113
+ }
114
+ @parts << @out
115
+ end
116
+
117
+ def reset
118
+ @out.close if @out
119
+ @out = StringIO.new
120
+ @parts = [@out]
121
+ end
122
+
123
+ def self.finalize(id)
124
+ STDOUT.print "Httpsession finalize #{id}.\n" if @debug
125
+ end
126
+
127
+ def destruct
128
+ STDOUT.print "Httpsession destruct (#{@httpserver.http_sessions.length})\n" if @debug and @httpserver
129
+
130
+ begin
131
+ @socket.close if @socket and !@socket.closed?
132
+ rescue => e
133
+ STDOUT.puts e.inspect
134
+ STDOUT.puts e.backtrace
135
+ #ignore if it fails...
136
+ end
137
+
138
+ @httpserver.http_sessions.delete(self) if @httpserver
139
+ @httpserver = nil
140
+
141
+ @data = nil
142
+ @kas = nil
143
+ @active = nil
144
+ @session = nil
145
+ @session_id = nil
146
+ @session_hash = nil
147
+ @out = nil
148
+ @socket = nil
149
+ @browser = nil
150
+
151
+ @eruby.destroy if @eruby
152
+ @eruby = nil
153
+
154
+ @handler.destroy if @handler
155
+ @handler = nil
156
+
157
+ thread = @thread_request
158
+ @thread_request = nil
159
+ thread.kill if thread and thread.alive?
160
+ end
161
+
162
+ def serve
163
+ resp = Knjappserver::Httpresp.new
164
+
165
+ meta = @handler.meta
166
+ cookie = @handler.cookie
167
+ page_path = @handler.page_path
168
+
169
+ pinfo = Knj::Php.pathinfo(page_path)
170
+ ext = pinfo["extension"].downcase
171
+
172
+ ctype = @kas.types[ext.to_sym] if @kas.types[ext.to_sym]
173
+ ctype = @kas.config[:default_filetype] if !ctype and @kas.config.has_key?(:default_filetype)
174
+ resp.header("Content-Type", ctype)
175
+
176
+ @browser = Knj::Web.browser(meta)
177
+ @ip = nil
178
+ @ip = meta["HTTP_X_FORWARDED_FOR"].split(",")[0].strip if !@ip and meta["HTTP_X_FORWARDED_FOR"]
179
+ @ip = meta["REMOTE_ADDR"] if !@ip and meta["REMOTE_ADDR"]
180
+
181
+ @ips = [meta["REMOTE_ADDR"]]
182
+ @ips << meta["HTTP_X_FORWARDED_FOR"].split(",")[0].strip if meta["HTTP_X_FORWARDED_FOR"]
183
+
184
+ @session_id = nil
185
+ @session_id = "bot" if @browser["browser"] == "bot"
186
+ @session_id = cookie["KnjappserverSession"] if cookie["KnjappserverSession"].to_s.length > 0
187
+
188
+ if !@session_id
189
+ @session_id = Digest::MD5.hexdigest("#{Time.new.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"]}")
190
+
191
+ resp.cookie(CGI::Cookie.new(
192
+ "name" => "KnjappserverSession",
193
+ "value" => @session_id,
194
+ "path" => "/",
195
+ "expires" => (Knj::Datet.new.months + 12).time
196
+ ).to_s)
197
+ end
198
+
199
+ session = @kas.session_fromid(:idhash => @session_id, :ip => @ip)
200
+
201
+ @session = session[:dbobj]
202
+ @session_hash = session[:hash]
203
+
204
+ if @kas.config[:logging] and @kas.config[:logging][:access_db]
205
+ @kas.logs_access_pending << {
206
+ :session_id => @session.id,
207
+ :date_request => Knj::Datet.new.dbstr,
208
+ :ips => @ips,
209
+ :get => @handler.get,
210
+ :post => @handler.post,
211
+ :meta => meta,
212
+ :cookie => cookie
213
+ }
214
+ end
215
+
216
+ time_start = Time.now if @debug
217
+ serv_data = self.serve_real(
218
+ :filepath => page_path,
219
+ :get => @handler.get,
220
+ :post => @handler.post,
221
+ :cookie => cookie,
222
+ :meta => meta,
223
+ :headers => {},
224
+ :ctype => ctype,
225
+ :ext => ext,
226
+ :session => @session,
227
+ :session_id => @session_id,
228
+ :session_hash => @session_hash,
229
+ :httpsession => self,
230
+ :db => @kas.db_handler,
231
+ :kas => @kas
232
+ )
233
+
234
+ serv_data[:headers].each do |header|
235
+ key = header[0]
236
+ val = header[1]
237
+ keystr = key.to_s.strip.downcase
238
+
239
+ if keystr.match(/^set-cookie/)
240
+ WEBrick::Cookie.parse_set_cookies(val).each do |cookie|
241
+ resp.cookie(cookie.to_s)
242
+ end
243
+ else
244
+ resp.header(key, val)
245
+ end
246
+ end
247
+
248
+ body_parts = []
249
+ @parts.each do |part|
250
+ if part.is_a?(Hash) and part[:thread]
251
+ part[:thread].join
252
+ part[:stringio].rewind
253
+ body_parts << part[:stringio]
254
+ elsif part.is_a?(StringIO) or part.is_a?(File)
255
+ part.rewind
256
+ body_parts << part
257
+ else
258
+ raise "Unknown object: '#{part.class.name}'."
259
+ end
260
+ end
261
+ resp.body = body_parts
262
+
263
+ if serv_data[:lastmod]
264
+ resp.header("Last-Modified", serv_data[:lastmod].time)
265
+ resp.header("Expires", Time.now + (3600 * 24))
266
+ end
267
+
268
+ if serv_data[:cache]
269
+ resp.status = 304
270
+ resp.header("Last-Modified", serv_data[:lastmod].time)
271
+ resp.header("Expires", Time.now + (3600 * 24))
272
+ end
273
+
274
+ resp.status = serv_data[:statuscode] if serv_data[:statuscode]
275
+ STDOUT.print "Served '#{meta["REQUEST_URI"]}' in #{Time.now.to_f - time_start.to_f} secs.\n" if @debug
276
+
277
+ resp.write_chunked(@socket) if meta["METHOD"] != "HEAD"
278
+ resp.destroy
279
+
280
+ #Letting them be nil is simply not enough (read that on a forum) - knj.
281
+ serv_data.clear
282
+ end
283
+
284
+ def serve_real(details)
285
+ request = details[:request]
286
+ headers = {}
287
+ cont = ""
288
+ statuscode = nil
289
+ lastmod = false
290
+ max_age = 365 * 24
291
+
292
+ cache = false
293
+ cache_control = {}
294
+ cache_use = true
295
+
296
+ if @handler.headers["cache-control"] and @handler.headers["cache-control"][0]
297
+ @handler.headers["cache-control"][0].scan(/(.+)=(.+)/) do |match|
298
+ cache_control[match[1]] = match[2]
299
+ end
300
+ end
301
+
302
+ cache_use = false if cache_control["max-age"].to_i <= 0
303
+
304
+ #check if we should use a handler for this request.
305
+ handler_use = false
306
+ @kas.config[:handlers].each do |handler_info|
307
+ if handler_info[:file_ext] and handler_info[:file_ext] == details[:ext]
308
+ handler_use = true
309
+ ret = handler_info[:callback].call(details)
310
+ cont = ret[:content] if ret[:content]
311
+ headers = ret[:headers] if ret[:headers]
312
+ break
313
+ elsif handler_info[:path] and handler_info[:mount] and details[:meta]["SCRIPT_NAME"].slice(0, handler_info[:path].length) == handler_info[:path]
314
+ details[:filepath] = "#{handler_info[:mount]}#{details[:meta]["SCRIPT_NAME"].slice(handler_info[:path].length, details[:meta]["SCRIPT_NAME"].length)}"
315
+ break
316
+ end
317
+ end
318
+
319
+ if !handler_use
320
+ if !File.exists?(details[:filepath])
321
+ statuscode = 404
322
+ headers["Content-Type"] = "text/html"
323
+ @parts << StringIO.new("File you are looking for was not found: '#{details[:meta]["REQUEST_URI"]}'.")
324
+ else
325
+ lastmod = Knj::Datet.new(File.new(details[:filepath]).mtime)
326
+
327
+ if cache_use and @handler.headers["if-modified-since"] and @handler.headers["if-modified-since"][0]
328
+ request_mod = Knj::Datet.parse(@handler.headers["if-modified-since"][0])
329
+ if request_mod == lastmod
330
+ cache = true
331
+ end
332
+ end
333
+
334
+ if !cache
335
+ @parts << File.new(details[:filepath]) #get plain content from file.
336
+ end
337
+ end
338
+ end
339
+
340
+ details.clear
341
+
342
+ return {
343
+ :statuscode => statuscode,
344
+ :content => cont,
345
+ :headers => headers,
346
+ :lastmod => lastmod,
347
+ :cache => cache
348
+ }
349
+ end
350
+ end