nitro 0.27.0 → 0.28.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. data/CHANGELOG +276 -0
  2. data/ProjectInfo +4 -4
  3. data/README +9 -1
  4. data/Rakefile +1 -1
  5. data/doc/AUTHORS +30 -17
  6. data/doc/RELEASES +110 -23
  7. data/lib/glue/sweeper.rb +1 -1
  8. data/lib/glue/webfile.rb +1 -1
  9. data/lib/nitro.rb +1 -1
  10. data/lib/nitro/adapter/acgi.rb +5 -1
  11. data/lib/nitro/adapter/cgi.rb +4 -0
  12. data/lib/nitro/adapter/fastcgi.rb +12 -1
  13. data/lib/nitro/adapter/mongrel.rb +219 -0
  14. data/lib/nitro/adapter/scgi.rb +62 -69
  15. data/lib/nitro/caching.rb +6 -5
  16. data/lib/nitro/caching/actions.rb +14 -8
  17. data/lib/nitro/caching/fragments.rb +32 -17
  18. data/lib/nitro/caching/output.rb +10 -2
  19. data/lib/nitro/cgi.rb +7 -3
  20. data/lib/nitro/cgi/stream.rb +1 -3
  21. data/lib/nitro/compiler.rb +5 -4
  22. data/lib/nitro/compiler/errors.rb +1 -1
  23. data/lib/nitro/compiler/morphing.rb +2 -2
  24. data/lib/nitro/compiler/script.rb +1 -1
  25. data/lib/nitro/context.rb +8 -2
  26. data/lib/nitro/controller.rb +1 -1
  27. data/lib/nitro/dispatcher.rb +0 -2
  28. data/lib/nitro/element.rb +5 -5
  29. data/lib/nitro/flash.rb +1 -1
  30. data/lib/nitro/helper.rb +2 -2
  31. data/lib/nitro/helper/form.rb +1 -1
  32. data/lib/nitro/helper/form/controls.rb +2 -1
  33. data/lib/nitro/helper/javascript.rb +1 -1
  34. data/lib/nitro/helper/javascript/morphing.rb +1 -1
  35. data/lib/nitro/helper/layout.rb +1 -1
  36. data/lib/nitro/helper/pager.rb +7 -3
  37. data/lib/nitro/helper/rss.rb +1 -1
  38. data/lib/nitro/render.rb +1 -1
  39. data/lib/nitro/scaffolding.rb +26 -4
  40. data/lib/nitro/server/drb.rb +106 -0
  41. data/lib/nitro/server/runner.rb +23 -2
  42. data/lib/nitro/session.rb +33 -16
  43. data/lib/nitro/session/drb.rb +6 -20
  44. data/lib/nitro/session/file.rb +4 -49
  45. data/lib/nitro/session/memory.rb +16 -0
  46. data/lib/nitro/session/og.rb +4 -46
  47. data/src/part/admin/controller.rb +2 -3
  48. data/src/part/admin/template/index.xhtml +1 -1
  49. data/test/nitro/tc_cgi.rb +72 -3
  50. data/test/nitro/tc_render.rb +1 -1
  51. data/test/nitro/tc_session.rb +16 -15
  52. metadata +12 -14
  53. data/lib/nitro/caching/invalidation.rb +0 -25
  54. data/lib/nitro/caching/stores.rb +0 -94
  55. data/lib/nitro/helper/form/test.xhtml +0 -0
  56. data/lib/nitro/session/drbserver.rb +0 -84
@@ -60,7 +60,7 @@ private
60
60
  # Expire affected cached fragments.
61
61
 
62
62
  def expire_affected_fragment(name, options = {})
63
- Nitro::Caching::Fragments.store.delete(name, options)
63
+ Nitro::Caching::Fragments.cache.delete(name, options)
64
64
  end
65
65
  alias_method :expire_fragment, :expire_affected_fragment
66
66
 
@@ -1,6 +1,6 @@
1
1
  require 'fileutils'
2
2
 
3
- require 'mega/inheritor'
3
+ require 'facet/inheritor'
4
4
 
5
5
  module Glue
6
6
 
@@ -16,7 +16,7 @@ module Nitro
16
16
 
17
17
  # The version.
18
18
 
19
- Version = '0.27.0'
19
+ Version = '0.28.0'
20
20
 
