thin 1.8.0 → 2.0.0.pre
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of thin might be problematic. Click here for more details.
- data/.gitignore +9 -0
- data/CHANGELOG +29 -107
- data/Gemfile +8 -0
- data/README.md +44 -78
- data/Rakefile +28 -18
- data/bin/thin +4 -4
- data/examples/async.ru +21 -0
- data/examples/thin.conf.rb +39 -0
- data/lib/thin.rb +2 -44
- data/lib/thin/async.rb +108 -0
- data/lib/thin/backends/prefork.rb +44 -0
- data/lib/thin/backends/single_process.rb +28 -0
- data/lib/thin/chunked_body.rb +28 -0
- data/lib/thin/configurator.rb +118 -0
- data/lib/thin/connection.rb +246 -172
- data/lib/thin/listener.rb +114 -0
- data/lib/thin/request.rb +94 -76
- data/lib/thin/response.rb +112 -45
- data/lib/thin/runner.rb +134 -197
- data/lib/thin/server.rb +203 -252
- data/lib/thin/system.rb +49 -0
- data/lib/thin/version.rb +11 -26
- data/man/index.txt +3 -0
- data/man/thin-conf.5.ronn +121 -0
- data/man/thin.1.ronn +105 -0
- data/site/.gitignore +2 -0
- data/site/README.md +21 -0
- data/site/Rakefile +20 -0
- data/site/config.ru +4 -0
- data/site/public/images/grid.png +0 -0
- data/site/public/javascripts/dd_belatedpng.js +13 -0
- data/site/public/javascripts/modernizr-1.6.min.js +30 -0
- data/site/public/man/thin-conf.5.html +220 -0
- data/site/public/man/thin.1.html +177 -0
- data/site/site/assets/javascripts/main.coffee +2 -0
- data/site/site/assets/stylesheets/_config.scss +55 -0
- data/site/site/assets/stylesheets/main.scss +24 -0
- data/site/site/helpers.rb +17 -0
- data/site/site/layouts/base.erb +55 -0
- data/site/site/layouts/default.erb +17 -0
- data/site/site/pages/about.md +5 -0
- data/site/site/pages/index.erb +10 -0
- data/site/site/partials/.gitkeep +0 -0
- data/test/fixtures/big.txt +1 -0
- data/test/fixtures/small.txt +1 -0
- data/test/fixtures/thin.conf.rb +15 -0
- data/test/integration/async_test.rb +35 -0
- data/test/integration/big_request_test.rb +30 -0
- data/test/integration/config.ru +57 -0
- data/test/integration/daemonize_test.rb +26 -0
- data/test/integration/env_test.rb +44 -0
- data/test/integration/error_test.rb +37 -0
- data/test/integration/file_sending_test.rb +24 -0
- data/test/integration/keep_alive_test.rb +35 -0
- data/test/integration/robustness_test.rb +37 -0
- data/test/integration/single_process_test.rb +15 -0
- data/test/integration/socket_family_test.rb +38 -0
- data/test/integration/worker_test.rb +22 -0
- data/test/test_helper.rb +195 -0
- data/test/unit/configurator_test.rb +43 -0
- data/test/unit/connection_test.rb +94 -0
- data/test/unit/listener_test.rb +74 -0
- data/test/unit/request_test.rb +74 -0
- data/test/unit/response_test.rb +90 -0
- data/test/unit/server_test.rb +29 -0
- data/test/unit/system_test.rb +17 -0
- data/thin.gemspec +26 -0
- data/v2.todo +21 -0
- metadata +138 -93
- checksums.yaml +0 -7
- data/example/adapter.rb +0 -32
- data/example/async_app.ru +0 -126
- data/example/async_chat.ru +0 -247
- data/example/async_tailer.ru +0 -100
- data/example/config.ru +0 -22
- data/example/monit_sockets +0 -20
- data/example/monit_unixsock +0 -20
- data/example/myapp.rb +0 -1
- data/example/ramaze.ru +0 -12
- data/example/thin.god +0 -80
- data/example/thin_solaris_smf.erb +0 -36
- data/example/thin_solaris_smf.readme.txt +0 -150
- data/example/vlad.rake +0 -72
- data/ext/thin_parser/common.rl +0 -59
- data/ext/thin_parser/ext_help.h +0 -14
- data/ext/thin_parser/extconf.rb +0 -6
- data/ext/thin_parser/parser.c +0 -1447
- data/ext/thin_parser/parser.h +0 -49
- data/ext/thin_parser/parser.rl +0 -152
- data/ext/thin_parser/thin.c +0 -435
- data/lib/rack/adapter/loader.rb +0 -75
- data/lib/rack/adapter/rails.rb +0 -178
- data/lib/rack/handler/thin.rb +0 -38
- data/lib/thin/backends/base.rb +0 -169
- data/lib/thin/backends/swiftiply_client.rb +0 -56
- data/lib/thin/backends/tcp_server.rb +0 -34
- data/lib/thin/backends/unix_server.rb +0 -56
- data/lib/thin/command.rb +0 -53
- data/lib/thin/controllers/cluster.rb +0 -178
- data/lib/thin/controllers/controller.rb +0 -189
- data/lib/thin/controllers/service.rb +0 -76
- data/lib/thin/controllers/service.sh.erb +0 -39
- data/lib/thin/daemonizing.rb +0 -199
- data/lib/thin/headers.rb +0 -40
- data/lib/thin/logging.rb +0 -174
- data/lib/thin/stats.html.erb +0 -216
- data/lib/thin/stats.rb +0 -52
- data/lib/thin/statuses.rb +0 -48
@@ -1,56 +0,0 @@
|
|
1
|
-
module Thin
|
2
|
-
module Backends
|
3
|
-
# Backend to act as a Swiftiply client (http://swiftiply.swiftcore.org).
|
4
|
-
class SwiftiplyClient < Base
|
5
|
-
attr_accessor :key
|
6
|
-
|
7
|
-
attr_accessor :host, :port
|
8
|
-
|
9
|
-
def initialize(host, port, options = {})
|
10
|
-
@host = host
|
11
|
-
@port = port.to_i
|
12
|
-
@key = options[:swiftiply].to_s
|
13
|
-
super()
|
14
|
-
end
|
15
|
-
|
16
|
-
# Connect the server
|
17
|
-
def connect
|
18
|
-
EventMachine.connect(@host, @port, SwiftiplyConnection, &method(:initialize_connection))
|
19
|
-
end
|
20
|
-
|
21
|
-
# Stops the server
|
22
|
-
def disconnect
|
23
|
-
EventMachine.stop
|
24
|
-
end
|
25
|
-
|
26
|
-
def to_s
|
27
|
-
"#{@host}:#{@port} swiftiply"
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
class SwiftiplyConnection < Connection
|
33
|
-
def connection_completed
|
34
|
-
send_data swiftiply_handshake(@backend.key)
|
35
|
-
end
|
36
|
-
|
37
|
-
def persistent?
|
38
|
-
true
|
39
|
-
end
|
40
|
-
|
41
|
-
def unbind
|
42
|
-
super
|
43
|
-
EventMachine.add_timer(rand(2)) { reconnect(@backend.host, @backend.port) } if @backend.running?
|
44
|
-
end
|
45
|
-
|
46
|
-
protected
|
47
|
-
def swiftiply_handshake(key)
|
48
|
-
'swiftclient' << host_ip.collect { |x| sprintf('%02x', x.to_i) }.join << sprintf('%04x', @backend.port) << sprintf('%02x', key.length) << key
|
49
|
-
end
|
50
|
-
|
51
|
-
# For some reason Swiftiply request the current host
|
52
|
-
def host_ip
|
53
|
-
Socket.gethostbyname(@backend.host)[3].unpack('CCCC') rescue [0, 0, 0, 0]
|
54
|
-
end
|
55
|
-
end
|
56
|
-
end
|
@@ -1,34 +0,0 @@
|
|
1
|
-
module Thin
|
2
|
-
module Backends
|
3
|
-
# Backend to act as a TCP socket server.
|
4
|
-
class TcpServer < Base
|
5
|
-
# Address and port on which the server is listening for connections.
|
6
|
-
attr_accessor :host, :port
|
7
|
-
|
8
|
-
def initialize(host, port)
|
9
|
-
@host = host
|
10
|
-
@port = port
|
11
|
-
super()
|
12
|
-
end
|
13
|
-
|
14
|
-
# Connect the server
|
15
|
-
def connect
|
16
|
-
@signature = EventMachine.start_server(@host, @port, Connection, &method(:initialize_connection))
|
17
|
-
binary_name = EventMachine.get_sockname( @signature )
|
18
|
-
port_name = Socket.unpack_sockaddr_in( binary_name )
|
19
|
-
@port = port_name[0]
|
20
|
-
@host = port_name[1]
|
21
|
-
@signature
|
22
|
-
end
|
23
|
-
|
24
|
-
# Stops the server
|
25
|
-
def disconnect
|
26
|
-
EventMachine.stop_server(@signature)
|
27
|
-
end
|
28
|
-
|
29
|
-
def to_s
|
30
|
-
"#{@host}:#{@port}"
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
@@ -1,56 +0,0 @@
|
|
1
|
-
module Thin
|
2
|
-
module Backends
|
3
|
-
# Backend to act as a UNIX domain socket server.
|
4
|
-
class UnixServer < Base
|
5
|
-
# UNIX domain socket on which the server is listening for connections.
|
6
|
-
attr_accessor :socket
|
7
|
-
|
8
|
-
def initialize(socket)
|
9
|
-
raise PlatformNotSupported, 'UNIX domain sockets not available on Windows' if Thin.win?
|
10
|
-
@socket = socket
|
11
|
-
super()
|
12
|
-
end
|
13
|
-
|
14
|
-
# Connect the server
|
15
|
-
def connect
|
16
|
-
at_exit { remove_socket_file } # In case it crashes
|
17
|
-
old_umask = File.umask(0)
|
18
|
-
begin
|
19
|
-
EventMachine.start_unix_domain_server(@socket, UnixConnection, &method(:initialize_connection))
|
20
|
-
# HACK EventMachine.start_unix_domain_server doesn't return the connection signature
|
21
|
-
# so we have to go in the internal stuff to find it.
|
22
|
-
@signature = EventMachine.instance_eval{@acceptors.keys.first}
|
23
|
-
ensure
|
24
|
-
File.umask(old_umask)
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
# Stops the server
|
29
|
-
def disconnect
|
30
|
-
EventMachine.stop_server(@signature)
|
31
|
-
end
|
32
|
-
|
33
|
-
# Free up resources used by the backend.
|
34
|
-
def close
|
35
|
-
remove_socket_file
|
36
|
-
end
|
37
|
-
|
38
|
-
def to_s
|
39
|
-
@socket
|
40
|
-
end
|
41
|
-
|
42
|
-
protected
|
43
|
-
def remove_socket_file
|
44
|
-
File.delete(@socket) if @socket && File.exist?(@socket)
|
45
|
-
end
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
# Connection through a UNIX domain socket.
|
50
|
-
class UnixConnection < Connection
|
51
|
-
protected
|
52
|
-
def socket_address
|
53
|
-
'127.0.0.1' # Unix domain sockets can only be local
|
54
|
-
end
|
55
|
-
end
|
56
|
-
end
|
data/lib/thin/command.rb
DELETED
@@ -1,53 +0,0 @@
|
|
1
|
-
require 'open3'
|
2
|
-
|
3
|
-
module Thin
|
4
|
-
# Run a command through the +thin+ command-line script.
|
5
|
-
class Command
|
6
|
-
include Logging
|
7
|
-
|
8
|
-
class << self
|
9
|
-
# Path to the +thin+ script used to control the servers.
|
10
|
-
# Leave this to default to use the one in the path.
|
11
|
-
attr_accessor :script
|
12
|
-
end
|
13
|
-
|
14
|
-
def initialize(name, options={})
|
15
|
-
@name = name
|
16
|
-
@options = options
|
17
|
-
end
|
18
|
-
|
19
|
-
def self.run(*args)
|
20
|
-
new(*args).run
|
21
|
-
end
|
22
|
-
|
23
|
-
# Send the command to the +thin+ script
|
24
|
-
def run
|
25
|
-
shell_cmd = shellify
|
26
|
-
trace shell_cmd
|
27
|
-
trap('INT') {} # Ignore INT signal to pass CTRL+C to subprocess
|
28
|
-
Open3.popen3(shell_cmd) do |stdin, stdout, stderr|
|
29
|
-
log_info stdout.gets until stdout.eof?
|
30
|
-
log_info stderr.gets until stderr.eof?
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
# Turn into a runnable shell command
|
35
|
-
def shellify
|
36
|
-
shellified_options = @options.inject([]) do |args, (name, value)|
|
37
|
-
option_name = name.to_s.tr("_", "-")
|
38
|
-
case value
|
39
|
-
when NilClass,
|
40
|
-
TrueClass then args << "--#{option_name}"
|
41
|
-
when FalseClass
|
42
|
-
when Array then value.each { |v| args << "--#{option_name}=#{v.inspect}" }
|
43
|
-
else args << "--#{option_name}=#{value.inspect}"
|
44
|
-
end
|
45
|
-
args
|
46
|
-
end
|
47
|
-
|
48
|
-
raise ArgumentError, "Path to thin script can't be found, set Command.script" unless self.class.script
|
49
|
-
|
50
|
-
"#{self.class.script} #{@name} #{shellified_options.compact.join(' ')}"
|
51
|
-
end
|
52
|
-
end
|
53
|
-
end
|
@@ -1,178 +0,0 @@
|
|
1
|
-
require 'socket'
|
2
|
-
|
3
|
-
module Thin
|
4
|
-
# An exception class to handle the event that server didn't start on time
|
5
|
-
class RestartTimeout < RuntimeError; end
|
6
|
-
|
7
|
-
module Controllers
|
8
|
-
# Control a set of servers.
|
9
|
-
# * Generate start and stop commands and run them.
|
10
|
-
# * Inject the port or socket number in the pid and log filenames.
|
11
|
-
# Servers are started throught the +thin+ command-line script.
|
12
|
-
class Cluster < Controller
|
13
|
-
# Cluster only options that should not be passed in the command sent
|
14
|
-
# to the indiviual servers.
|
15
|
-
CLUSTER_OPTIONS = [:servers, :only, :onebyone, :wait]
|
16
|
-
|
17
|
-
# Maximum wait time for the server to be restarted
|
18
|
-
DEFAULT_WAIT_TIME = 30 # seconds
|
19
|
-
|
20
|
-
# Create a new cluster of servers launched using +options+.
|
21
|
-
def initialize(options)
|
22
|
-
super
|
23
|
-
# Cluster can only contain daemonized servers
|
24
|
-
@options.merge!(:daemonize => true)
|
25
|
-
end
|
26
|
-
|
27
|
-
def first_port; @options[:port] end
|
28
|
-
def address; @options[:address] end
|
29
|
-
def socket; @options[:socket] end
|
30
|
-
def pid_file; @options[:pid] end
|
31
|
-
def log_file; @options[:log] end
|
32
|
-
def size; @options[:servers] end
|
33
|
-
def only; @options[:only] end
|
34
|
-
def onebyone; @options[:onebyone] end
|
35
|
-
def wait; @options[:wait] end
|
36
|
-
|
37
|
-
def swiftiply?
|
38
|
-
@options.has_key?(:swiftiply)
|
39
|
-
end
|
40
|
-
|
41
|
-
# Start the servers
|
42
|
-
def start
|
43
|
-
with_each_server { |n| start_server n }
|
44
|
-
end
|
45
|
-
|
46
|
-
# Start a single server
|
47
|
-
def start_server(number)
|
48
|
-
log_info "Starting server on #{server_id(number)} ... "
|
49
|
-
|
50
|
-
run :start, number
|
51
|
-
end
|
52
|
-
|
53
|
-
# Stop the servers
|
54
|
-
def stop
|
55
|
-
with_each_server { |n| stop_server n }
|
56
|
-
end
|
57
|
-
|
58
|
-
# Stop a single server
|
59
|
-
def stop_server(number)
|
60
|
-
log_info "Stopping server on #{server_id(number)} ... "
|
61
|
-
|
62
|
-
run :stop, number
|
63
|
-
end
|
64
|
-
|
65
|
-
# Stop and start the servers.
|
66
|
-
def restart
|
67
|
-
unless onebyone
|
68
|
-
# Let's do a normal restart by defaults
|
69
|
-
stop
|
70
|
-
sleep 0.1 # Let's breath a bit shall we ?
|
71
|
-
start
|
72
|
-
else
|
73
|
-
with_each_server do |n|
|
74
|
-
stop_server(n)
|
75
|
-
sleep 0.1 # Let's breath a bit shall we ?
|
76
|
-
start_server(n)
|
77
|
-
wait_until_server_started(n)
|
78
|
-
end
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
def test_socket(number)
|
83
|
-
if socket
|
84
|
-
UNIXSocket.new(socket_for(number))
|
85
|
-
else
|
86
|
-
TCPSocket.new(address, number)
|
87
|
-
end
|
88
|
-
rescue
|
89
|
-
nil
|
90
|
-
end
|
91
|
-
|
92
|
-
# Make sure the server is running before moving on to the next one.
|
93
|
-
def wait_until_server_started(number)
|
94
|
-
log_info "Waiting for server to start ..."
|
95
|
-
STDOUT.flush # Need this to make sure user got the message
|
96
|
-
|
97
|
-
tries = 0
|
98
|
-
loop do
|
99
|
-
if test_socket = test_socket(number)
|
100
|
-
test_socket.close
|
101
|
-
break
|
102
|
-
elsif tries < wait
|
103
|
-
sleep 1
|
104
|
-
tries += 1
|
105
|
-
else
|
106
|
-
raise RestartTimeout, "The server didn't start in time. Please look at server's log file " +
|
107
|
-
"for more information, or set the value of 'wait' in your config " +
|
108
|
-
"file to be higher (defaults: 30)."
|
109
|
-
end
|
110
|
-
end
|
111
|
-
end
|
112
|
-
|
113
|
-
def server_id(number)
|
114
|
-
if socket
|
115
|
-
socket_for(number)
|
116
|
-
elsif swiftiply?
|
117
|
-
[address, first_port, number].join(':')
|
118
|
-
else
|
119
|
-
[address, number].join(':')
|
120
|
-
end
|
121
|
-
end
|
122
|
-
|
123
|
-
def log_file_for(number)
|
124
|
-
include_server_number log_file, number
|
125
|
-
end
|
126
|
-
|
127
|
-
def pid_file_for(number)
|
128
|
-
include_server_number pid_file, number
|
129
|
-
end
|
130
|
-
|
131
|
-
def socket_for(number)
|
132
|
-
include_server_number socket, number
|
133
|
-
end
|
134
|
-
|
135
|
-
def pid_for(number)
|
136
|
-
File.read(pid_file_for(number)).chomp.to_i
|
137
|
-
end
|
138
|
-
|
139
|
-
private
|
140
|
-
# Send the command to the +thin+ script
|
141
|
-
def run(cmd, number)
|
142
|
-
cmd_options = @options.reject { |option, value| CLUSTER_OPTIONS.include?(option) }
|
143
|
-
cmd_options.merge!(:pid => pid_file_for(number), :log => log_file_for(number))
|
144
|
-
if socket
|
145
|
-
cmd_options.merge!(:socket => socket_for(number))
|
146
|
-
elsif swiftiply?
|
147
|
-
cmd_options.merge!(:port => first_port)
|
148
|
-
else
|
149
|
-
cmd_options.merge!(:port => number)
|
150
|
-
end
|
151
|
-
Command.run(cmd, cmd_options)
|
152
|
-
end
|
153
|
-
|
154
|
-
def with_each_server
|
155
|
-
if only
|
156
|
-
if first_port && only < 80
|
157
|
-
# interpret +only+ as a sequence number
|
158
|
-
yield first_port + only
|
159
|
-
else
|
160
|
-
# interpret +only+ as an absolute port number
|
161
|
-
yield only
|
162
|
-
end
|
163
|
-
elsif socket || swiftiply?
|
164
|
-
size.times { |n| yield n }
|
165
|
-
else
|
166
|
-
size.times { |n| yield first_port + n }
|
167
|
-
end
|
168
|
-
end
|
169
|
-
|
170
|
-
# Add the server port or number in the filename
|
171
|
-
# so each instance get its own file
|
172
|
-
def include_server_number(path, number)
|
173
|
-
ext = File.extname(path)
|
174
|
-
path.gsub(/#{ext}$/, ".#{number}#{ext}")
|
175
|
-
end
|
176
|
-
end
|
177
|
-
end
|
178
|
-
end
|
@@ -1,189 +0,0 @@
|
|
1
|
-
require 'yaml'
|
2
|
-
|
3
|
-
module Thin
|
4
|
-
# Error raised that will abort the process and print not backtrace.
|
5
|
-
class RunnerError < RuntimeError; end
|
6
|
-
|
7
|
-
# Raised when a mandatory option is missing to run a command.
|
8
|
-
class OptionRequired < RunnerError
|
9
|
-
def initialize(option)
|
10
|
-
super("#{option} option required")
|
11
|
-
end
|
12
|
-
end
|
13
|
-
|
14
|
-
# Raised when an option is not valid.
|
15
|
-
class InvalidOption < RunnerError; end
|
16
|
-
|
17
|
-
# Build and control Thin servers.
|
18
|
-
# Hey Controller pattern is not only for web apps yo!
|
19
|
-
module Controllers
|
20
|
-
# Controls one Thin server.
|
21
|
-
# Allow to start, stop, restart and configure a single thin server.
|
22
|
-
class Controller
|
23
|
-
include Logging
|
24
|
-
|
25
|
-
# Command line options passed to the thin script
|
26
|
-
attr_accessor :options
|
27
|
-
|
28
|
-
def initialize(options)
|
29
|
-
@options = options
|
30
|
-
|
31
|
-
if @options[:socket]
|
32
|
-
@options.delete(:address)
|
33
|
-
@options.delete(:port)
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
def start
|
38
|
-
# Constantize backend class
|
39
|
-
@options[:backend] = eval(@options[:backend], TOPLEVEL_BINDING) if @options[:backend]
|
40
|
-
|
41
|
-
server = Server.new(@options[:socket] || @options[:address], # Server detects kind of socket
|
42
|
-
@options[:port], # Port ignored on UNIX socket
|
43
|
-
@options)
|
44
|
-
|
45
|
-
# Set options
|
46
|
-
server.pid_file = @options[:pid]
|
47
|
-
server.log_file = @options[:log]
|
48
|
-
server.timeout = @options[:timeout]
|
49
|
-
server.maximum_connections = @options[:max_conns]
|
50
|
-
server.maximum_persistent_connections = @options[:max_persistent_conns]
|
51
|
-
server.threaded = @options[:threaded]
|
52
|
-
server.no_epoll = @options[:no_epoll] if server.backend.respond_to?(:no_epoll=)
|
53
|
-
server.threadpool_size = @options[:threadpool_size] if server.threaded?
|
54
|
-
|
55
|
-
# ssl support
|
56
|
-
if @options[:ssl]
|
57
|
-
server.ssl = true
|
58
|
-
server.ssl_options = { :private_key_file => @options[:ssl_key_file], :cert_chain_file => @options[:ssl_cert_file], :verify_peer => !@options[:ssl_disable_verify], :ssl_version => @options[:ssl_version], :cipher_list => @options[:ssl_cipher_list]}
|
59
|
-
end
|
60
|
-
|
61
|
-
# Detach the process, after this line the current process returns
|
62
|
-
server.daemonize if @options[:daemonize]
|
63
|
-
|
64
|
-
# +config+ must be called before changing privileges since it might require superuser power.
|
65
|
-
server.config
|
66
|
-
|
67
|
-
server.change_privilege @options[:user], @options[:group] if @options[:user] && @options[:group]
|
68
|
-
|
69
|
-
# If a Rack config file is specified we eval it inside a Rack::Builder block to create
|
70
|
-
# a Rack adapter from it. Or else we guess which adapter to use and load it.
|
71
|
-
if @options[:rackup]
|
72
|
-
server.app = load_rackup_config
|
73
|
-
else
|
74
|
-
server.app = load_adapter
|
75
|
-
end
|
76
|
-
|
77
|
-
# If a prefix is required, wrap in Rack URL mapper
|
78
|
-
server.app = Rack::URLMap.new(@options[:prefix] => server.app) if @options[:prefix]
|
79
|
-
|
80
|
-
# If a stats URL is specified, wrap in Stats adapter
|
81
|
-
server.app = Stats::Adapter.new(server.app, @options[:stats]) if @options[:stats]
|
82
|
-
|
83
|
-
# Register restart procedure which just start another process with same options,
|
84
|
-
# so that's why this is done here.
|
85
|
-
server.on_restart { Command.run(:start, @options) }
|
86
|
-
|
87
|
-
server.start
|
88
|
-
end
|
89
|
-
|
90
|
-
def stop
|
91
|
-
raise OptionRequired, :pid unless @options[:pid]
|
92
|
-
|
93
|
-
tail_log(@options[:log]) do
|
94
|
-
if Server.kill(@options[:pid], @options[:force] ? 0 : (@options[:timeout] || 60))
|
95
|
-
wait_for_file :deletion, @options[:pid]
|
96
|
-
end
|
97
|
-
end
|
98
|
-
end
|
99
|
-
|
100
|
-
def restart
|
101
|
-
raise OptionRequired, :pid unless @options[:pid]
|
102
|
-
|
103
|
-
tail_log(@options[:log]) do
|
104
|
-
if Server.restart(@options[:pid])
|
105
|
-
wait_for_file :creation, @options[:pid]
|
106
|
-
end
|
107
|
-
end
|
108
|
-
end
|
109
|
-
|
110
|
-
def config
|
111
|
-
config_file = @options.delete(:config) || raise(OptionRequired, :config)
|
112
|
-
|
113
|
-
# Stringify keys
|
114
|
-
@options.keys.each { |o| @options[o.to_s] = @options.delete(o) }
|
115
|
-
|
116
|
-
File.open(config_file, 'w') { |f| f << @options.to_yaml }
|
117
|
-
log_info "Wrote configuration to #{config_file}"
|
118
|
-
end
|
119
|
-
|
120
|
-
protected
|
121
|
-
# Wait for a pid file to either be created or deleted.
|
122
|
-
def wait_for_file(state, file)
|
123
|
-
Timeout.timeout(@options[:timeout] || 30) do
|
124
|
-
case state
|
125
|
-
when :creation then sleep 0.1 until File.exist?(file)
|
126
|
-
when :deletion then sleep 0.1 while File.exist?(file)
|
127
|
-
end
|
128
|
-
end
|
129
|
-
end
|
130
|
-
|
131
|
-
# Tail the log file of server +number+ during the execution of the block.
|
132
|
-
def tail_log(log_file)
|
133
|
-
if log_file
|
134
|
-
tail_thread = tail(log_file)
|
135
|
-
yield
|
136
|
-
tail_thread.kill
|
137
|
-
else
|
138
|
-
yield
|
139
|
-
end
|
140
|
-
end
|
141
|
-
|
142
|
-
# Acts like GNU tail command. Taken from Rails.
|
143
|
-
def tail(file)
|
144
|
-
cursor = File.exist?(file) ? File.size(file) : 0
|
145
|
-
last_checked = Time.now
|
146
|
-
tail_thread = Thread.new do
|
147
|
-
Thread.pass until File.exist?(file)
|
148
|
-
File.open(file, 'r') do |f|
|
149
|
-
loop do
|
150
|
-
f.seek cursor
|
151
|
-
if f.mtime > last_checked
|
152
|
-
last_checked = f.mtime
|
153
|
-
contents = f.read
|
154
|
-
cursor += contents.length
|
155
|
-
print contents
|
156
|
-
STDOUT.flush
|
157
|
-
end
|
158
|
-
sleep 0.1
|
159
|
-
end
|
160
|
-
end
|
161
|
-
end
|
162
|
-
sleep 1 if File.exist?(file) # HACK Give the thread a little time to open the file
|
163
|
-
tail_thread
|
164
|
-
end
|
165
|
-
|
166
|
-
private
|
167
|
-
def load_adapter
|
168
|
-
adapter = @options[:adapter] || Rack::Adapter.guess(@options[:chdir])
|
169
|
-
log_info "Using #{adapter} adapter"
|
170
|
-
Rack::Adapter.for(adapter, @options)
|
171
|
-
rescue Rack::AdapterNotFound => e
|
172
|
-
raise InvalidOption, e.message
|
173
|
-
end
|
174
|
-
|
175
|
-
def load_rackup_config
|
176
|
-
ENV['RACK_ENV'] = @options[:environment]
|
177
|
-
case @options[:rackup]
|
178
|
-
when /\.rb$/
|
179
|
-
Kernel.load(@options[:rackup])
|
180
|
-
Object.const_get(File.basename(@options[:rackup], '.rb').capitalize.to_sym)
|
181
|
-
when /\.ru$/
|
182
|
-
Rack::Adapter.load(@options[:rackup])
|
183
|
-
else
|
184
|
-
raise "Invalid rackup file. please specify either a .ru or .rb file"
|
185
|
-
end
|
186
|
-
end
|
187
|
-
end
|
188
|
-
end
|
189
|
-
end
|