gross 1.7.2
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.
- 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
|