21
21
  # Library path.
22
22
 
@@ -14,6 +14,10 @@ require 'glue/flexob'
14
14
 
15
15
  Socket.do_not_reverse_lookup = true
16
16
 
17
+ # No multi-threading.
18
+
19
+ Og.thread_safe = false
20
+
17
21
  module Nitro
18
22
 
19
23
  # ACGI Adaptor. ACGI is a language independent,
@@ -33,7 +37,7 @@ module Nitro
33
37
  # instead of fcgi.rb / cgi.rb
34
38
  #
35
39
  #--
36
- # gmosx: not tested
40
+ # gmosx: not tested! Use at your own risk!
37
41
  #++
38
42
 
39
43
  class ACGI < ::CGI
@@ -16,6 +16,10 @@ end
16
16
 
17
17
  Socket.do_not_reverse_lookup = true
18
18
 
19
+ # No multi-threading.
20
+
21
+ Og.thread_safe = false
22
+
19
23
  module Nitro
20
24
 
21
25
  # A plain CGI adapter. To be used only in development
@@ -11,6 +11,10 @@ require 'glue/flexob'
11
11
 
12
12
  Socket.do_not_reverse_lookup = true
13
13
 
14
+ # No multi-threading.
15
+
16
+ Og.thread_safe = false
17
+
14
18
  module Nitro
15
19
 
16
20
  # FastCGI Adaptor. FastCGI is a language independent,
@@ -18,7 +22,12 @@ module Nitro
18
22
  # performance without the limitations of server
19
23
  # specific APIs.
20
24
  #
21
- # No need for connection pooling, fastcgi uses processes.
25
+ # === Sessions
26
+ #
27
+ # As FCGI is process based, you have cant use the default
28
+ # in-memory session store. For production web sites you should
29
+ # use the drb session store. Moreover, there is no need for
30
+ # DB connection pooling in Og.
22
31
 
23
32
  class FastCGI
24
33
 
@@ -34,3 +43,5 @@ class FastCGI
34
43
  end
35
44
 
36
45
  end
