nitro 0.23.0 → 0.24.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +350 -0
- data/INSTALL +2 -2
- data/ProjectInfo +61 -0
- data/README +5 -4
- data/Rakefile +5 -4
- data/bin/nitrogen +3 -1
- data/doc/AUTHORS +27 -3
- data/doc/RELEASES +193 -0
- data/doc/lhttpd.txt +4 -0
- data/lib/nitro.rb +1 -1
- data/lib/nitro/adapter/cgi.rb +6 -321
- data/lib/nitro/adapter/fastcgi.rb +2 -14
- data/lib/nitro/adapter/scgi.rb +237 -71
- data/lib/nitro/adapter/webrick.rb +25 -7
- data/lib/nitro/caching.rb +1 -0
- data/lib/nitro/cgi.rb +296 -0
- data/lib/nitro/{cookie.rb → cgi/cookie.rb} +0 -0
- data/lib/nitro/cgi/http.rb +62 -0
- data/lib/nitro/{request.rb → cgi/request.rb} +4 -1
- data/lib/nitro/{response.rb → cgi/response.rb} +0 -0
- data/lib/nitro/cgi/stream.rb +43 -0
- data/lib/nitro/cgi/utils.rb +38 -0
- data/lib/nitro/compiler.rb +23 -11
- data/lib/nitro/compiler/css.rb +8 -0
- data/lib/nitro/compiler/morphing.rb +66 -0
- data/lib/nitro/context.rb +21 -30
- data/lib/nitro/controller.rb +23 -100
- data/lib/nitro/dispatcher.rb +18 -8
- data/lib/nitro/element.rb +6 -2
- data/lib/nitro/flash.rb +2 -2
- data/lib/nitro/mixin/buffer.rb +2 -2
- data/lib/nitro/mixin/form.rb +204 -93
- data/lib/nitro/mixin/javascript.rb +170 -11
- data/lib/nitro/mixin/markup.rb +1 -0
- data/lib/nitro/mixin/pager.rb +7 -4
- data/lib/nitro/mixin/rss.rb +2 -0
- data/lib/nitro/mixin/table.rb +23 -6
- data/lib/nitro/mixin/xhtml.rb +2 -2
- data/lib/nitro/render.rb +19 -5
- data/lib/nitro/scaffold.rb +12 -6
- data/lib/nitro/server.rb +4 -6
- data/lib/nitro/server/runner.rb +2 -2
- data/lib/nitro/session.rb +8 -1
- data/lib/nitro/session/file.rb +40 -0
- data/lib/part/admin.rb +2 -0
- data/lib/part/admin/controller.rb +7 -3
- data/lib/part/admin/skin.rb +8 -1
- data/lib/part/admin/template/index.xhtml +39 -1
- data/proto/public/error.xhtml +5 -3
- data/proto/public/js/behaviour.js +254 -254
- data/proto/public/js/controls.js +427 -165
- data/proto/public/js/dragdrop.js +255 -276
- data/proto/public/js/effects.js +476 -277
- data/proto/public/js/prototype.js +561 -127
- data/proto/public/js/scaffold.js +74 -0
- data/proto/public/js/scriptaculous.js +44 -0
- data/proto/public/js/util.js +548 -0
- data/proto/public/scaffold/list.xhtml +4 -1
- data/proto/scgi.rb +333 -0
- data/script/scgi_ctl +221 -0
- data/script/scgi_service +120 -0
- data/test/nitro/adapter/raw_post1.bin +0 -0
- data/test/nitro/{tc_cookie.rb → cgi/tc_cookie.rb} +1 -1
- data/test/nitro/{tc_request.rb → cgi/tc_request.rb} +1 -1
- data/test/nitro/mixin/tc_xhtml.rb +1 -1
- data/test/nitro/{adapter/tc_cgi.rb → tc_cgi.rb} +12 -12
- data/test/nitro/tc_controller.rb +9 -5
- metadata +159 -169
- data/benchmark/bench.rb +0 -5
- data/benchmark/simple-webrick-n-200.txt +0 -44
- data/benchmark/static-webrick-n-200.txt +0 -43
- data/benchmark/tiny-lhttpd-n-200-c-5.txt +0 -43
- data/benchmark/tiny-webrick-n-200-c-5.txt +0 -44
- data/benchmark/tiny-webrick-n-200.txt +0 -44
- data/benchmark/tiny2-webrick-n-200.txt +0 -44
- data/examples/README +0 -7
@@ -1,9 +1,12 @@
|
|
1
1
|
<SystemPage name="List of %list_name%">
|
2
2
|
<h2><a href="#@base/new_%name%">New %name%</a></h2>
|
3
|
+
<form action="search">
|
4
|
+
Search %list_name%: <input type="text" name="q" /> <input type="submit" value="Search" /> (not implemented yet)
|
5
|
+
</form>
|
3
6
|
<table>
|
4
7
|
<?r for obj in @%list_name% ?>
|
5
8
|
<tr>
|
6
|
-
<td width="100%">#{obj.to_s}</td>
|
9
|
+
<td width="100%"><a href="#@base/edit_%name%/#{obj.oid}">#{obj.to_s}</a></td>
|
7
10
|
<?r if obj.respond_to?(:update_time) ?>
|
8
11
|
<td nowrap="1">#{obj.update_time.stamp(:db)}</td>
|
9
12
|
<?r end ?>
|
data/proto/scgi.rb
ADDED
@@ -0,0 +1,333 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'stringio'
|
4
|
+
require 'yaml'
|
5
|
+
require 'digest/sha1'
|
6
|
+
require 'logger'
|
7
|
+
require 'fileutils'
|
8
|
+
require 'socket'
|
9
|
+
require 'cgi'
|
10
|
+
require 'rubygems'
|
11
|
+
require 'cmdparse'
|
12
|
+
require 'monitor'
|
13
|
+
|
14
|
+
def log(msg)
|
15
|
+
$stderr.print msg,"\n"
|
16
|
+
end
|
17
|
+
|
18
|
+
def error(msg, exc=nil)
|
19
|
+
if exc
|
20
|
+
$stderr.print "ERROR: #{msg}: #{exc}\n"
|
21
|
+
$stderr.puts exc.backtrace
|
22
|
+
else
|
23
|
+
$stderr.print "ERROR: #{msg}\n"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
# Modifies CGI so that we can use it.
|
29
|
+
class SCGIFixed < ::CGI
|
30
|
+
public :env_table
|
31
|
+
|
32
|
+
def initialize(params, data, out, *args)
|
33
|
+
@env_table = params
|
34
|
+
@args = *args
|
35
|
+
@input = StringIO.new(data)
|
36
|
+
@out = out
|
37
|
+
super(*args)
|
38
|
+
end
|
39
|
+
def args
|
40
|
+
@args
|
41
|
+
end
|
42
|
+
def env_table
|
43
|
+
@env_table
|
44
|
+
end
|
45
|
+
def stdinput
|
46
|
+
@input
|
47
|
+
end
|
48
|
+
def stdoutput
|
49
|
+
@out
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
class SCGIProcessor < Monitor
|
55
|
+
|
56
|
+
def initialize(settings)
|
57
|
+
@env = settings[:env] || "development"
|
58
|
+
@debug = settings[:debug] || false
|
59
|
+
@host = settings[:host] || "127.0.0.1"
|
60
|
+
@port = settings[:port] || "9999"
|
61
|
+
@children = settings[:children] || 1
|
62
|
+
@pid_file = settings[:pid_file] || "children.yaml"
|
63
|
+
@status_dir = settings[:status_dir] || "/tmp"
|
64
|
+
@log_file = settings[:logfile] || "log/scgi.log"
|
65
|
+
@maxconns = settings[:maxconns]
|
66
|
+
@busy_msg = settings[:busy_msg] || "BUSY"
|
67
|
+
@settings = settings
|
68
|
+
@started = Time.now
|
69
|
+
@conns = 0
|
70
|
+
@total_conns = 0
|
71
|
+
@errors = 0
|
72
|
+
|
73
|
+
if @maxconns
|
74
|
+
@maxconns = @maxconns.to_i
|
75
|
+
else
|
76
|
+
@maxconns = 2**30-1
|
77
|
+
end
|
78
|
+
|
79
|
+
if settings[:conns_second]
|
80
|
+
@throttle_sleep = 1.0/settings[:conns_second].to_i
|
81
|
+
end
|
82
|
+
|
83
|
+
super()
|
84
|
+
end
|
85
|
+
|
86
|
+
def run
|
87
|
+
ENV['NITRO_ENV'] = @env
|
88
|
+
|
89
|
+
require 'nitro'
|
90
|
+
server = TCPServer.new(@host, @port)
|
91
|
+
|
92
|
+
if @debug
|
93
|
+
log("Listening for connections on #@host:#@port")
|
94
|
+
listen(server)
|
95
|
+
else
|
96
|
+
childpids = []
|
97
|
+
@children.to_i.times do
|
98
|
+
# fork each child listening to the same port. very simple yet effective way to spread the load
|
99
|
+
# to multiple processes without using threads and still using high performance libevent
|
100
|
+
begin
|
101
|
+
pid = fork do
|
102
|
+
$stderr = open(@log_file,"w")
|
103
|
+
$stderr.sync = false
|
104
|
+
listen(server)
|
105
|
+
end
|
106
|
+
childpids << pid
|
107
|
+
Process.detach(pid)
|
108
|
+
rescue Object
|
109
|
+
error("Could not fork child processes. Your system might not support fork. Use -D instead.", $!)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# tell the user what the sha1 is so they can check for modification later
|
114
|
+
log("#@pid_file will have SHA1 #{Digest::SHA1.hexdigest(YAML.dump(childpids))}")
|
115
|
+
log("Record this somewhere so you know if it was modified later by someone else.")
|
116
|
+
# all children forked and the pids are now ready to write to the pid file
|
117
|
+
open(@pid_file,"w") { |f| f.write(YAML.dump(childpids)) }
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
|
122
|
+
def listen(socket)
|
123
|
+
thread = Thread.new do
|
124
|
+
while true
|
125
|
+
handle_client(socket.accept)
|
126
|
+
sleep @throttle_sleep if @throttle_sleep
|
127
|
+
|
128
|
+
@total_conns += 1
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
begin
|
133
|
+
thread.join
|
134
|
+
rescue Interrupt
|
135
|
+
log("Shutting down from SIGINT.")
|
136
|
+
rescue Object
|
137
|
+
error("while listening for connections on #@host:#@port", $!)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
|
142
|
+
def handle_client(socket)
|
143
|
+
Thread.new do
|
144
|
+
begin
|
145
|
+
synchronize { @conns += 1}
|
146
|
+
|
147
|
+
len = ""
|
148
|
+
# we only read 10 bytes of the length. any request longer than this is invalid
|
149
|
+
while len.length <= 10
|
150
|
+
c = socket.read(1)
|
151
|
+
if c == ':'
|
152
|
+
# found the terminal, len now has a length in it so read the payload
|
153
|
+
break
|
154
|
+
else
|
155
|
+
len << c
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
# we should now either have a payload length to get
|
160
|
+
payload = socket.read(len.to_i)
|
161
|
+
if (c = socket.read(1)) != ','
|
162
|
+
error("Malformed request, does not end with ','")
|
163
|
+
else
|
164
|
+
read_header(socket, payload, @conns)
|
165
|
+
end
|
166
|
+
rescue IOError
|
167
|
+
error("received IOError #$! when handling client. Your web server doesn't like me.")
|
168
|
+
rescue Object
|
169
|
+
@errors += 1
|
170
|
+
error("after accepting client #@host:#@port -- #{$!.class}", $!)
|
171
|
+
ensure
|
172
|
+
synchronize { @conns -= 1}
|
173
|
+
socket.close if not socket.closed?
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
end
|
178
|
+
|
179
|
+
|
180
|
+
def read_header(socket, payload, conns)
|
181
|
+
return if socket.closed?
|
182
|
+
request = split_body(payload)
|
183
|
+
if request and request["CONTENT_LENGTH"]
|
184
|
+
length = request["CONTENT_LENGTH"].to_i
|
185
|
+
if length > 0
|
186
|
+
body = socket.read(length)
|
187
|
+
else
|
188
|
+
body = ""
|
189
|
+
end
|
190
|
+
|
191
|
+
if @conns > @maxconns
|
192
|
+
socket.write("Content-type: text/plain\r\n\r\n")
|
193
|
+
socket.write(@busy_msg)
|
194
|
+
else
|
195
|
+
process_request(request, body, socket)
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
|
201
|
+
def process_request(request, body, socket)
|
202
|
+
return if socket.closed?
|
203
|
+
cgi = SCGIFixed.new(request, body, socket)
|
204
|
+
begin
|
205
|
+
synchronize do
|
206
|
+
# unfortuneatly, the dependencies.rb file is not thread safe and will throw exceptions
|
207
|
+
# claiming that Dispatcher is not defined, or that other classes are missing. We have
|
208
|
+
# to sync the dispatch call to get around this.
|
209
|
+
Dispatcher.dispatch(cgi, ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS, cgi.stdoutput)
|
210
|
+
end
|
211
|
+
rescue IOError
|
212
|
+
error("received IOError #$! when handling client. Your web server doesn't like me.")
|
213
|
+
rescue Object => nitro_error
|
214
|
+
error("calling Dispatcher.dispatch", nitro_error)
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
|
219
|
+
def split_body(data)
|
220
|
+
result = {}
|
221
|
+
el = data.split("\0")
|
222
|
+
i = 0
|
223
|
+
len = el.length
|
224
|
+
while i < len
|
225
|
+
result[el[i]] = el[i+1]
|
226
|
+
i += 1
|
227
|
+
end
|
228
|
+
|
229
|
+
return result
|
230
|
+
end
|
231
|
+
|
232
|
+
def status
|
233
|
+
pid = Process.pid
|
234
|
+
open("#@status_dir/scgi_nitro_status.#{pid}","w") do |f|
|
235
|
+
status = {
|
236
|
+
'time' => Time.now, 'pid' => pid, 'settings' => @settings,
|
237
|
+
'env' => @env, 'status_dir' => @status_dir, 'started' => @started,
|
238
|
+
'max_conns' => @maxconns, 'total_conns' => @total_conns,
|
239
|
+
'conns' => @conns, 'errors' => @errors, 'systimes' => Process.times
|
240
|
+
}
|
241
|
+
f.write(YAML.dump(status))
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
|
247
|
+
def signal_children(pidfile, signal)
|
248
|
+
if not File.exists? pidfile
|
249
|
+
log("No #{pidfile} as specified. Probably nothing running or wrong path.")
|
250
|
+
exit 1
|
251
|
+
end
|
252
|
+
|
253
|
+
childpids = YAML.load_file(pidfile)
|
254
|
+
childpids.each do |pid|
|
255
|
+
begin
|
256
|
+
log("Signaling pid #{pid}")
|
257
|
+
Process.kill(signal, pid)
|
258
|
+
rescue Object
|
259
|
+
log("Couldn't send #{signal} signal to #{pid} pid.")
|
260
|
+
end
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
|
265
|
+
def make_command(parent, name, desc, options)
|
266
|
+
cmd = CmdParse::Command.new(name, false )
|
267
|
+
cmd.short_desc = desc
|
268
|
+
settings = {}
|
269
|
+
cmd.options = CmdParse::OptionParserWrapper.new do |opt|
|
270
|
+
options.each do |short, long, info, symbol|
|
271
|
+
opt.on(short, long, info) {|val| settings[symbol] = val}
|
272
|
+
end
|
273
|
+
end
|
274
|
+
cmd.set_execution_block do |args|
|
275
|
+
yield(settings, args)
|
276
|
+
end
|
277
|
+
parent.add_command(cmd)
|
278
|
+
end
|
279
|
+
|
280
|
+
|
281
|
+
cmd = CmdParse::CommandParser.new( true )
|
282
|
+
cmd.program_name = 'scgi'
|
283
|
+
cmd.program_version = [0, 2, 1]
|
284
|
+
cmd.options = CmdParse::OptionParserWrapper.new do |opt|
|
285
|
+
opt.separator "Global options:"
|
286
|
+
opt.on("--verbose", "Be verbose when outputting info") {|t| $verbose = true }
|
287
|
+
end
|
288
|
+
|
289
|
+
cmd.add_command( CmdParse::HelpCommand.new )
|
290
|
+
cmd.add_command( CmdParse::VersionCommand.new )
|
291
|
+
|
292
|
+
make_command(cmd, 'start', "Start Nitro Application",
|
293
|
+
[['-e','--env STRING','Nitro environment', :env],
|
294
|
+
['-D','--[no-]debug', 'Do not fork children, stay in foreground.', :debug],
|
295
|
+
['-h','--host STRING', 'IP address to bind as server', :host],
|
296
|
+
['-p','--port NUMBER', 'Port to bind to', :port],
|
297
|
+
['-c','--children NUMBER', 'Number of children to start (not win32)', :children],
|
298
|
+
['-f','--pid-file PATH', 'Where to read the list of running children', :pid_file],
|
299
|
+
['-l','--log-file PATH', 'Use a different log from from log/scgi.log', :logfile],
|
300
|
+
['-t','--throttle NUMBER', 'Max conn/second to allow.', :conns_second],
|
301
|
+
['-m','--max-conns NUMBER', 'Max simultaneous connections before the busy message', :maxconns],
|
302
|
+
['-b','--busy-msg', 'Busy message given to clients over the max connections ("busy")', :busy_msg],
|
303
|
+
['-s','--status-dir PATH', 'Where to put the status files', :status_dir]]) do |settings, args|
|
304
|
+
scgi = SCGIProcessor.new(settings)
|
305
|
+
begin
|
306
|
+
trap("HUP") { scgi.status }
|
307
|
+
rescue Object
|
308
|
+
error("Could not setup a SIGHUP handler. You won't be able to get status.")
|
309
|
+
end
|
310
|
+
|
311
|
+
scgi.run
|
312
|
+
end
|
313
|
+
|
314
|
+
|
315
|
+
make_command(cmd, 'status', "Get status from all running children",
|
316
|
+
[['-s','--status-dir PATH', 'Where to put the status files', :status_dir],
|
317
|
+
['-f','--pid-file PATH', 'Where to read the list of running children', :pid_file]]) do |settings, args|
|
318
|
+
signal_children(settings[:pid_file] || 'children.yaml', "HUP")
|
319
|
+
log("Status files for each child should show up in the configured status directory (/tmp by default).")
|
320
|
+
end
|
321
|
+
|
322
|
+
make_command(cmd, 'stop', "Stop all running children",
|
323
|
+
[['-s','--sig SIGNAL', 'Where to put the status files', :signal],
|
324
|
+
['-n','--[no-]delete', 'Keep the children.yaml file rather than delete', :nodelete],
|
325
|
+
['-f','--pid-file PATH', 'Where to read the list of running children', :pid_file]]) do |settings, args|
|
326
|
+
pid_file = settings[:pid_file] || "children.yaml"
|
327
|
+
signal_children(pid_file, settings[:signal] || "INT")
|
328
|
+
if not settings[:nodelete] and File.exist?(pid_file)
|
329
|
+
File.unlink(pid_file)
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
cmd.parse
|
data/script/scgi_ctl
ADDED
@@ -0,0 +1,221 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'drb'
|
3
|
+
require 'rubygems'
|
4
|
+
require 'cmdparse'
|
5
|
+
require 'yaml'
|
6
|
+
require 'highline'
|
7
|
+
|
8
|
+
UI = HighLine.new
|
9
|
+
DEFAULT_CONFIG = "conf/scgi.yaml"
|
10
|
+
|
11
|
+
def safe(error)
|
12
|
+
begin
|
13
|
+
yield
|
14
|
+
rescue Object
|
15
|
+
STDERR.puts("ERROR: #{error}: #$!")
|
16
|
+
exit 1
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def load_config(file)
|
21
|
+
safe("Could not load config") do
|
22
|
+
return YAML.load_file(file)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def connect(url)
|
27
|
+
conn = nil
|
28
|
+
safe("Can't connect to #{url}") do
|
29
|
+
conn = DRbObject.new(nil, url)
|
30
|
+
end
|
31
|
+
|
32
|
+
safe("Failed communicating with #{url}") do
|
33
|
+
yield conn
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def deduce_url(settings)
|
38
|
+
settings[:control_url] || load_config(settings[:config] || DEFAULT_CONFIG)[:control_url]
|
39
|
+
end
|
40
|
+
|
41
|
+
def defaults(settings)
|
42
|
+
defaults = nil
|
43
|
+
if settings[:merge]
|
44
|
+
UI.say("Merging with previous settings.")
|
45
|
+
defaults = load_config(settings[:config] || DEFAULT_CONFIG)
|
46
|
+
else
|
47
|
+
defaults = {
|
48
|
+
:env => "production",
|
49
|
+
:host => "127.0.0.1",
|
50
|
+
:port => 9999,
|
51
|
+
:logfile => "log/scgi.log",
|
52
|
+
:config => DEFAULT_CONFIG }
|
53
|
+
end
|
54
|
+
|
55
|
+
settings = defaults.merge(settings)
|
56
|
+
# fix up the stuff that's not quite right yet
|
57
|
+
settings[:control_url] = "druby://127.0.0.1:#{settings[:port]-1000}"
|
58
|
+
settings[:port] = settings[:port].to_i
|
59
|
+
settings[:throttle] = settings[:throttle].to_i if settings[:throttle]
|
60
|
+
settings[:maxconns] = settings[:maxconns].to_i if settings[:maxconns]
|
61
|
+
|
62
|
+
return settings
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
def configure(settings)
|
67
|
+
settings = defaults(settings)
|
68
|
+
|
69
|
+
pass = settings[:password] || UI.ask("What password do you want? ")
|
70
|
+
salting = ('a' .. 'z').to_a + ('A' .. 'Z').to_a + ('0' .. '9').to_a
|
71
|
+
settings[:password] = pass.crypt(salting[rand(salting.length)] + salting[rand(salting.length)])
|
72
|
+
|
73
|
+
# great, they are not idiots. Well, they can read --help at least.
|
74
|
+
safe("Could not write config") do
|
75
|
+
open(settings[:config],"w") {|f| f.write(YAML.dump(settings)) }
|
76
|
+
UI.say("Configuration settings written to #{settings[:config]}")
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def password
|
81
|
+
if not $password
|
82
|
+
$password = UI.ask("Password: ")
|
83
|
+
end
|
84
|
+
$password
|
85
|
+
end
|
86
|
+
|
87
|
+
def start(cmd, config)
|
88
|
+
fork do
|
89
|
+
exec cmd, config
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def status(url)
|
94
|
+
connect(url) do |conn|
|
95
|
+
s = conn.status(password)
|
96
|
+
times = s[:systimes]
|
97
|
+
|
98
|
+
puts <<-END
|
99
|
+
#{UI.color("Status as of #{s[:time]}:",:green,:bold)}
|
100
|
+
PID: #{s[:pid]}\tStarted: #{s[:started]}\tEnvironment: #{s[:env]}
|
101
|
+
Connected Requests: #{s[:conns]}
|
102
|
+
Conns/Second: #{s[:conns_second] || "Not Set"}
|
103
|
+
Max Simultaneous Conns: #{s[:max_conns]}
|
104
|
+
Shutdown Started: #{s[:shutdown]}
|
105
|
+
Processing Time: #{times.utime} #{times.stime} #{times.cutime} #{times.cstime}
|
106
|
+
Current Settings:
|
107
|
+
#{s[:settings].to_yaml.gsub(/:([a-z])/, ' \1')}
|
108
|
+
END
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def reconfigure(url)
|
113
|
+
connect(url) do |conn|
|
114
|
+
conn.reconfigure(password)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def stop(url, force)
|
119
|
+
connect(url) do |conn|
|
120
|
+
conn.shutdown(password, force)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def restart(url, force)
|
125
|
+
connect(url) do |conn|
|
126
|
+
conn.restart(password, force)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def monitor(url)
|
131
|
+
connect(url) do |conn|
|
132
|
+
while true
|
133
|
+
sleep 3
|
134
|
+
puts "\e[2J"
|
135
|
+
status(url)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
|
141
|
+
def make_command(parent, name, desc, options)
|
142
|
+
cmd = CmdParse::Command.new(name, false )
|
143
|
+
cmd.short_desc = desc
|
144
|
+
settings = {}
|
145
|
+
cmd.options = CmdParse::OptionParserWrapper.new do |opt|
|
146
|
+
options.each do |short, long, info, symbol|
|
147
|
+
opt.on(short, long, info) {|val| settings[symbol] = val}
|
148
|
+
end
|
149
|
+
end
|
150
|
+
cmd.set_execution_block do |args|
|
151
|
+
yield(settings, args)
|
152
|
+
end
|
153
|
+
parent.add_command(cmd)
|
154
|
+
end
|
155
|
+
|
156
|
+
|
157
|
+
cmd = CmdParse::CommandParser.new( true )
|
158
|
+
cmd.program_name = "scgi_nitro"
|
159
|
+
cmd.program_version = [0, 4, 0]
|
160
|
+
cmd.options = CmdParse::OptionParserWrapper.new do |opt|
|
161
|
+
opt.separator "Global options:"
|
162
|
+
opt.on("--verbose", "Be verbose when outputting info") {|t| $verbose = true }
|
163
|
+
end
|
164
|
+
|
165
|
+
cmd.add_command( CmdParse::HelpCommand.new )
|
166
|
+
cmd.add_command( CmdParse::VersionCommand.new )
|
167
|
+
|
168
|
+
make_command(cmd, 'config', "Configure the SCGI servers",
|
169
|
+
[['-e','--env STRING','Nitro environment', :env],
|
170
|
+
['-h','--host STRING', 'IP address to bind as server', :host],
|
171
|
+
['-p','--port NUMBER', 'Port to bind to (starts at 9999)', :port],
|
172
|
+
['-u','--control-url URL', 'DRuby URL to run control on (same as SCGI -1000)', :control_url],
|
173
|
+
['-l','--log-file PATH', 'Use a different log from from log/scgi.log', :logfile],
|
174
|
+
['-t','--throttle NUMBER', 'Max conn/second to allow.', :conns_second],
|
175
|
+
['-m','--max-conns NUMBER', 'Max simultaneous connections before the busy message', :maxconns],
|
176
|
+
['-P','--moron-mode PASSWORD', 'You are an idiot and you want your password on the command line', :password],
|
177
|
+
['-M','--merge', 'Merge new settings with previous rather than defaults', :merge],
|
178
|
+
['-c','--config PATH', 'Config file to use (#{DEFAULT_CONFIG})', :config]]) do |settings, args|
|
179
|
+
configure(settings)
|
180
|
+
end
|
181
|
+
|
182
|
+
make_command(cmd, 'start', "Start the application",
|
183
|
+
[['-u','--control-url URL', 'DRuby URL to run control on (same as SCGI -1000)', :control_url],
|
184
|
+
['-c','--config PATH', 'Config file to use (#{DEFAULT_CONFIG})', :config]]) do |settings, args|
|
185
|
+
cmd = File.dirname(__FILE__) + "/scgi_service"
|
186
|
+
start(cmd, settings[:config] || DEFAULT_CONFIG)
|
187
|
+
end
|
188
|
+
|
189
|
+
make_command(cmd, 'reconfig', "Reconfigure the SCGI servers with a new config",
|
190
|
+
[['-u','--control-url URL', 'DRuby URL to run control on (same as SCGI -1000)', :control_url],
|
191
|
+
['-c','--config PATH', 'Config file to use (#{DEFAULT_CONFIG})', :config]]) do |settings, args|
|
192
|
+
reconfigure(deduce_url(settings))
|
193
|
+
end
|
194
|
+
|
195
|
+
make_command(cmd, 'status', "Get status",
|
196
|
+
[['-u','--control-url URL', 'DRuby URL to run control on (same as SCGI -1000)', :control_url],
|
197
|
+
['-c','--config PATH', 'Config file to use (#{DEFAULT_CONFIG})', :config]]) do |settings, args|
|
198
|
+
status(deduce_url(settings))
|
199
|
+
end
|
200
|
+
|
201
|
+
make_command(cmd, 'stop', "Stop the application",
|
202
|
+
[['-u','--control-url URL', 'DRuby URL to run control on (same as SCGI -1000)', :control_url],
|
203
|
+
['-c','--config PATH', 'Config file to use (#{DEFAULT_CONFIG})', :config],
|
204
|
+
['-f','--force', 'Forced shutdown rather than graceful (default graceful)', :force]]) do |settings, args|
|
205
|
+
stop(deduce_url(settings), settings[:force] || false)
|
206
|
+
end
|
207
|
+
|
208
|
+
make_command(cmd, 'restart', "Restart the application",
|
209
|
+
[['-u','--control-url URL', 'DRuby URL to run control on (same as SCGI -1000)', :control_url],
|
210
|
+
['-c','--config PATH', 'Config file to use (#{DEFAULT_CONFIG})', :config],
|
211
|
+
['-f','--force', 'Forced shutdown rather than graceful (default graceful)', :force]]) do |settings, args|
|
212
|
+
restart(deduce_url(settings), settings[:force] || false)
|
213
|
+
end
|
214
|
+
|
215
|
+
make_command(cmd, 'monitor', "Monitor the application",
|
216
|
+
[['-u','--control-url URL', 'DRuby URL to run control on (same as SCGI -1000)', :control_url],
|
217
|
+
['-c','--config PATH', 'Config file to use (#{DEFAULT_CONFIG})', :config]]) do |settings, args|
|
218
|
+
monitor(deduce_url(settings))
|
219
|
+
end
|
220
|
+
|
221
|
+
cmd.parse
|