nitro 0.27.0 → 0.28.0

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 (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>