46
+
47
+ # * George Moschovitis <gm@navel.gr>
@@ -0,0 +1,219 @@
1
+ require 'stringio'
2
+
3
+ require 'mongrel'
4
+
5
+ require 'glue/flexob'
6
+ require 'nitro/cgi'
7
+ require 'nitro/context'
8
+ require 'nitro/dispatcher'
9
+
10
+ # Speeds things up, more comaptible with OSX.
11
+
12
+ Socket.do_not_reverse_lookup = true
13
+
14
+ #--
15
+ # Fix for Nitro.
16
+ #++
17
+
18
+ module Mongrel # :nodoc: all
19
+ class HttpRequest
20
+ def method_missing(name, *args)
21
+ if @params.has_key?(name.to_s.upcase)
22
+ return @params[name.to_s.upcase]
23
+ elsif name.to_s =~ /\A(.*)=\Z/ && @params.has_key?($1.upcase)
24
+ @params[$1.upcase] = args[0]
25
+ else
26
+ super
27
+ end
28
+ end
29
+ end
30
+ end
31
+
32
+
33
+ module Nitro
34
+
35
+ class Mongrel
36
+
37
+ class << self
38
+ attr_accessor :mongrel
39
+
40
+ # Start the Webrick adapter.
41
+
42
+ def start(server)
43
+ # TODO add logging.
44
+
45
+ mongrel_options = server.options.dup
46
+
47
+ mongrel_options.update(
48
+ :BindAddress => server.address,
49
+ :Port => server.port,
50
+ :DocumentRoot => server.public_root
51
+ )
52
+ @mongrel = ::Mongrel::HttpServer.new(mongrel_options[:BindAddress],
53
+ mongrel_options[:Port])
54
+
55
+ trap('INT') { stop } # will this work?
56
+
57
+ @mongrel.register('/', MongrelAdapter.new(server))
58
+
59
+ initialize_mongrel(server)
60
+
61
+ @mongrel_thread = @mongrel.run
62
+ @mongrel_thread.join
63
+ end
64
+
65
+ # Stop the Mongrel adapter.
66
+
67
+ def stop
68
+ @mongrel_thread.kill
69
+ end
70
+
71
+ # Override this method to perform customized mongrel
72
+ # initialization.
73
+
74
+ def initialize_mongrel(server)
75
+ end
76
+
77
+ end
78
+
79
+ end
80
+
81
+ # A special handler for Xhtml files.
82
+
83
+ #class XhtmlFileHandler < WEBrick::HTTPServlet::DefaultFileHandler
84
+ # def do_GET(req, res)
85
+ # res['content-type'] = 'text/html'
86
+ # res.body = '<html><body>Permission denied</body></html>'
87
+ # end
88
+ #end
89
+
90
+ # A Mongrel Adapter for Nitro.
91
+
92
+ class MongrelAdapter < ::Mongrel::HttpHandler
93
+ attr_accessor :server
94
+
95
+ STATUS_CODES = {
96
+ 200 => "OK", "304" => "Not Modified",
97
+ "404" => "Not found", "500" => "Server Error"
98
+ }
99
+
100
+ def initialize(server)
101
+ @server = server
102
+ end
103
+
104
+ def process(req, res)
105
+ handle(req, res)
106
+ end
107
+
108
+ # Handle a static file. Also handles cached pages.
109
+
110
+ def handle_file(req, res)
111
+ begin
112
+ rewrite(req)
113
+ # TODO handle If-Modified-Since and add Last-Modified headers
114
+ filename = [@server.public_root, req.path_info].join("/")
115
+ File.open(filename, "r") { |f|
116
+ # TODO look up mime type
117
+ # TODO check whether path circumvents public_root directory?
118
+ size = File.size(filename)
119
+ res.socket << "HTTP/1.1 200 OK\r\n"
120
+ res.socket << "Content-type: text/plain\r\n"
121
+ res.socket << "Content-length: #{size}\r\n"
122
+ res.socket << "\r\n"
123
+ res.socket << f.read # XXX inefficient for large files, may cause leaks
124
+ }
125
+ return true
126
+ rescue Object => ex
127
+ return false
128
+ ensure
129
+ unrewrite(req)
130
+ end
131
+ end
132
+
133
+ # Handle the request.
134
+
135
+ def handle(req, res)
136
+ unless handle_file(req, res)
137
+ path = req.path_info
138
+
139
+ unless path =~ /\./
140
+ begin
141
+ context = Context.new(@server)
142
+
143
+ context.in = StringIO.new(req.body || "")
144
+
145
+ context.headers = {}
146
+ req.params.each { |h, v|
147
+ if h =~ /\AHTTP_(.*)\Z/
148
+ context.headers[$1.gsub("_", "-")] = v
149
+ end
150
+ context.headers[h] = v
151
+ }
152
+ # context.headers.update(req.meta_vars)
153
+
154
+ context.headers['REQUEST_URI'] = context.headers['SCRIPT_NAME']
155
+
156
+ if context.headers['PATH_INFO'].blank?
157
+ context.headers['REQUEST_URI'] = '/'
158
+ else
159
+ context.headers['REQUEST_URI'] = '/' + context.headers['PATH_INFO']
160
+ end
161
+
162
+ # gmosx: make compatible with fastcgi.
163
+ #context.headers['REQUEST_URI'].slice!(/http:\/\/(.*?)\//)
164
+ # context.headers['REQUEST_URI'] << '/'
165
+
166
+ Cgi.parse_params(context)
167
+ Cgi.parse_cookies(context)
168
+
169
+ context.render(path)
170
+
171
+ res.socket << "HTTP/1.1 #{context.status.to_s} "
172
+
173
+ if STATUS_CODES.has_key? context.status
174
+ res.socket << STATUS_CODES[context.status]
175
+ else
176
+ res.socket << "Unknown Status Code"
177
+ end
178
+ res.socket << "\r\n"
179
+
180
+ headers = context.response_headers
181
+ headers["Content-length"] = context.out.size
182
+ headers.each { |h,v| res.socket << "#{h}: #{v}\r\n" }
183
+ res.socket << "\r\n"
184
+
185
+ # TODO handle setting cookies
186
+ res.socket << context.out
187
+
188
+ context.close
189
+ ensure
190
+ Og.manager.put_store if defined?(Og) and Og.respond_to?(:manager)
191
+ end
192
+ end
193
+ end
194
+ end
195
+
196
+ def rewrite(req)
197
+ if req.path_info == '/' || req.path_info == ''
198
+ req.path_info = '/index.html'
199
+ elsif req.path_info =~ /^([^.]+)$/
200
+ req.path_info = "#{$1}/index.html"
201
+ end
202
+ end
203
+
204
+ # Rewrite back to the original path.
205
+
206
+ def unrewrite(req)
207
+ if req.path_info == '/index.html'
208
+ req.path_info = '/'
209
+ elsif req.path_info =~ /^([^.]+)\/index.html$/
210
+ req.path_info = $1
211
+ end
212
+ end
213
+ end
214
+
215
+ end
216
+
217
+ # * Joshua Hoke
218
+ # * George Moschovitis <gm@navel.gr>
219
+
@@ -10,57 +10,6 @@ require 'singleton'
10
10
 
