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
data/lib/thin/runner.rb
CHANGED
@@ -1,238 +1,175 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
|
4
|
-
require
|
1
|
+
require "optparse"
|
2
|
+
require "rack"
|
3
|
+
|
4
|
+
require "thin/configurator"
|
5
5
|
|
6
6
|
module Thin
|
7
|
-
#
|
8
|
-
# Parse options and send command to the correct Controller.
|
7
|
+
# Command line runner. Mimic Rack's +rackup+.
|
9
8
|
class Runner
|
10
|
-
|
11
|
-
|
9
|
+
class OptionsParser
|
10
|
+
def parse!(args)
|
11
|
+
options = {}
|
12
|
+
opt_parser = OptionParser.new("", 24, ' ') do |opts|
|
13
|
+
opts.banner = "Usage: thin [ruby options] [thin options] [rackup config]"
|
12
14
|
|
13
|
-
|
14
|
-
|
15
|
+
opts.separator ""
|
16
|
+
opts.separator "Ruby options:"
|
17
|
+
|
18
|
+
lineno = 1
|
19
|
+
opts.on("-e", "--eval LINE", "evaluate a LINE of code") { |line|
|
20
|
+
eval line, TOPLEVEL_BINDING, "-e", lineno
|
21
|
+
lineno += 1
|
22
|
+
}
|
23
|
+
|
24
|
+
opts.on("-d", "--debug", "set debugging flags (set $DEBUG to true)") {
|
25
|
+
$DEBUG = true
|
26
|
+
}
|
27
|
+
opts.on("-w", "--warn", "turn warnings on for your script") {
|
28
|
+
$-w = true
|
29
|
+
}
|
30
|
+
|
31
|
+
opts.on("-I", "--include PATH",
|
32
|
+
"specify $LOAD_PATH (may be used more than once)") { |path|
|
33
|
+
$LOAD_PATH.unshift *path.split(":")
|
34
|
+
}
|
35
|
+
|
36
|
+
opts.on("-r", "--require LIBRARY",
|
37
|
+
"require the library, before executing your script") { |library|
|
38
|
+
require library
|
39
|
+
}
|
15
40
|
|
16
|
-
|
17
|
-
|
41
|
+
opts.separator ""
|
42
|
+
opts.separator "Thin options:"
|
18
43
|
|
19
|
-
|
20
|
-
|
44
|
+
opts.on("-o", "--host HOST", "bind to HOST") { |host|
|
45
|
+
options[:host] = host
|
46
|
+
}
|
21
47
|
|
22
|
-
|
23
|
-
|
48
|
+
opts.on("-p", "--port PORT", "use PORT (default: 9292)") { |port|
|
49
|
+
options[:port] = port
|
50
|
+
}
|
24
51
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
commands += LINUX_ONLY_COMMANDS if Thin.linux?
|
29
|
-
commands
|
30
|
-
end
|
52
|
+
opts.on("-E", "--env ENVIRONMENT", "use ENVIRONMENT for defaults (default: development)") { |e|
|
53
|
+
options[:environment] = e
|
54
|
+
}
|
31
55
|
|
32
|
-
|
33
|
-
|
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
|
-
}
|
56
|
+
opts.on("-D", "--daemonize", "run daemonized in the background") { |d|
|
57
|
+
options[:daemonize] = d ? true : false
|
58
|
+
}
|
50
59
|
|
51
|
-
|
52
|
-
|
60
|
+
opts.on("-P", "--pid FILE", "file to store PID (default: thin.pid)") { |f|
|
61
|
+
options[:pid] = ::File.expand_path(f)
|
62
|
+
}
|
53
63
|
|
54
|
-
|
55
|
-
|
56
|
-
|
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:"
|
64
|
+
opts.on("-l", "--log FILE", "file to log to (default: stdout)") { |f|
|
65
|
+
options[:log] = ::File.expand_path(f)
|
66
|
+
}
|
96
67
|
|
97
|
-
opts.on("-
|
98
|
-
|
99
|
-
|
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 }
|
68
|
+
opts.on("-c", "--config FILE", "Thin configuration file.") { |file|
|
69
|
+
options[:thin_config] = file
|
70
|
+
}
|
105
71
|
|
106
72
|
opts.separator ""
|
107
|
-
opts.separator "
|
108
|
-
|
109
|
-
opts.
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
opts.
|
73
|
+
opts.separator "Common options:"
|
74
|
+
|
75
|
+
opts.on_tail("-h", "-?", "--help", "Show this message") do
|
76
|
+
puts opts
|
77
|
+
exit
|
78
|
+
end
|
79
|
+
|
80
|
+
opts.on_tail("--version", "Show version") do
|
81
|
+
puts Thin::SERVER
|
82
|
+
exit
|
83
|
+
end
|
115
84
|
end
|
116
85
|
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
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
|
86
|
+
begin
|
87
|
+
opt_parser.parse! args
|
88
|
+
rescue OptionParser::InvalidOption => e
|
89
|
+
warn e.message
|
90
|
+
abort opt_parser.to_s
|
91
|
+
end
|
147
92
|
|
148
|
-
|
149
|
-
def parse!
|
150
|
-
parser.parse! @argv
|
151
|
-
@command = @argv.shift
|
152
|
-
@arguments = @argv
|
153
|
-
end
|
93
|
+
options[:config] = args.last if args.last
|
154
94
|
|
155
|
-
|
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(', ')}"
|
95
|
+
options
|
166
96
|
end
|
167
97
|
end
|
168
98
|
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
99
|
+
def default_options
|
100
|
+
{
|
101
|
+
:environment => ENV['RACK_ENV'] || "development",
|
102
|
+
:port => 9292,
|
103
|
+
:config => "config.ru"
|
104
|
+
}
|
105
|
+
end
|
176
106
|
|
177
|
-
|
178
|
-
|
179
|
-
Dir.chdir(@options[:chdir]) unless CONFIGLESS_COMMANDS.include?(@command)
|
107
|
+
def run(args)
|
108
|
+
options = default_options.dup
|
180
109
|
|
181
|
-
|
110
|
+
parser = OptionsParser.new
|
111
|
+
options.update parser.parse!(args)
|
182
112
|
|
183
|
-
#
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
Logging.level = Logger::DEBUG if @options[:debug]
|
113
|
+
# Parse in file options like rackup
|
114
|
+
shebang = File.open(options[:config]) { |f| f.readline }
|
115
|
+
if shebang[/^#\\(.*)/]
|
116
|
+
options.update parser.parse! $1.split(/\s+/)
|
188
117
|
end
|
189
118
|
|
190
|
-
|
191
|
-
|
192
|
-
|
119
|
+
ENV["RACK_ENV"] = options[:environment]
|
120
|
+
options[:config] = ::File.expand_path(options[:config])
|
121
|
+
|
122
|
+
# Configure the server
|
123
|
+
server = Server.new do
|
124
|
+
# This is passed as a block so it can be loaded inside workers if preload_app disabled.
|
125
|
+
build_app(options[:config], options[:environment])
|
193
126
|
end
|
194
127
|
|
195
|
-
|
196
|
-
|
197
|
-
when service? then Controllers::Service.new(@options)
|
198
|
-
else Controllers::Controller.new(@options)
|
128
|
+
if options[:thin_config]
|
129
|
+
Configurator.load(options[:thin_config]).apply(server)
|
199
130
|
end
|
200
131
|
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
rescue RunnerError => e
|
205
|
-
abort e.message
|
206
|
-
end
|
207
|
-
else
|
208
|
-
abort "Invalid options for command: #{@command}"
|
132
|
+
# If no listeners yet, use the one from the options
|
133
|
+
if !options.has_key?(:thin_config) || server.listeners.empty?
|
134
|
+
server.listen [options[:host], options[:port]].compact.join(":")
|
209
135
|
end
|
210
|
-
end
|
211
136
|
|
212
|
-
|
213
|
-
|
214
|
-
|
137
|
+
server.pid_path = options[:pid] if options[:pid]
|
138
|
+
server.log_path = options[:log] if options[:log]
|
139
|
+
server.worker_processes = options[:workers] if options[:workers]
|
140
|
+
server.timeout = options[:timeout] if options[:timeout]
|
141
|
+
|
142
|
+
# Start the server
|
143
|
+
server.start(options[:daemonize])
|
215
144
|
end
|
216
145
|
|
217
|
-
|
218
|
-
|
219
|
-
@options.has_key?(:all) || @command == 'install'
|
146
|
+
def self.run(args)
|
147
|
+
new.run(args)
|
220
148
|
end
|
221
149
|
|
222
150
|
private
|
223
|
-
def
|
224
|
-
|
225
|
-
|
226
|
-
|
151
|
+
def build_app(config, environment)
|
152
|
+
inner_app = Rack::Builder.parse_file(config).first
|
153
|
+
|
154
|
+
Rack::Builder.new do
|
155
|
+
case environment
|
156
|
+
when "development"
|
157
|
+
use Rack::ContentLength
|
158
|
+
use Rack::Chunked
|
159
|
+
use Rack::CommonLogger
|
160
|
+
use Rack::ShowExceptions
|
161
|
+
use Rack::Lint
|
162
|
+
|
163
|
+
when "deployment"
|
164
|
+
use Rack::ContentLength
|
165
|
+
use Rack::Chunked
|
166
|
+
use Rack::CommonLogger
|
167
|
+
|
168
|
+
end
|
169
|
+
|
170
|
+
run inner_app
|
171
|
+
end.to_app
|
227
172
|
end
|
228
173
|
|
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
174
|
end
|
238
175
|
end
|
data/lib/thin/server.rb
CHANGED
@@ -1,290 +1,241 @@
|
|
1
|
+
require "eventmachine"
|
2
|
+
|
3
|
+
require "thin/system"
|
4
|
+
require "thin/listener"
|
5
|
+
require "thin/connection"
|
6
|
+
require "thin/backends/prefork"
|
7
|
+
require "thin/backends/single_process"
|
8
|
+
|
1
9
|
module Thin
|
2
|
-
# The
|
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.
|
10
|
+
# The uterly famous Thin server.
|
9
11
|
#
|
10
|
-
#
|
12
|
+
# == Listening
|
13
|
+
# Create and start a new server listenting on port 3000 and forwarding all requests to +app+.
|
11
14
|
#
|
12
|
-
#
|
13
|
-
#
|
14
|
-
#
|
15
|
-
# it will be assumed to be a UNIX socket.
|
15
|
+
# server = Thin::Server.new(app)
|
16
|
+
# server.listen 3000
|
17
|
+
# server.start
|
16
18
|
#
|
17
|
-
#
|
19
|
+
# == Preforking and workers
|
20
|
+
# If fork(2) is available on your system, Thin will try to start workers to process requests.
|
21
|
+
# By default the number of workers will be based on the number of processors on your system.
|
22
|
+
# Configure this using the +worker_processes+ attribute.
|
23
|
+
# However, if fork(2) or if +worker_processes+ if equal to +0+, Thin will run in a single process
|
24
|
+
# with limited features.
|
18
25
|
#
|
19
|
-
# ==
|
20
|
-
#
|
21
|
-
#
|
26
|
+
# == Single process mode
|
27
|
+
# In this mode, Thin features will be limited:
|
28
|
+
# - no log files
|
29
|
+
# - no signal handling (only exits on INT).
|
22
30
|
#
|
23
|
-
#
|
31
|
+
# This mode is only intended as a fallback for systems with no fork(2) system call.
|
24
32
|
#
|
25
|
-
#
|
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
|
33
|
+
# You can force this mode by setting +worker_processes+ to +0+.
|
42
34
|
#
|
43
35
|
# == Controlling with signals
|
44
|
-
# *
|
45
|
-
# *
|
46
|
-
# *
|
47
|
-
# *
|
48
|
-
#
|
49
|
-
# Disable signals by passing <tt>:signals => false</tt>.
|
50
|
-
#
|
36
|
+
# - *WINCH*: Gracefully kill all workers but keep master alive
|
37
|
+
# - *TTIN*: Increase number of workers
|
38
|
+
# - *TTOU*: Decrease number of workers
|
39
|
+
# - *QUIT*: Kill workers and master in a graceful way
|
40
|
+
# - *TERM*, *INT*: Kill workers and master immediately
|
51
41
|
class Server
|
52
|
-
|
53
|
-
|
54
|
-
extend Forwardable
|
42
|
+
# Application called with the request that produces the response.
|
43
|
+
attr_reader :app
|
55
44
|
|
56
|
-
#
|
57
|
-
|
58
|
-
|
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
|
45
|
+
# Set to +true+ to load the app before forking to workers.
|
46
|
+
# Default: false
|
47
|
+
attr_accessor :preload_app
|
65
48
|
|
66
49
|
# A tag that will show in the process listing
|
67
50
|
attr_accessor :tag
|
68
51
|
|
69
|
-
#
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
#
|
80
|
-
|
81
|
-
|
82
|
-
#
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
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=
|
52
|
+
# Number of child worker processes.
|
53
|
+
# Setting this to 0 will result in running in a single process with limited features.
|
54
|
+
# Default: number of processors available, or 1 in development, or 0 if +fork+ is not available.
|
55
|
+
attr_accessor :worker_processes
|
56
|
+
|
57
|
+
# Maximum number of file descriptors that the worker may open.
|
58
|
+
# Default: 1024
|
59
|
+
attr_accessor :worker_connections
|
60
|
+
|
61
|
+
# Set to +true+ to call +app+ in a thread.
|
62
|
+
# Default: false
|
63
|
+
attr_accessor :threaded
|
64
|
+
|
65
|
+
# Size of the pool of threads used to call the +app+.
|
66
|
+
# Default: 20
|
67
|
+
attr_accessor :thread_pool_size
|
68
|
+
|
69
|
+
# Workers are killed if they don't check-in under +timeout+ seconds.
|
70
|
+
# Default: 30
|
71
|
+
attr_accessor :timeout
|
99
72
|
|
100
|
-
|
101
|
-
|
73
|
+
# Maximum number of concurrent requests which can be made over a keep-alive connection.
|
74
|
+
# Default: 100
|
75
|
+
attr_accessor :max_keep_alive_requests
|
76
|
+
|
77
|
+
# Path to the file in which the PID is saved.
|
78
|
+
# Default: ./thin.pid
|
79
|
+
attr_accessor :pid_path
|
80
|
+
|
81
|
+
# Path to the file in which standard output streams are redirected.
|
82
|
+
# Default: none, outputs to stdout
|
83
|
+
attr_accessor :log_path
|
84
|
+
|
85
|
+
# Set to +true+ to use epoll event model.
|
86
|
+
attr_accessor :use_epoll
|
87
|
+
|
88
|
+
# Set to +true+ to use kqueue event model.
|
89
|
+
attr_accessor :use_kqueue
|
90
|
+
|
91
|
+
# Set the backend handling the connections to the clients.
|
92
|
+
attr_writer :backend
|
93
|
+
|
94
|
+
# Listeners currently registered on this server.
|
95
|
+
# @see Thin::Listener
|
96
|
+
attr_accessor :listeners
|
97
|
+
|
98
|
+
# Object that is +call+ed before forking a worker process inside the master process.
|
99
|
+
attr_accessor :before_fork
|
100
|
+
|
101
|
+
# Object that is +call+ed after forking a worker process inside the worker process.
|
102
|
+
attr_accessor :after_fork
|
103
|
+
|
104
|
+
# Creates a new server that will forward requests to the app returned when +call+ing the +app_loader+ block.
|
105
|
+
# When +preload_app+ is set to +true+, +app_loader+ will be called before forking.
|
106
|
+
# When +preload_app+ is set to +false+, +app_loader+ will be called after forking.
|
107
|
+
# +host+, +port+ and +app+ supported for backward compatibility with Rack adapter.
|
108
|
+
def initialize(host=nil, port=nil, app=nil, &app_loader)
|
109
|
+
app_loader = proc { app } if app
|
110
|
+
@app_loader = app_loader || raise(ArgumentError, "app_loader block required")
|
102
111
|
|
103
|
-
#
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
112
|
+
# Set defaults
|
113
|
+
@preload_app = false
|
114
|
+
@timeout = 30
|
115
|
+
@pid_path = "./thin.pid"
|
116
|
+
@log_path = nil
|
117
|
+
@worker_connections = 1024
|
118
|
+
@threaded = false
|
119
|
+
@thread_pool_size = 20
|
120
|
+
@max_keep_alive_requests = 100
|
121
|
+
@keep_alive_requests = 0
|
122
|
+
@connections = 0 # Number of active connections
|
123
|
+
|
124
|
+
if System.supports_fork?
|
125
|
+
if ENV["RACK_ENV"] == "development"
|
126
|
+
# Default to one worker in development.
|
127
|
+
@worker_processes = 1
|
110
128
|
else
|
111
|
-
|
129
|
+
# One worker per processor in production.
|
130
|
+
@worker_processes = System.processor_count
|
112
131
|
end
|
132
|
+
else
|
133
|
+
# No workers, runs in a single process.
|
134
|
+
@worker_processes = 0
|
113
135
|
end
|
114
|
-
|
115
|
-
# Set tag if needed
|
116
|
-
self.tag = options[:tag]
|
117
136
|
|
118
|
-
|
119
|
-
@backend = select_backend(host, port, options)
|
120
|
-
|
121
|
-
load_cgi_multipart_eof_fix
|
137
|
+
@listeners = []
|
122
138
|
|
123
|
-
|
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
|
139
|
+
listen "#{host}:#{port}" if host && port
|
137
140
|
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
141
|
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
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"
|
142
|
+
# Backend handling connections to the clients.
|
143
|
+
def backend
|
144
|
+
@backend ||= begin
|
145
|
+
if prefork?
|
146
|
+
Backends::Prefork.new(self)
|
147
|
+
else
|
148
|
+
Backends::SingleProcess.new(self)
|
177
149
|
end
|
178
|
-
else
|
179
|
-
stop!
|
180
150
|
end
|
181
151
|
end
|
182
152
|
|
183
|
-
#
|
184
|
-
#
|
185
|
-
#
|
186
|
-
#
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
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)
|
153
|
+
# Listen for incoming connections on +address+.
|
154
|
+
# @example
|
155
|
+
# listen 3000 # port number
|
156
|
+
# listen "0.0.0.0:8008", :backlog => 80
|
157
|
+
# listen "[::]:8008", :ipv6_only => true
|
158
|
+
# listen "/tmp/thin.sock"
|
159
|
+
# @param address [String, Integer] address to listen on. Can be a port number of host:port.
|
160
|
+
# @option options [Boolean] :tcp_no_delay (true) Disables the Nagle algorithm for send coalescing.
|
161
|
+
# @option options [Boolean] :ipv6_only (false) do not listen on IPv4 interface.
|
162
|
+
# @option options [Integer] :backlog (1024) Maximum number of clients in the listening backlog.
|
163
|
+
def listen(address, options={})
|
164
|
+
@listeners << Listener.new(address, options)
|
206
165
|
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
166
|
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
%w( QUIT HUP USR1 ).each do |signal|
|
240
|
-
trap(signal) { @signal_queue.push signal }
|
241
|
-
end unless Thin.win?
|
167
|
+
# Starts the server and open a listening socket for each +listeners+.
|
168
|
+
# @param daemonize Daemonize the process after starting.
|
169
|
+
def start(daemonize=false)
|
170
|
+
puts "Starting #{to_s} ..."
|
242
171
|
|
243
|
-
|
244
|
-
|
245
|
-
|
172
|
+
# Configure EventMachine
|
173
|
+
EM.epoll = @use_epoll unless @use_epoll.nil?
|
174
|
+
EM.kqueue = @use_kqueue unless @use_kqueue.nil?
|
175
|
+
@worker_connections = EM.set_descriptor_table_size(@worker_connections)
|
176
|
+
EM.threadpool_size = @thread_pool_size
|
246
177
|
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
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
|
178
|
+
# Preload the app in the master process.
|
179
|
+
@app = @app_loader.call if @preload_app
|
180
|
+
|
181
|
+
@listeners.each do |listener|
|
182
|
+
puts "Listening with #{listener}"
|
183
|
+
listener.listen
|
273
184
|
end
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
185
|
+
puts "CTRL+C to stop"
|
186
|
+
|
187
|
+
backend.start(daemonize) do
|
188
|
+
# Load the app in the worker process if it was not preloaded.
|
189
|
+
@app = @app_loader.call unless @preload_app
|
190
|
+
|
191
|
+
@listeners.each do |listener|
|
192
|
+
EM.attach_server(listener.socket, Connection) do |connection|
|
193
|
+
connection.comm_inactivity_timeout = @timeout
|
194
|
+
connection.server = self
|
195
|
+
connection.listener = listener
|
196
|
+
|
197
|
+
# We control the number of keep-alive connections to prevent easy DDoS attacks.
|
198
|
+
if @keep_alive_requests < @max_keep_alive_requests
|
199
|
+
connection.can_keep_alive = true
|
200
|
+
@keep_alive_requests += 1
|
201
|
+
else
|
202
|
+
connection.can_keep_alive = false
|
203
|
+
end
|
204
|
+
|
205
|
+
@connections += 1
|
206
|
+
|
207
|
+
# Decrement counters on close
|
208
|
+
connection.on_close do
|
209
|
+
@keep_alive_requests -= 1 if connection.can_keep_alive
|
210
|
+
@connections -= 1
|
211
|
+
end
|
286
212
|
end
|
287
213
|
end
|
288
214
|
end
|
215
|
+
rescue
|
216
|
+
stop
|
217
|
+
raise
|
218
|
+
end
|
219
|
+
|
220
|
+
# Stops the server and close all listeners.
|
221
|
+
def stop
|
222
|
+
puts "Stopping ..."
|
223
|
+
@listeners.each { |listener| listener.close }
|
224
|
+
end
|
225
|
+
alias :shutdown :stop
|
226
|
+
|
227
|
+
# Returns +true+ if the server will fork workers or +false+ if it will run in a single process.
|
228
|
+
def prefork?
|
229
|
+
@worker_processes > 0
|
230
|
+
end
|
231
|
+
|
232
|
+
def threaded?
|
233
|
+
@threaded
|
234
|
+
end
|
235
|
+
|
236
|
+
# Procline of the process when the server is running.
|
237
|
+
def to_s
|
238
|
+
"Thin" + (@tag ? " [#{@tag}]" : "")
|
239
|
+
end
|
289
240
|
end
|
290
241
|
end
|