nitro 0.27.0 → 0.28.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +276 -0
- data/ProjectInfo +4 -4
- data/README +9 -1
- data/Rakefile +1 -1
- data/doc/AUTHORS +30 -17
- data/doc/RELEASES +110 -23
- data/lib/glue/sweeper.rb +1 -1
- data/lib/glue/webfile.rb +1 -1
- data/lib/nitro.rb +1 -1
- data/lib/nitro/adapter/acgi.rb +5 -1
- data/lib/nitro/adapter/cgi.rb +4 -0
- data/lib/nitro/adapter/fastcgi.rb +12 -1
- data/lib/nitro/adapter/mongrel.rb +219 -0
- data/lib/nitro/adapter/scgi.rb +62 -69
- data/lib/nitro/caching.rb +6 -5
- data/lib/nitro/caching/actions.rb +14 -8
- data/lib/nitro/caching/fragments.rb +32 -17
- data/lib/nitro/caching/output.rb +10 -2
- data/lib/nitro/cgi.rb +7 -3
- data/lib/nitro/cgi/stream.rb +1 -3
- data/lib/nitro/compiler.rb +5 -4
- data/lib/nitro/compiler/errors.rb +1 -1
- data/lib/nitro/compiler/morphing.rb +2 -2
- data/lib/nitro/compiler/script.rb +1 -1
- data/lib/nitro/context.rb +8 -2
- data/lib/nitro/controller.rb +1 -1
- data/lib/nitro/dispatcher.rb +0 -2
- data/lib/nitro/element.rb +5 -5
- data/lib/nitro/flash.rb +1 -1
- data/lib/nitro/helper.rb +2 -2
- data/lib/nitro/helper/form.rb +1 -1
- data/lib/nitro/helper/form/controls.rb +2 -1
- data/lib/nitro/helper/javascript.rb +1 -1
- data/lib/nitro/helper/javascript/morphing.rb +1 -1
- data/lib/nitro/helper/layout.rb +1 -1
- data/lib/nitro/helper/pager.rb +7 -3
- data/lib/nitro/helper/rss.rb +1 -1
- data/lib/nitro/render.rb +1 -1
- data/lib/nitro/scaffolding.rb +26 -4
- data/lib/nitro/server/drb.rb +106 -0
- data/lib/nitro/server/runner.rb +23 -2
- data/lib/nitro/session.rb +33 -16
- data/lib/nitro/session/drb.rb +6 -20
- data/lib/nitro/session/file.rb +4 -49
- data/lib/nitro/session/memory.rb +16 -0
- data/lib/nitro/session/og.rb +4 -46
- data/src/part/admin/controller.rb +2 -3
- data/src/part/admin/template/index.xhtml +1 -1
- data/test/nitro/tc_cgi.rb +72 -3
- data/test/nitro/tc_render.rb +1 -1
- data/test/nitro/tc_session.rb +16 -15
- metadata +12 -14
- data/lib/nitro/caching/invalidation.rb +0 -25
- data/lib/nitro/caching/stores.rb +0 -94
- data/lib/nitro/helper/form/test.xhtml +0 -0
- data/lib/nitro/session/drbserver.rb +0 -84
data/lib/glue/sweeper.rb
CHANGED
@@ -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.
|
63
|
+
Nitro::Caching::Fragments.cache.delete(name, options)
|
64
64
|
end
|
65
65
|
alias_method :expire_fragment, :expire_affected_fragment
|
66
66
|
|
data/lib/glue/webfile.rb
CHANGED
data/lib/nitro.rb
CHANGED
data/lib/nitro/adapter/acgi.rb
CHANGED
@@ -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
|
data/lib/nitro/adapter/cgi.rb
CHANGED
@@ -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
|
-
#
|
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
|
+
|
data/lib/nitro/adapter/scgi.rb
CHANGED
@@ -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
|
-
|
105
|
-
@
|
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]
|
112
|
-
@port = settings[:port]
|
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.
|
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.
|
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.
|
120
|
+
@log.warn("SCGI: received IOError #$! when handling client. Your web server doesn't like me.")
|
167
121
|
rescue Object
|
168
|
-
@log.
|
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
|
-
|
201
|
-
|
202
|
-
|
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>
|