11
11
  module SCGI # :nodoc: all
12
12
 
13
- class LogFactory < Monitor
14
- include Singleton
15
-
16
- def initialize
17
- super()
18
- @@logs = {}
19
- end
20
-
21
- def create(file)
22
- result = nil
23
- synchronize do
24
- result = @@logs[file]
25
- if not result
26
- result = Log.new(file)
27
- @@logs[file] = result
28
- end
29
- end
30
- return result
31
- end
32
- end
33
-
34
- class Log < Monitor
35
- def initialize(file)
36
- super()
37
- @out = open(file, "a+")
38
- @out.sync = true
39
- @pid = Process.pid
40
- @info = "[INF][#{Process.pid}] "
41
- @error = "[ERR][#{Process.pid}] "
42
- end
43
-
44
- def info(msg)
45
- synchronize do
46
- @out.print @info, msg,"\n"
47
- end
48
- end
49
-
50
- def error(msg, exc=nil)
51
- if exc
52
- synchronize do
53
- @out.print @error, "#{msg}: #{exc}\n"
54
- @out.print @error, exc.backtrace.join("\n"), "\n"
55
- end
56
- else
57
- synchronize do
58
- @out.print @error, msg,"\n"
59
- end
60
- end
61
- end
62
- end
63
-
64
13
  # Modifies CGI so that we can use it.
65
14
  class SCGIFixed < ::CGI # :nodoc: all
66
15
  public :env_table
@@ -78,6 +27,8 @@ module SCGI # :nodoc: all
78
27
  def env_table
79
28
  @env_table
80
29
  end
30
+ alias_method :env, :env_table
31
+
81
32
  def stdinput
82
33
  @input
83
34
  end
@@ -86,14 +37,14 @@ module SCGI # :nodoc: all
86
37
  end
87
38
  end
88
39
 
89
-
90
40
  class SCGIProcessor < Monitor # :nodoc: all
91
41
  attr_reader :settings
92
42
 
93
- def initialize(settings = {})
43
+ def initialize(server, settings = {})
94
44
  @conns = 0
95
45
  @shutdown = false
96
46
  @dead = false
47
+ @server = server
97
48
  super()
98
49
 
99
50
  configure(settings)
@@ -101,15 +52,18 @@ module SCGI # :nodoc: all
101
52
 
102
53
  def configure(settings)
103
54
  @settings = settings
104
- @log = LogFactory.instance.create(settings[:logfile] || "log/scgi.log")
105
- @maxconns = settings[:maxconns] || 2**30-1
55
+ #@log = LogFactory.instance.create(settings[:logfile])
56
+ @log = Logger
57
+ @log = Logger.new(settings[:logfile]) if settings[:logfile]
58
+
59
+ @maxconns = settings[:maxconns]
106
60
  @started = Time.now
107
61
 
108
62
  if settings[:socket]
109
63
  @socket = settings[:socket]
110
64
  else
111
- @host = settings[:host] || "127.0.0.1"
112
- @port = settings[:port] || "9999"
65
+ @host = settings[:host]
66
+ @port = settings[:port]
113
67
  end
114
68
 
115
69
  @throttle_sleep = 1.0/settings[:conns_second] if settings[:conns_second]
@@ -125,14 +79,14 @@ module SCGI # :nodoc: all
125
79
  break if @shutdown and @conns <= 0
126
80
  end
127
81
  rescue Interrupt
