gross 1.7.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG +410 -0
- data/README.md +102 -0
- data/Rakefile +25 -0
- data/bin/thin +6 -0
- data/example/adapter.rb +32 -0
- data/example/async_app.ru +126 -0
- data/example/async_chat.ru +247 -0
- data/example/async_tailer.ru +100 -0
- data/example/config.ru +22 -0
- data/example/monit_sockets +20 -0
- data/example/monit_unixsock +20 -0
- data/example/myapp.rb +1 -0
- data/example/ramaze.ru +12 -0
- data/example/thin.god +80 -0
- data/example/thin_solaris_smf.erb +36 -0
- data/example/thin_solaris_smf.readme.txt +150 -0
- data/example/vlad.rake +72 -0
- data/ext/thin_parser/common.rl +59 -0
- data/ext/thin_parser/ext_help.h +14 -0
- data/ext/thin_parser/extconf.rb +6 -0
- data/ext/thin_parser/parser.c +1447 -0
- data/ext/thin_parser/parser.h +49 -0
- data/ext/thin_parser/parser.rl +152 -0
- data/ext/thin_parser/thin.c +435 -0
- data/lib/rack/adapter/loader.rb +75 -0
- data/lib/rack/adapter/rails.rb +178 -0
- data/lib/thin.rb +45 -0
- data/lib/thin/backends/base.rb +167 -0
- data/lib/thin/backends/swiftiply_client.rb +56 -0
- data/lib/thin/backends/tcp_server.rb +34 -0
- data/lib/thin/backends/unix_server.rb +56 -0
- data/lib/thin/command.rb +53 -0
- data/lib/thin/connection.rb +215 -0
- data/lib/thin/controllers/cluster.rb +178 -0
- data/lib/thin/controllers/controller.rb +189 -0
- data/lib/thin/controllers/service.rb +76 -0
- data/lib/thin/controllers/service.sh.erb +39 -0
- data/lib/thin/daemonizing.rb +180 -0
- data/lib/thin/headers.rb +40 -0
- data/lib/thin/logging.rb +174 -0
- data/lib/thin/request.rb +162 -0
- data/lib/thin/response.rb +117 -0
- data/lib/thin/runner.rb +238 -0
- data/lib/thin/server.rb +290 -0
- data/lib/thin/stats.html.erb +216 -0
- data/lib/thin/stats.rb +52 -0
- data/lib/thin/statuses.rb +44 -0
- data/lib/thin/version.rb +32 -0
- metadata +156 -0
data/lib/thin/runner.rb
ADDED
@@ -0,0 +1,238 @@
|
|
1
|
+
require 'logger'
|
2
|
+
require 'optparse'
|
3
|
+
require 'yaml'
|
4
|
+
require 'erb'
|
5
|
+
|
6
|
+
module Thin
|
7
|
+
# CLI runner.
|
8
|
+
# Parse options and send command to the correct Controller.
|
9
|
+
class Runner
|
10
|
+
COMMANDS = %w(start stop restart config)
|
11
|
+
LINUX_ONLY_COMMANDS = %w(install)
|
12
|
+
|
13
|
+
# Commands that wont load options from the config file
|
14
|
+
CONFIGLESS_COMMANDS = %w(config install)
|
15
|
+
|
16
|
+
# Parsed options
|
17
|
+
attr_accessor :options
|
18
|
+
|
19
|
+
# Name of the command to be runned.
|
20
|
+
attr_accessor :command
|
21
|
+
|
22
|
+
# Arguments to be passed to the command.
|
23
|
+
attr_accessor :arguments
|
24
|
+
|
25
|
+
# Return all available commands
|
26
|
+
def self.commands
|
27
|
+
commands = COMMANDS
|
28
|
+
commands += LINUX_ONLY_COMMANDS if Thin.linux?
|
29
|
+
commands
|
30
|
+
end
|
31
|
+
|
32
|
+
def initialize(argv)
|
33
|
+
@argv = argv
|
34
|
+
|
35
|
+
# Default options values
|
36
|
+
@options = {
|
37
|
+
:chdir => Dir.pwd,
|
38
|
+
:environment => ENV['RACK_ENV'] || 'development',
|
39
|
+
:address => '0.0.0.0',
|
40
|
+
:port => Server::DEFAULT_PORT,
|
41
|
+
:timeout => Server::DEFAULT_TIMEOUT,
|
42
|
+
:log => File.join(Dir.pwd, 'log/thin.log'),
|
43
|
+
:pid => 'tmp/pids/thin.pid',
|
44
|
+
:max_conns => Server::DEFAULT_MAXIMUM_CONNECTIONS,
|
45
|
+
:max_persistent_conns => Server::DEFAULT_MAXIMUM_PERSISTENT_CONNECTIONS,
|
46
|
+
:require => [],
|
47
|
+
:wait => Controllers::Cluster::DEFAULT_WAIT_TIME,
|
48
|
+
:threadpool_size => 20
|
49
|
+
}
|
50
|
+
|
51
|
+
parse!
|
52
|
+
end
|
53
|
+
|
54
|
+
def parser
|
55
|
+
# NOTE: If you add an option here make sure the key in the +options+ hash is the
|
56
|
+
# same as the name of the command line option.
|
57
|
+
# +option+ keys are used to build the command line to launch other processes,
|
58
|
+
# see <tt>lib/thin/command.rb</tt>.
|
59
|
+
@parser ||= OptionParser.new do |opts|
|
60
|
+
opts.banner = "Usage: thin [options] #{self.class.commands.join('|')}"
|
61
|
+
|
62
|
+
opts.separator ""
|
63
|
+
opts.separator "Server options:"
|
64
|
+
|
65
|
+
opts.on("-a", "--address HOST", "bind to HOST address " +
|
66
|
+
"(default: #{@options[:address]})") { |host| @options[:address] = host }
|
67
|
+
opts.on("-p", "--port PORT", "use PORT (default: #{@options[:port]})") { |port| @options[:port] = port.to_i }
|
68
|
+
opts.on("-S", "--socket FILE", "bind to unix domain socket") { |file| @options[:socket] = file }
|
69
|
+
opts.on("-y", "--swiftiply [KEY]", "Run using swiftiply") { |key| @options[:swiftiply] = key }
|
70
|
+
opts.on("-A", "--adapter NAME", "Rack adapter to use (default: autodetect)",
|
71
|
+
"(#{Rack::ADAPTERS.map{|(a,b)|a}.join(', ')})") { |name| @options[:adapter] = name }
|
72
|
+
opts.on("-R", "--rackup FILE", "Load a Rack config file instead of " +
|
73
|
+
"Rack adapter") { |file| @options[:rackup] = file }
|
74
|
+
opts.on("-c", "--chdir DIR", "Change to dir before starting") { |dir| @options[:chdir] = File.expand_path(dir) }
|
75
|
+
opts.on( "--stats PATH", "Mount the Stats adapter under PATH") { |path| @options[:stats] = path }
|
76
|
+
|
77
|
+
opts.separator ""
|
78
|
+
opts.separator "SSL options:"
|
79
|
+
|
80
|
+
opts.on( "--ssl", "Enables SSL") { @options[:ssl] = true }
|
81
|
+
opts.on( "--ssl-key-file PATH", "Path to private key") { |path| @options[:ssl_key_file] = path }
|
82
|
+
opts.on( "--ssl-cert-file PATH", "Path to certificate") { |path| @options[:ssl_cert_file] = path }
|
83
|
+
opts.on( "--ssl-disable-verify", "Disables (optional) client cert requests") { @options[:ssl_disable_verify] = true }
|
84
|
+
opts.on( "--ssl-version VERSION", "TLSv1, TLSv1_1, TLSv1_2") { |version| @options[:ssl_version] = version }
|
85
|
+
opts.on( "--ssl-cipher-list STRING", "Example: HIGH:!ADH:!RC4:-MEDIUM:-LOW:-EXP:-CAMELLIA") { |cipher| @options[:ssl_cipher_list] = cipher }
|
86
|
+
|
87
|
+
opts.separator ""
|
88
|
+
opts.separator "Adapter options:"
|
89
|
+
opts.on("-e", "--environment ENV", "Framework environment " +
|
90
|
+
"(default: #{@options[:environment]})") { |env| @options[:environment] = env }
|
91
|
+
opts.on( "--prefix PATH", "Mount the app under PATH (start with /)") { |path| @options[:prefix] = path }
|
92
|
+
|
93
|
+
unless Thin.win? # Daemonizing not supported on Windows
|
94
|
+
opts.separator ""
|
95
|
+
opts.separator "Daemon options:"
|
96
|
+
|
97
|
+
opts.on("-d", "--daemonize", "Run daemonized in the background") { @options[:daemonize] = true }
|
98
|
+
opts.on("-l", "--log FILE", "File to redirect output " +
|
99
|
+
"(default: #{@options[:log]})") { |file| @options[:log] = file }
|
100
|
+
opts.on("-P", "--pid FILE", "File to store PID " +
|
101
|
+
"(default: #{@options[:pid]})") { |file| @options[:pid] = file }
|
102
|
+
opts.on("-u", "--user NAME", "User to run daemon as (use with -g)") { |user| @options[:user] = user }
|
103
|
+
opts.on("-g", "--group NAME", "Group to run daemon as (use with -u)") { |group| @options[:group] = group }
|
104
|
+
opts.on( "--tag NAME", "Additional text to display in process listing") { |tag| @options[:tag] = tag }
|
105
|
+
|
106
|
+
opts.separator ""
|
107
|
+
opts.separator "Cluster options:"
|
108
|
+
|
109
|
+
opts.on("-s", "--servers NUM", "Number of servers to start") { |num| @options[:servers] = num.to_i }
|
110
|
+
opts.on("-o", "--only NUM", "Send command to only one server of the cluster") { |only| @options[:only] = only.to_i }
|
111
|
+
opts.on("-C", "--config FILE", "Load options from config file") { |file| @options[:config] = file }
|
112
|
+
opts.on( "--all [DIR]", "Send command to each config files in DIR") { |dir| @options[:all] = dir } if Thin.linux?
|
113
|
+
opts.on("-O", "--onebyone", "Restart the cluster one by one (only works with restart command)") { @options[:onebyone] = true }
|
114
|
+
opts.on("-w", "--wait NUM", "Maximum wait time for server to be started in seconds (use with -O)") { |time| @options[:wait] = time.to_i }
|
115
|
+
end
|
116
|
+
|
117
|
+
opts.separator ""
|
118
|
+
opts.separator "Tuning options:"
|
119
|
+
|
120
|
+
opts.on("-b", "--backend CLASS", "Backend to use, full classname") { |name| @options[:backend] = name }
|
121
|
+
opts.on("-t", "--timeout SEC", "Request or command timeout in sec " +
|
122
|
+
"(default: #{@options[:timeout]})") { |sec| @options[:timeout] = sec.to_i }
|
123
|
+
opts.on("-f", "--force", "Force the execution of the command") { @options[:force] = true }
|
124
|
+
opts.on( "--max-conns NUM", "Maximum number of open file descriptors " +
|
125
|
+
"(default: #{@options[:max_conns]})",
|
126
|
+
"Might require sudo to set higher than 1024") { |num| @options[:max_conns] = num.to_i } unless Thin.win?
|
127
|
+
opts.on( "--max-persistent-conns NUM",
|
128
|
+
"Maximum number of persistent connections",
|
129
|
+
"(default: #{@options[:max_persistent_conns]})") { |num| @options[:max_persistent_conns] = num.to_i }
|
130
|
+
opts.on( "--threaded", "Call the Rack application in threads " +
|
131
|
+
"[experimental]") { @options[:threaded] = true }
|
132
|
+
opts.on( "--threadpool-size NUM", "Sets the size of the EventMachine threadpool.",
|
133
|
+
"(default: #{@options[:threadpool_size]})") { |num| @options[:threadpool_size] = num.to_i }
|
134
|
+
opts.on( "--no-epoll", "Disable the use of epoll") { @options[:no_epoll] = true } if Thin.linux?
|
135
|
+
|
136
|
+
opts.separator ""
|
137
|
+
opts.separator "Common options:"
|
138
|
+
|
139
|
+
opts.on_tail("-r", "--require FILE", "require the library") { |file| @options[:require] << file }
|
140
|
+
opts.on_tail("-q", "--quiet", "Silence all logging") { @options[:quiet] = true }
|
141
|
+
opts.on_tail("-D", "--debug", "Enable debug logging") { @options[:debug] = true }
|
142
|
+
opts.on_tail("-V", "--trace", "Set tracing on (log raw request/response)") { @options[:trace] = true }
|
143
|
+
opts.on_tail("-h", "--help", "Show this message") { puts opts; exit }
|
144
|
+
opts.on_tail('-v', '--version', "Show version") { puts Thin::SERVER; exit }
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
# Parse the options.
|
149
|
+
def parse!
|
150
|
+
parser.parse! @argv
|
151
|
+
@command = @argv.shift
|
152
|
+
@arguments = @argv
|
153
|
+
end
|
154
|
+
|
155
|
+
# Parse the current shell arguments and run the command.
|
156
|
+
# Exits on error.
|
157
|
+
def run!
|
158
|
+
if self.class.commands.include?(@command)
|
159
|
+
run_command
|
160
|
+
elsif @command.nil?
|
161
|
+
puts "Command required"
|
162
|
+
puts @parser
|
163
|
+
exit 1
|
164
|
+
else
|
165
|
+
abort "Unknown command: #{@command}. Use one of #{self.class.commands.join(', ')}"
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
# Send the command to the controller: single instance or cluster.
|
170
|
+
def run_command
|
171
|
+
load_options_from_config_file! unless CONFIGLESS_COMMANDS.include?(@command)
|
172
|
+
|
173
|
+
# PROGRAM_NAME is relative to the current directory, so make sure
|
174
|
+
# we store and expand it before changing directory.
|
175
|
+
Command.script = File.expand_path($PROGRAM_NAME)
|
176
|
+
|
177
|
+
# Change the current directory ASAP so that all relative paths are
|
178
|
+
# relative to this one.
|
179
|
+
Dir.chdir(@options[:chdir]) unless CONFIGLESS_COMMANDS.include?(@command)
|
180
|
+
|
181
|
+
@options[:require].each { |r| ruby_require r }
|
182
|
+
|
183
|
+
# Setup the logger
|
184
|
+
if @options[:quiet]
|
185
|
+
Logging.silent = true
|
186
|
+
else
|
187
|
+
Logging.level = Logger::DEBUG if @options[:debug]
|
188
|
+
end
|
189
|
+
|
190
|
+
if @options[:trace]
|
191
|
+
# Trace raw requests/responses
|
192
|
+
Logging.trace_logger = Logging.logger
|
193
|
+
end
|
194
|
+
|
195
|
+
controller = case
|
196
|
+
when cluster? then Controllers::Cluster.new(@options)
|
197
|
+
when service? then Controllers::Service.new(@options)
|
198
|
+
else Controllers::Controller.new(@options)
|
199
|
+
end
|
200
|
+
|
201
|
+
if controller.respond_to?(@command)
|
202
|
+
begin
|
203
|
+
controller.send(@command, *@arguments)
|
204
|
+
rescue RunnerError => e
|
205
|
+
abort e.message
|
206
|
+
end
|
207
|
+
else
|
208
|
+
abort "Invalid options for command: #{@command}"
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
# +true+ if we're controlling a cluster.
|
213
|
+
def cluster?
|
214
|
+
@options[:only] || @options[:servers] || @options[:config]
|
215
|
+
end
|
216
|
+
|
217
|
+
# +true+ if we're acting a as system service.
|
218
|
+
def service?
|
219
|
+
@options.has_key?(:all) || @command == 'install'
|
220
|
+
end
|
221
|
+
|
222
|
+
private
|
223
|
+
def load_options_from_config_file!
|
224
|
+
if file = @options.delete(:config)
|
225
|
+
YAML.load(ERB.new(File.read(file)).result).each { |key, value| @options[key.to_sym] = value }
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
def ruby_require(file)
|
230
|
+
if File.extname(file) == '.ru'
|
231
|
+
warn 'WARNING: Use the -R option to load a Rack config file'
|
232
|
+
@options[:rackup] = file
|
233
|
+
else
|
234
|
+
require file
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
data/lib/thin/server.rb
ADDED
@@ -0,0 +1,290 @@
|
|
1
|
+
module Thin
|
2
|
+
# The utterly famous Thin HTTP server.
|
3
|
+
# It listens for incoming requests through a given +backend+
|
4
|
+
# and forwards all requests to +app+.
|
5
|
+
#
|
6
|
+
# == TCP server
|
7
|
+
# Create a new TCP server bound to <tt>host:port</tt> by specifiying +host+
|
8
|
+
# and +port+ as the first 2 arguments.
|
9
|
+
#
|
10
|
+
# Thin::Server.start('0.0.0.0', 3000, app)
|
11
|
+
#
|
12
|
+
# == UNIX domain server
|
13
|
+
# Create a new UNIX domain socket bound to +socket+ file by specifiying a filename
|
14
|
+
# as the first argument. Eg.: /tmp/thin.sock. If the first argument contains a <tt>/</tt>
|
15
|
+
# it will be assumed to be a UNIX socket.
|
16
|
+
#
|
17
|
+
# Thin::Server.start('/tmp/thin.sock', app)
|
18
|
+
#
|
19
|
+
# == Using a custom backend
|
20
|
+
# You can implement your own way to connect the server to its client by creating your
|
21
|
+
# own Backend class and passing it as the :backend option.
|
22
|
+
#
|
23
|
+
# Thin::Server.start('galaxy://faraway', 1345, app, :backend => Thin::Backends::MyFancyBackend)
|
24
|
+
#
|
25
|
+
# == Rack application (+app+)
|
26
|
+
# All requests will be processed through +app+, which must be a valid Rack adapter.
|
27
|
+
# A valid Rack adapter (application) must respond to <tt>call(env#Hash)</tt> and
|
28
|
+
# return an array of <tt>[status, headers, body]</tt>.
|
29
|
+
#
|
30
|
+
# == Building an app in place
|
31
|
+
# If a block is passed, a <tt>Rack::Builder</tt> instance
|
32
|
+
# will be passed to build the +app+. So you can do cool stuff like this:
|
33
|
+
#
|
34
|
+
# Thin::Server.start('0.0.0.0', 3000) do
|
35
|
+
# use Rack::CommonLogger
|
36
|
+
# use Rack::ShowExceptions
|
37
|
+
# map "/lobster" do
|
38
|
+
# use Rack::Lint
|
39
|
+
# run Rack::Lobster.new
|
40
|
+
# end
|
41
|
+
# end
|
42
|
+
#
|
43
|
+
# == Controlling with signals
|
44
|
+
# * INT and TERM: Force shutdown (see Server#stop!)
|
45
|
+
# * TERM & QUIT calls +stop+ to shutdown gracefully.
|
46
|
+
# * HUP calls +restart+ to ... surprise, restart!
|
47
|
+
# * USR1 reopen log files.
|
48
|
+
# Signals are processed at one second intervals.
|
49
|
+
# Disable signals by passing <tt>:signals => false</tt>.
|
50
|
+
#
|
51
|
+
class Server
|
52
|
+
include Logging
|
53
|
+
include Daemonizable
|
54
|
+
extend Forwardable
|
55
|
+
|
56
|
+
# Default values
|
57
|
+
DEFAULT_TIMEOUT = 30 #sec
|
58
|
+
DEFAULT_HOST = '0.0.0.0'
|
59
|
+
DEFAULT_PORT = 3000
|
60
|
+
DEFAULT_MAXIMUM_CONNECTIONS = 1024
|
61
|
+
DEFAULT_MAXIMUM_PERSISTENT_CONNECTIONS = 100
|
62
|
+
|
63
|
+
# Application (Rack adapter) called with the request that produces the response.
|
64
|
+
attr_accessor :app
|
65
|
+
|
66
|
+
# A tag that will show in the process listing
|
67
|
+
attr_accessor :tag
|
68
|
+
|
69
|
+
# Backend handling the connections to the clients.
|
70
|
+
attr_accessor :backend
|
71
|
+
|
72
|
+
# Maximum number of seconds for incoming data to arrive before the connection
|
73
|
+
# is dropped.
|
74
|
+
def_delegators :backend, :timeout, :timeout=
|
75
|
+
|
76
|
+
# Maximum number of file or socket descriptors that the server may open.
|
77
|
+
def_delegators :backend, :maximum_connections, :maximum_connections=
|
78
|
+
|
79
|
+
# Maximum number of connections that can be persistent at the same time.
|
80
|
+
# Most browsers never close the connection so most of the time they are closed
|
81
|
+
# when the timeout occurs. If we don't control the number of persistent connections,
|
82
|
+
# it would be very easy to overflow the server for a DoS attack.
|
83
|
+
def_delegators :backend, :maximum_persistent_connections, :maximum_persistent_connections=
|
84
|
+
|
85
|
+
# Allow using threads in the backend.
|
86
|
+
def_delegators :backend, :threaded?, :threaded=, :threadpool_size, :threadpool_size=
|
87
|
+
|
88
|
+
# Allow using SSL in the backend.
|
89
|
+
def_delegators :backend, :ssl?, :ssl=, :ssl_options=
|
90
|
+
|
91
|
+
# Address and port on which the server is listening for connections.
|
92
|
+
def_delegators :backend, :host, :port
|
93
|
+
|
94
|
+
# UNIX domain socket on which the server is listening for connections.
|
95
|
+
def_delegator :backend, :socket
|
96
|
+
|
97
|
+
# Disable the use of epoll under Linux
|
98
|
+
def_delegators :backend, :no_epoll, :no_epoll=
|
99
|
+
|
100
|
+
def initialize(*args, &block)
|
101
|
+
host, port, options = DEFAULT_HOST, DEFAULT_PORT, {}
|
102
|
+
|
103
|
+
# Guess each parameter by its type so they can be
|
104
|
+
# received in any order.
|
105
|
+
args.each do |arg|
|
106
|
+
case arg
|
107
|
+
when 0.class, /^\d+$/ then port = arg.to_i
|
108
|
+
when String then host = arg
|
109
|
+
when Hash then options = arg
|
110
|
+
else
|
111
|
+
@app = arg if arg.respond_to?(:call)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# Set tag if needed
|
116
|
+
self.tag = options[:tag]
|
117
|
+
|
118
|
+
# Try to intelligently select which backend to use.
|
119
|
+
@backend = select_backend(host, port, options)
|
120
|
+
|
121
|
+
load_cgi_multipart_eof_fix
|
122
|
+
|
123
|
+
@backend.server = self
|
124
|
+
|
125
|
+
# Set defaults
|
126
|
+
@backend.maximum_connections = DEFAULT_MAXIMUM_CONNECTIONS
|
127
|
+
@backend.maximum_persistent_connections = DEFAULT_MAXIMUM_PERSISTENT_CONNECTIONS
|
128
|
+
@backend.timeout = options[:timeout] || DEFAULT_TIMEOUT
|
129
|
+
|
130
|
+
# Allow using Rack builder as a block
|
131
|
+
@app = Rack::Builder.new(&block).to_app if block
|
132
|
+
|
133
|
+
# If in debug mode, wrap in logger adapter
|
134
|
+
@app = Rack::CommonLogger.new(@app) if Logging.debug?
|
135
|
+
|
136
|
+
@setup_signals = options[:signals] != false
|
137
|
+
end
|
138
|
+
|
139
|
+
# Lil' shortcut to turn this:
|
140
|
+
#
|
141
|
+
# Server.new(...).start
|
142
|
+
#
|
143
|
+
# into this:
|
144
|
+
#
|
145
|
+
# Server.start(...)
|
146
|
+
#
|
147
|
+
def self.start(*args, &block)
|
148
|
+
new(*args, &block).start!
|
149
|
+
end
|
150
|
+
|
151
|
+
# Start the server and listen for connections.
|
152
|
+
def start
|
153
|
+
raise ArgumentError, 'app required' unless @app
|
154
|
+
|
155
|
+
log_info "Thin web server (v#{VERSION::STRING} codename #{VERSION::CODENAME})"
|
156
|
+
log_debug "Debugging ON"
|
157
|
+
trace "Tracing ON"
|
158
|
+
|
159
|
+
log_info "Maximum connections set to #{@backend.maximum_connections}"
|
160
|
+
log_info "Listening on #{@backend}, CTRL+C to stop"
|
161
|
+
|
162
|
+
@backend.start { setup_signals if @setup_signals }
|
163
|
+
end
|
164
|
+
alias :start! :start
|
165
|
+
|
166
|
+
# == Gracefull shutdown
|
167
|
+
# Stops the server after processing all current connections.
|
168
|
+
# As soon as this method is called, the server stops accepting
|
169
|
+
# new requests and waits for all current connections to finish.
|
170
|
+
# Calling twice is the equivalent of calling <tt>stop!</tt>.
|
171
|
+
def stop
|
172
|
+
if running?
|
173
|
+
@backend.stop
|
174
|
+
unless @backend.empty?
|
175
|
+
log_info "Waiting for #{@backend.size} connection(s) to finish, "\
|
176
|
+
"can take up to #{timeout} sec, CTRL+C to stop now"
|
177
|
+
end
|
178
|
+
else
|
179
|
+
stop!
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
# == Force shutdown
|
184
|
+
# Stops the server closing all current connections right away.
|
185
|
+
# This doesn't wait for connection to finish their work and send data.
|
186
|
+
# All current requests will be dropped.
|
187
|
+
def stop!
|
188
|
+
if @backend.started_reactor?
|
189
|
+
log_info "Stopping ..."
|
190
|
+
else
|
191
|
+
log_info "Stopping Thin ..."
|
192
|
+
log_info "Thin was started inside an existing EventMachine.run block."
|
193
|
+
log_info "Call `EventMachine.stop` to stop the reactor and quit the process."
|
194
|
+
end
|
195
|
+
|
196
|
+
@backend.stop!
|
197
|
+
end
|
198
|
+
|
199
|
+
# == Reopen log file.
|
200
|
+
# Reopen the log file and redirect STDOUT and STDERR to it.
|
201
|
+
def reopen_log
|
202
|
+
return unless log_file
|
203
|
+
file = File.expand_path(log_file)
|
204
|
+
log_info "Reopening log file: #{file}"
|
205
|
+
Daemonize.redirect_io(file)
|
206
|
+
end
|
207
|
+
|
208
|
+
# == Configure the server
|
209
|
+
# The process might need to have superuser privilege to configure
|
210
|
+
# server with optimal options.
|
211
|
+
def config
|
212
|
+
@backend.config
|
213
|
+
end
|
214
|
+
|
215
|
+
# Name of the server and type of backend used.
|
216
|
+
# This is also the name of the process in which Thin is running as a daemon.
|
217
|
+
def name
|
218
|
+
"thin server (#{@backend})" + (tag ? " [#{tag}]" : "")
|
219
|
+
end
|
220
|
+
alias :to_s :name
|
221
|
+
|
222
|
+
# Return +true+ if the server is running and ready to receive requests.
|
223
|
+
# Note that the server might still be running and return +false+ when
|
224
|
+
# shuting down and waiting for active connections to complete.
|
225
|
+
def running?
|
226
|
+
@backend.running?
|
227
|
+
end
|
228
|
+
|
229
|
+
protected
|
230
|
+
def setup_signals
|
231
|
+
# Queue up signals so they are processed in non-trap context
|
232
|
+
# using a EM timer.
|
233
|
+
@signal_queue ||= []
|
234
|
+
|
235
|
+
%w( INT TERM ).each do |signal|
|
236
|
+
trap(signal) { @signal_queue.push signal }
|
237
|
+
end
|
238
|
+
# *nix only signals
|
239
|
+
%w( QUIT HUP USR1 ).each do |signal|
|
240
|
+
trap(signal) { @signal_queue.push signal }
|
241
|
+
end unless Thin.win?
|
242
|
+
|
243
|
+
# Signals are processed at one second intervals.
|
244
|
+
@signal_timer ||= EM.add_periodic_timer(1) { handle_signals }
|
245
|
+
end
|
246
|
+
|
247
|
+
def handle_signals
|
248
|
+
case @signal_queue.shift
|
249
|
+
when 'INT'
|
250
|
+
stop!
|
251
|
+
when 'TERM', 'QUIT'
|
252
|
+
stop
|
253
|
+
when 'HUP'
|
254
|
+
restart
|
255
|
+
when 'USR1'
|
256
|
+
reopen_log
|
257
|
+
end
|
258
|
+
EM.next_tick { handle_signals } unless @signal_queue.empty?
|
259
|
+
end
|
260
|
+
|
261
|
+
def select_backend(host, port, options)
|
262
|
+
case
|
263
|
+
when options.has_key?(:backend)
|
264
|
+
raise ArgumentError, ":backend must be a class" unless options[:backend].is_a?(Class)
|
265
|
+
options[:backend].new(host, port, options)
|
266
|
+
when options.has_key?(:swiftiply)
|
267
|
+
Backends::SwiftiplyClient.new(host, port, options)
|
268
|
+
when host.include?('/')
|
269
|
+
Backends::UnixServer.new(host)
|
270
|
+
else
|
271
|
+
Backends::TcpServer.new(host, port)
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
# Taken from Mongrel cgi_multipart_eof_fix
|
276
|
+
# Ruby 1.8.5 has a security bug in cgi.rb, we need to patch it.
|
277
|
+
def load_cgi_multipart_eof_fix
|
278
|
+
version = RUBY_VERSION.split('.').map { |i| i.to_i }
|
279
|
+
|
280
|
+
if version[0] <= 1 && version[1] <= 8 && version[2] <= 5 && RUBY_PLATFORM !~ /java/
|
281
|
+
begin
|
282
|
+
require 'cgi_multipart_eof_fix'
|
283
|
+
rescue LoadError
|
284
|
+
log_error "Ruby 1.8.5 is not secure please install cgi_multipart_eof_fix:"
|
285
|
+
log_error "gem install cgi_multipart_eof_fix"
|
286
|
+
end
|
287
|
+
end
|
288
|
+
end
|
289
|
+
end
|
290
|
+
end
|