128
- @log.info("Shutting down from SIGINT.")
82
+ @log.info("SCGI: Shutting down from SIGINT.")
129
83
  rescue Object
130
- @log.error("while listening for connections on #@host:#@port -- #{$!.class}", $!)
84
+ @log.warn("SCGI: while listening for connections on #@host:#@port -- #{$!.class} #$! #{$!.backtrace.join("\n")}" )
131
85
  end
132
86
 
133
87
  @socket.close if not @socket.closed?
134
88
  @dead = true
135
- @log.info("Exited accept loop. Shutdown complete.")
89
+ @log.info("SCGI: Exited accept loop. Shutdown complete.")
136
90
  end
137
91
 
138
92
 
@@ -158,14 +112,14 @@ module SCGI # :nodoc: all
158
112
  # we should now either have a payload length to get
159
113
  payload = socket.read(len.to_i)
160
114
  if (c = socket.read(1)) != ','
161
- @log.error("Malformed request, does not end with ','")
115
+ @log.warn("SCGI: Malformed request, does not end with ','")
162
116
  else
163
117
  read_header(socket, payload, @conns)
164
118
  end
165
119
  rescue IOError
166
- @log.error("received IOError #$! when handling client. Your web server doesn't like me.")
120
+ @log.warn("SCGI: received IOError #$! when handling client. Your web server doesn't like me.")
167
121
  rescue Object
168
- @log.error("after accepting client #@host:#@port -- #{$!.class}", $!)
122
+ @log.warn("SCGI: after accepting client #@host:#@port -- #{$!.class} #$! #{$!.backtrace.join("\n")}")
169
123
  ensure
170
124
  synchronize { @conns -= 1 if not in_shutdown}
171
125
  socket.close if not socket.closed?
@@ -197,10 +151,26 @@ module SCGI # :nodoc: all
197
151
  end
198
152
 
199
153
 
200
- def process_request(request, body, socket)
201
- raise "You must implement process_request"
202
- end
203
-
154
+ def process_request(request, body, socket)
155
+ return if socket.closed?
156
+ cgi = SCGIFixed.new(request, body, socket)
157
+ begin
158
+ #--
159
+ # TODO: remove sync, Nitro *is* thread safe!
160
+ #++
161
+ # guill: and why not ? ;)
162
+ #synchronize do
163
+ #--
164
+ # FIXME: this is uggly, something better?
165
+ #++
166
+ cgi.stdinput.rewind
167
+ cgi.env["QUERY_STRING"] = (cgi.env["REQUEST_URI"] =~ /^[^?]+\?(.+)$/ and $1).to_s
168
+ Nitro::Cgi.process(@server, cgi, cgi.stdinput, cgi.stdoutput)
169
+ #end
170
+ ensure
171
+ Og.manager.put_store if defined?(Og) and Og.respond_to?(:manager)
172
+ end
173
+ end
204
174
 
205
175
  def split_body(data)
206
176
  result = {}
@@ -234,12 +204,35 @@ module SCGI # :nodoc: all
234
204
 
235
205
  if force
236
206
  @socket.close
237
- @log.info("FORCED shutdown requested. Oh well.")
207
+ @log.info("SCGI: FORCED shutdown requested. Oh well.")
238
208
  else
239
- @log.info("Shutdown requested. Beginning graceful shutdown with #@conns connected.")
209
+ @log.info("SCGI: Shutdown requested. Beginning graceful shutdown with #@conns connected.")
240
210
  end
241
211
  end
242
212
  end
213
+
214
+ class << self
215
+ def start(server)
216
+ settings = {
217
+ :host => server.address,
218
+ :port => server.port,
219
+ :logfile => nil, # will use Logger
220
+ :maxconns => 2**30-1,
221
+ :socket => nil,
222
+ :conns_second => nil,
223
+ :env => nil,
224
+ :drb_enable => false,
225
+ :drb_port => server.port - 1000,
226
+ :drb_password => ""
227
+ }
228
+
229
+ settings.update(server.options)
230
+
231
+ @nitro = SCGIProcessor.new(server, settings)
232
+ Logger.info("SCGI: Running on #{settings[:host]}:#{settings[:port]}")
233
+ @nitro.listen
234
+ end
235
+ end
243
236
  end
244
237
 
245
238
  # * Zed A Shaw <zedshaw@zedshaw.com>