thin 1.2.6-x86-mingw32
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +273 -0
- data/COPYING +18 -0
- data/README +69 -0
- data/Rakefile +39 -0
- data/benchmark/abc +51 -0
- data/benchmark/benchmarker.rb +80 -0
- data/benchmark/runner +82 -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 +64 -0
- data/ext/thin_parser/common.rl +55 -0
- data/ext/thin_parser/ext_help.h +14 -0
- data/ext/thin_parser/extconf.rb +6 -0
- data/ext/thin_parser/parser.c +1185 -0
- data/ext/thin_parser/parser.h +49 -0
- data/ext/thin_parser/parser.rl +157 -0
- data/ext/thin_parser/thin.c +436 -0
- data/lib/rack/adapter/loader.rb +91 -0
- data/lib/rack/adapter/rails.rb +180 -0
- data/lib/thin.rb +46 -0
- data/lib/thin/backends/base.rb +141 -0
- data/lib/thin/backends/swiftiply_client.rb +56 -0
- data/lib/thin/backends/tcp_server.rb +29 -0
- data/lib/thin/backends/unix_server.rb +51 -0
- data/lib/thin/command.rb +53 -0
- data/lib/thin/connection.rb +222 -0
- data/lib/thin/controllers/cluster.rb +178 -0
- data/lib/thin/controllers/controller.rb +182 -0
- data/lib/thin/controllers/service.rb +75 -0
- data/lib/thin/controllers/service.sh.erb +39 -0
- data/lib/thin/daemonizing.rb +176 -0
- data/lib/thin/headers.rb +39 -0
- data/lib/thin/logging.rb +54 -0
- data/lib/thin/request.rb +157 -0
- data/lib/thin/response.rb +101 -0
- data/lib/thin/runner.rb +212 -0
- data/lib/thin/server.rb +248 -0
- data/lib/thin/stats.html.erb +216 -0
- data/lib/thin/stats.rb +52 -0
- data/lib/thin/statuses.rb +43 -0
- data/lib/thin/version.rb +32 -0
- data/lib/thin_parser.so +0 -0
- data/spec/backends/swiftiply_client_spec.rb +66 -0
- data/spec/backends/tcp_server_spec.rb +33 -0
- data/spec/backends/unix_server_spec.rb +37 -0
- data/spec/command_spec.rb +25 -0
- data/spec/configs/cluster.yml +9 -0
- data/spec/configs/single.yml +9 -0
- data/spec/connection_spec.rb +106 -0
- data/spec/controllers/cluster_spec.rb +267 -0
- data/spec/controllers/controller_spec.rb +129 -0
- data/spec/controllers/service_spec.rb +50 -0
- data/spec/daemonizing_spec.rb +192 -0
- data/spec/headers_spec.rb +40 -0
- data/spec/logging_spec.rb +46 -0
- data/spec/perf/request_perf_spec.rb +50 -0
- data/spec/perf/response_perf_spec.rb +19 -0
- data/spec/perf/server_perf_spec.rb +39 -0
- data/spec/rack/loader_spec.rb +42 -0
- data/spec/rack/rails_adapter_spec.rb +106 -0
- data/spec/rails_app/app/controllers/application.rb +10 -0
- data/spec/rails_app/app/controllers/simple_controller.rb +19 -0
- data/spec/rails_app/app/helpers/application_helper.rb +3 -0
- data/spec/rails_app/app/views/simple/index.html.erb +15 -0
- data/spec/rails_app/config/boot.rb +109 -0
- data/spec/rails_app/config/environment.rb +64 -0
- data/spec/rails_app/config/environments/development.rb +18 -0
- data/spec/rails_app/config/environments/production.rb +19 -0
- data/spec/rails_app/config/environments/test.rb +22 -0
- data/spec/rails_app/config/initializers/inflections.rb +10 -0
- data/spec/rails_app/config/initializers/mime_types.rb +5 -0
- data/spec/rails_app/config/routes.rb +35 -0
- data/spec/rails_app/public/404.html +30 -0
- data/spec/rails_app/public/422.html +30 -0
- data/spec/rails_app/public/500.html +30 -0
- data/spec/rails_app/public/dispatch.cgi +10 -0
- data/spec/rails_app/public/dispatch.fcgi +24 -0
- data/spec/rails_app/public/dispatch.rb +10 -0
- data/spec/rails_app/public/favicon.ico +0 -0
- data/spec/rails_app/public/images/rails.png +0 -0
- data/spec/rails_app/public/index.html +277 -0
- data/spec/rails_app/public/javascripts/application.js +2 -0
- data/spec/rails_app/public/javascripts/controls.js +963 -0
- data/spec/rails_app/public/javascripts/dragdrop.js +972 -0
- data/spec/rails_app/public/javascripts/effects.js +1120 -0
- data/spec/rails_app/public/javascripts/prototype.js +4225 -0
- data/spec/rails_app/public/robots.txt +5 -0
- data/spec/rails_app/script/about +3 -0
- data/spec/rails_app/script/console +3 -0
- data/spec/rails_app/script/destroy +3 -0
- data/spec/rails_app/script/generate +3 -0
- data/spec/rails_app/script/performance/benchmarker +3 -0
- data/spec/rails_app/script/performance/profiler +3 -0
- data/spec/rails_app/script/performance/request +3 -0
- data/spec/rails_app/script/plugin +3 -0
- data/spec/rails_app/script/process/inspector +3 -0
- data/spec/rails_app/script/process/reaper +3 -0
- data/spec/rails_app/script/process/spawner +3 -0
- data/spec/rails_app/script/runner +3 -0
- data/spec/rails_app/script/server +3 -0
- data/spec/request/mongrel_spec.rb +39 -0
- data/spec/request/parser_spec.rb +243 -0
- data/spec/request/persistent_spec.rb +35 -0
- data/spec/request/processing_spec.rb +50 -0
- data/spec/response_spec.rb +91 -0
- data/spec/runner_spec.rb +168 -0
- data/spec/server/builder_spec.rb +44 -0
- data/spec/server/pipelining_spec.rb +110 -0
- data/spec/server/robustness_spec.rb +34 -0
- data/spec/server/stopping_spec.rb +55 -0
- data/spec/server/swiftiply.yml +6 -0
- data/spec/server/swiftiply_spec.rb +32 -0
- data/spec/server/tcp_spec.rb +57 -0
- data/spec/server/threaded_spec.rb +27 -0
- data/spec/server/unix_socket_spec.rb +26 -0
- data/spec/server_spec.rb +100 -0
- data/spec/spec_helper.rb +219 -0
- data/tasks/announce.rake +22 -0
- data/tasks/deploy.rake +13 -0
- data/tasks/email.erb +30 -0
- data/tasks/gem.rake +66 -0
- data/tasks/rdoc.rake +25 -0
- data/tasks/site.rake +15 -0
- data/tasks/spec.rake +43 -0
- data/tasks/stats.rake +28 -0
- metadata +219 -0
data/lib/thin/headers.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
module Thin
|
2
|
+
# Store HTTP header name-value pairs direcly to a string
|
3
|
+
# and allow duplicated entries on some names.
|
4
|
+
class Headers
|
5
|
+
HEADER_FORMAT = "%s: %s\r\n".freeze
|
6
|
+
ALLOWED_DUPLICATES = %w(Set-Cookie Set-Cookie2 Warning WWW-Authenticate).freeze
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@sent = {}
|
10
|
+
@out = []
|
11
|
+
end
|
12
|
+
|
13
|
+
# Add <tt>key: value</tt> pair to the headers.
|
14
|
+
# Ignore if already sent and no duplicates are allowed
|
15
|
+
# for this +key+.
|
16
|
+
def []=(key, value)
|
17
|
+
if !@sent.has_key?(key) || ALLOWED_DUPLICATES.include?(key)
|
18
|
+
@sent[key] = true
|
19
|
+
value = case value
|
20
|
+
when Time
|
21
|
+
value.httpdate
|
22
|
+
when NilClass
|
23
|
+
return
|
24
|
+
else
|
25
|
+
value.to_s
|
26
|
+
end
|
27
|
+
@out << HEADER_FORMAT % [key, value]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def has_key?(key)
|
32
|
+
@sent[key]
|
33
|
+
end
|
34
|
+
|
35
|
+
def to_s
|
36
|
+
@out.join
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/lib/thin/logging.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
module Thin
|
2
|
+
# To be included in classes to allow some basic logging
|
3
|
+
# that can be silenced (<tt>Logging.silent=</tt>) or made
|
4
|
+
# more verbose.
|
5
|
+
# <tt>Logging.debug=</tt>: log all error backtrace and messages
|
6
|
+
# logged with +debug+.
|
7
|
+
# <tt>Logging.trace=</tt>: log all raw request and response and
|
8
|
+
# messages logged with +trace+.
|
9
|
+
module Logging
|
10
|
+
class << self
|
11
|
+
attr_writer :trace, :debug, :silent
|
12
|
+
|
13
|
+
def trace?; !@silent && @trace end
|
14
|
+
def debug?; !@silent && @debug end
|
15
|
+
def silent?; @silent end
|
16
|
+
end
|
17
|
+
|
18
|
+
# Global silencer methods
|
19
|
+
def silent
|
20
|
+
Logging.silent?
|
21
|
+
end
|
22
|
+
def silent=(value)
|
23
|
+
Logging.silent = value
|
24
|
+
end
|
25
|
+
|
26
|
+
# Log a message to the console
|
27
|
+
def log(msg)
|
28
|
+
puts msg unless Logging.silent?
|
29
|
+
end
|
30
|
+
module_function :log
|
31
|
+
public :log
|
32
|
+
|
33
|
+
# Log a message to the console if tracing is activated
|
34
|
+
def trace(msg=nil)
|
35
|
+
log msg || yield if Logging.trace?
|
36
|
+
end
|
37
|
+
module_function :trace
|
38
|
+
public :trace
|
39
|
+
|
40
|
+
# Log a message to the console if debugging is activated
|
41
|
+
def debug(msg=nil)
|
42
|
+
log msg || yield if Logging.debug?
|
43
|
+
end
|
44
|
+
module_function :debug
|
45
|
+
public :debug
|
46
|
+
|
47
|
+
# Log an error backtrace if debugging is activated
|
48
|
+
def log_error(e=$!)
|
49
|
+
debug "#{e}\n\t" + e.backtrace.join("\n\t")
|
50
|
+
end
|
51
|
+
module_function :log_error
|
52
|
+
public :log_error
|
53
|
+
end
|
54
|
+
end
|
data/lib/thin/request.rb
ADDED
@@ -0,0 +1,157 @@
|
|
1
|
+
require "#{Thin::ROOT}/thin_parser"
|
2
|
+
require 'tempfile'
|
3
|
+
|
4
|
+
module Thin
|
5
|
+
# Raised when an incoming request is not valid
|
6
|
+
# and the server can not process it.
|
7
|
+
class InvalidRequest < IOError; end
|
8
|
+
|
9
|
+
# A request sent by the client to the server.
|
10
|
+
class Request
|
11
|
+
# Maximum request body size before it is moved out of memory
|
12
|
+
# and into a tempfile for reading.
|
13
|
+
MAX_BODY = 1024 * (80 + 32)
|
14
|
+
BODY_TMPFILE = 'thin-body'.freeze
|
15
|
+
MAX_HEADER = 1024 * (80 + 32)
|
16
|
+
|
17
|
+
INITIAL_BODY = ''
|
18
|
+
# Force external_encoding of request's body to ASCII_8BIT
|
19
|
+
INITIAL_BODY.encode!(Encoding::ASCII_8BIT) if INITIAL_BODY.respond_to?(:encode!)
|
20
|
+
|
21
|
+
# Freeze some HTTP header names & values
|
22
|
+
SERVER_SOFTWARE = 'SERVER_SOFTWARE'.freeze
|
23
|
+
SERVER_NAME = 'SERVER_NAME'.freeze
|
24
|
+
LOCALHOST = 'localhost'.freeze
|
25
|
+
HTTP_VERSION = 'HTTP_VERSION'.freeze
|
26
|
+
HTTP_1_0 = 'HTTP/1.0'.freeze
|
27
|
+
REMOTE_ADDR = 'REMOTE_ADDR'.freeze
|
28
|
+
CONTENT_LENGTH = 'CONTENT_LENGTH'.freeze
|
29
|
+
CONNECTION = 'HTTP_CONNECTION'.freeze
|
30
|
+
KEEP_ALIVE_REGEXP = /\bkeep-alive\b/i.freeze
|
31
|
+
CLOSE_REGEXP = /\bclose\b/i.freeze
|
32
|
+
|
33
|
+
# Freeze some Rack header names
|
34
|
+
RACK_INPUT = 'rack.input'.freeze
|
35
|
+
RACK_VERSION = 'rack.version'.freeze
|
36
|
+
RACK_ERRORS = 'rack.errors'.freeze
|
37
|
+
RACK_MULTITHREAD = 'rack.multithread'.freeze
|
38
|
+
RACK_MULTIPROCESS = 'rack.multiprocess'.freeze
|
39
|
+
RACK_RUN_ONCE = 'rack.run_once'.freeze
|
40
|
+
ASYNC_CALLBACK = 'async.callback'.freeze
|
41
|
+
ASYNC_CLOSE = 'async.close'.freeze
|
42
|
+
|
43
|
+
# CGI-like request environment variables
|
44
|
+
attr_reader :env
|
45
|
+
|
46
|
+
# Unparsed data of the request
|
47
|
+
attr_reader :data
|
48
|
+
|
49
|
+
# Request body
|
50
|
+
attr_reader :body
|
51
|
+
|
52
|
+
def initialize
|
53
|
+
@parser = Thin::HttpParser.new
|
54
|
+
@data = ''
|
55
|
+
@nparsed = 0
|
56
|
+
@body = StringIO.new(INITIAL_BODY.dup)
|
57
|
+
@env = {
|
58
|
+
SERVER_SOFTWARE => SERVER,
|
59
|
+
SERVER_NAME => LOCALHOST,
|
60
|
+
|
61
|
+
# Rack stuff
|
62
|
+
RACK_INPUT => @body,
|
63
|
+
|
64
|
+
RACK_VERSION => VERSION::RACK,
|
65
|
+
RACK_ERRORS => STDERR,
|
66
|
+
|
67
|
+
RACK_MULTITHREAD => false,
|
68
|
+
RACK_MULTIPROCESS => false,
|
69
|
+
RACK_RUN_ONCE => false
|
70
|
+
}
|
71
|
+
end
|
72
|
+
|
73
|
+
# Parse a chunk of data into the request environment
|
74
|
+
# Raises a +InvalidRequest+ if invalid.
|
75
|
+
# Returns +true+ if the parsing is complete.
|
76
|
+
def parse(data)
|
77
|
+
if @parser.finished? # Header finished, can only be some more body
|
78
|
+
body << data
|
79
|
+
else # Parse more header using the super parser
|
80
|
+
@data << data
|
81
|
+
raise InvalidRequest, 'Header longer than allowed' if @data.size > MAX_HEADER
|
82
|
+
|
83
|
+
@nparsed = @parser.execute(@env, @data, @nparsed)
|
84
|
+
|
85
|
+
# Transfert to a tempfile if body is very big
|
86
|
+
move_body_to_tempfile if @parser.finished? && content_length > MAX_BODY
|
87
|
+
end
|
88
|
+
|
89
|
+
|
90
|
+
if finished? # Check if header and body are complete
|
91
|
+
@data = nil
|
92
|
+
@body.rewind
|
93
|
+
true # Request is fully parsed
|
94
|
+
else
|
95
|
+
false # Not finished, need more data
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# +true+ if headers and body are finished parsing
|
100
|
+
def finished?
|
101
|
+
@parser.finished? && @body.size >= content_length
|
102
|
+
end
|
103
|
+
|
104
|
+
# Expected size of the body
|
105
|
+
def content_length
|
106
|
+
@env[CONTENT_LENGTH].to_i
|
107
|
+
end
|
108
|
+
|
109
|
+
# Returns +true+ if the client expect the connection to be persistent.
|
110
|
+
def persistent?
|
111
|
+
# Clients and servers SHOULD NOT assume that a persistent connection
|
112
|
+
# is maintained for HTTP versions less than 1.1 unless it is explicitly
|
113
|
+
# signaled. (http://www.w3.org/Protocols/rfc2616/rfc2616-sec8.html)
|
114
|
+
if @env[HTTP_VERSION] == HTTP_1_0
|
115
|
+
@env[CONNECTION] =~ KEEP_ALIVE_REGEXP
|
116
|
+
|
117
|
+
# HTTP/1.1 client intends to maintain a persistent connection unless
|
118
|
+
# a Connection header including the connection-token "close" was sent
|
119
|
+
# in the request
|
120
|
+
else
|
121
|
+
@env[CONNECTION].nil? || @env[CONNECTION] !~ CLOSE_REGEXP
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def remote_address=(address)
|
126
|
+
@env[REMOTE_ADDR] = address
|
127
|
+
end
|
128
|
+
|
129
|
+
def threaded=(value)
|
130
|
+
@env[RACK_MULTITHREAD] = value
|
131
|
+
end
|
132
|
+
|
133
|
+
def async_callback=(callback)
|
134
|
+
@env[ASYNC_CALLBACK] = callback
|
135
|
+
@env[ASYNC_CLOSE] = EventMachine::DefaultDeferrable.new
|
136
|
+
end
|
137
|
+
|
138
|
+
def async_close
|
139
|
+
@async_close ||= @env[ASYNC_CLOSE]
|
140
|
+
end
|
141
|
+
|
142
|
+
# Close any resource used by the request
|
143
|
+
def close
|
144
|
+
@body.delete if @body.class == Tempfile
|
145
|
+
end
|
146
|
+
|
147
|
+
private
|
148
|
+
def move_body_to_tempfile
|
149
|
+
current_body = @body
|
150
|
+
current_body.rewind
|
151
|
+
@body = Tempfile.new(BODY_TMPFILE)
|
152
|
+
@body.binmode
|
153
|
+
@body << current_body.read
|
154
|
+
@env[RACK_INPUT] = @body
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
module Thin
|
2
|
+
# A response sent to the client.
|
3
|
+
class Response
|
4
|
+
CONNECTION = 'Connection'.freeze
|
5
|
+
CLOSE = 'close'.freeze
|
6
|
+
KEEP_ALIVE = 'keep-alive'.freeze
|
7
|
+
SERVER = 'Server'.freeze
|
8
|
+
CONTENT_LENGTH = 'Content-Length'.freeze
|
9
|
+
|
10
|
+
# Status code
|
11
|
+
attr_accessor :status
|
12
|
+
|
13
|
+
# Response body, must respond to +each+.
|
14
|
+
attr_accessor :body
|
15
|
+
|
16
|
+
# Headers key-value hash
|
17
|
+
attr_reader :headers
|
18
|
+
|
19
|
+
def initialize
|
20
|
+
@headers = Headers.new
|
21
|
+
@status = 200
|
22
|
+
@persistent = false
|
23
|
+
end
|
24
|
+
|
25
|
+
# String representation of the headers
|
26
|
+
# to be sent in the response.
|
27
|
+
def headers_output
|
28
|
+
# Set default headers
|
29
|
+
@headers[CONNECTION] = persistent? ? KEEP_ALIVE : CLOSE
|
30
|
+
@headers[SERVER] = Thin::SERVER
|
31
|
+
|
32
|
+
@headers.to_s
|
33
|
+
end
|
34
|
+
|
35
|
+
# Top header of the response,
|
36
|
+
# containing the status code and response headers.
|
37
|
+
def head
|
38
|
+
"HTTP/1.1 #{@status} #{HTTP_STATUS_CODES[@status.to_i]}\r\n#{headers_output}\r\n"
|
39
|
+
end
|
40
|
+
|
41
|
+
if Thin.ruby_18?
|
42
|
+
|
43
|
+
# Ruby 1.8 implementation.
|
44
|
+
# Respects Rack specs.
|
45
|
+
#
|
46
|
+
# See http://rack.rubyforge.org/doc/files/SPEC.html
|
47
|
+
def headers=(key_value_pairs)
|
48
|
+
key_value_pairs.each do |k, vs|
|
49
|
+
vs.each { |v| @headers[k] = v.chomp } if vs
|
50
|
+
end if key_value_pairs
|
51
|
+
end
|
52
|
+
|
53
|
+
else
|
54
|
+
|
55
|
+
# Ruby 1.9 doesn't have a String#each anymore.
|
56
|
+
# Rack spec doesn't take care of that yet, for now we just use
|
57
|
+
# +each+ but fallback to +each_line+ on strings.
|
58
|
+
# I wish we could remove that condition.
|
59
|
+
# To be reviewed when a new Rack spec comes out.
|
60
|
+
def headers=(key_value_pairs)
|
61
|
+
key_value_pairs.each do |k, vs|
|
62
|
+
next unless vs
|
63
|
+
if vs.is_a?(String)
|
64
|
+
vs.each_line { |v| @headers[k] = v.chomp }
|
65
|
+
else
|
66
|
+
vs.each { |v| @headers[k] = v.chomp }
|
67
|
+
end
|
68
|
+
end if key_value_pairs
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
# Close any resource used by the response
|
74
|
+
def close
|
75
|
+
@body.close if @body.respond_to?(:close)
|
76
|
+
end
|
77
|
+
|
78
|
+
# Yields each chunk of the response.
|
79
|
+
# To control the size of each chunk
|
80
|
+
# define your own +each+ method on +body+.
|
81
|
+
def each
|
82
|
+
yield head
|
83
|
+
if @body.is_a?(String)
|
84
|
+
yield @body
|
85
|
+
else
|
86
|
+
@body.each { |chunk| yield chunk }
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Tell the client the connection should stay open
|
91
|
+
def persistent!
|
92
|
+
@persistent = true
|
93
|
+
end
|
94
|
+
|
95
|
+
# Persistent connection must be requested as keep-alive
|
96
|
+
# from the server and have a Content-Length.
|
97
|
+
def persistent?
|
98
|
+
@persistent && @headers.has_key?(CONTENT_LENGTH)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
data/lib/thin/runner.rb
ADDED
@@ -0,0 +1,212 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
module Thin
|
5
|
+
# CLI runner.
|
6
|
+
# Parse options and send command to the correct Controller.
|
7
|
+
class Runner
|
8
|
+
COMMANDS = %w(start stop restart config)
|
9
|
+
LINUX_ONLY_COMMANDS = %w(install)
|
10
|
+
|
11
|
+
# Commands that wont load options from the config file
|
12
|
+
CONFIGLESS_COMMANDS = %w(config install)
|
13
|
+
|
14
|
+
# Parsed options
|
15
|
+
attr_accessor :options
|
16
|
+
|
17
|
+
# Name of the command to be runned.
|
18
|
+
attr_accessor :command
|
19
|
+
|
20
|
+
# Arguments to be passed to the command.
|
21
|
+
attr_accessor :arguments
|
22
|
+
|
23
|
+
# Return all available commands
|
24
|
+
def self.commands
|
25
|
+
commands = COMMANDS
|
26
|
+
commands += LINUX_ONLY_COMMANDS if Thin.linux?
|
27
|
+
commands
|
28
|
+
end
|
29
|
+
|
30
|
+
def initialize(argv)
|
31
|
+
@argv = argv
|
32
|
+
|
33
|
+
# Default options values
|
34
|
+
@options = {
|
35
|
+
:chdir => Dir.pwd,
|
36
|
+
:environment => 'development',
|
37
|
+
:address => '0.0.0.0',
|
38
|
+
:port => Server::DEFAULT_PORT,
|
39
|
+
:timeout => Server::DEFAULT_TIMEOUT,
|
40
|
+
:log => 'log/thin.log',
|
41
|
+
:pid => 'tmp/pids/thin.pid',
|
42
|
+
:max_conns => Server::DEFAULT_MAXIMUM_CONNECTIONS,
|
43
|
+
:max_persistent_conns => Server::DEFAULT_MAXIMUM_PERSISTENT_CONNECTIONS,
|
44
|
+
:require => [],
|
45
|
+
:wait => Controllers::Cluster::DEFAULT_WAIT_TIME
|
46
|
+
}
|
47
|
+
|
48
|
+
parse!
|
49
|
+
end
|
50
|
+
|
51
|
+
def parser
|
52
|
+
# NOTE: If you add an option here make sure the key in the +options+ hash is the
|
53
|
+
# same as the name of the command line option.
|
54
|
+
# +option+ keys are used to build the command line to launch other processes,
|
55
|
+
# see <tt>lib/thin/command.rb</tt>.
|
56
|
+
@parser ||= OptionParser.new do |opts|
|
57
|
+
opts.banner = "Usage: thin [options] #{self.class.commands.join('|')}"
|
58
|
+
|
59
|
+
opts.separator ""
|
60
|
+
opts.separator "Server options:"
|
61
|
+
|
62
|
+
opts.on("-a", "--address HOST", "bind to HOST address " +
|
63
|
+
"(default: #{@options[:address]})") { |host| @options[:address] = host }
|
64
|
+
opts.on("-p", "--port PORT", "use PORT (default: #{@options[:port]})") { |port| @options[:port] = port.to_i }
|
65
|
+
opts.on("-S", "--socket FILE", "bind to unix domain socket") { |file| @options[:socket] = file }
|
66
|
+
opts.on("-y", "--swiftiply [KEY]", "Run using swiftiply") { |key| @options[:swiftiply] = key }
|
67
|
+
opts.on("-A", "--adapter NAME", "Rack adapter to use (default: autodetect)",
|
68
|
+
"(#{Rack::ADAPTERS.map{|(a,b)|a}.join(', ')})") { |name| @options[:adapter] = name }
|
69
|
+
opts.on("-R", "--rackup FILE", "Load a Rack config file instead of " +
|
70
|
+
"Rack adapter") { |file| @options[:rackup] = file }
|
71
|
+
opts.on("-c", "--chdir DIR", "Change to dir before starting") { |dir| @options[:chdir] = File.expand_path(dir) }
|
72
|
+
opts.on( "--stats PATH", "Mount the Stats adapter under PATH") { |path| @options[:stats] = path }
|
73
|
+
|
74
|
+
opts.separator ""
|
75
|
+
opts.separator "Adapter options:"
|
76
|
+
opts.on("-e", "--environment ENV", "Framework environment " +
|
77
|
+
"(default: #{@options[:environment]})") { |env| @options[:environment] = env }
|
78
|
+
opts.on( "--prefix PATH", "Mount the app under PATH (start with /)") { |path| @options[:prefix] = path }
|
79
|
+
|
80
|
+
unless Thin.win? # Daemonizing not supported on Windows
|
81
|
+
opts.separator ""
|
82
|
+
opts.separator "Daemon options:"
|
83
|
+
|
84
|
+
opts.on("-d", "--daemonize", "Run daemonized in the background") { @options[:daemonize] = true }
|
85
|
+
opts.on("-l", "--log FILE", "File to redirect output " +
|
86
|
+
"(default: #{@options[:log]})") { |file| @options[:log] = file }
|
87
|
+
opts.on("-P", "--pid FILE", "File to store PID " +
|
88
|
+
"(default: #{@options[:pid]})") { |file| @options[:pid] = file }
|
89
|
+
opts.on("-u", "--user NAME", "User to run daemon as (use with -g)") { |user| @options[:user] = user }
|
90
|
+
opts.on("-g", "--group NAME", "Group to run daemon as (use with -u)") { |group| @options[:group] = group }
|
91
|
+
opts.on( "--tag NAME", "Additional text to display in process listing") { |tag| @options[:tag] = tag }
|
92
|
+
|
93
|
+
opts.separator ""
|
94
|
+
opts.separator "Cluster options:"
|
95
|
+
|
96
|
+
opts.on("-s", "--servers NUM", "Number of servers to start") { |num| @options[:servers] = num.to_i }
|
97
|
+
opts.on("-o", "--only NUM", "Send command to only one server of the cluster") { |only| @options[:only] = only.to_i }
|
98
|
+
opts.on("-C", "--config FILE", "Load options from config file") { |file| @options[:config] = file }
|
99
|
+
opts.on( "--all [DIR]", "Send command to each config files in DIR") { |dir| @options[:all] = dir } if Thin.linux?
|
100
|
+
opts.on("-O", "--onebyone", "Restart the cluster one by one (only works with restart command)") { @options[:onebyone] = true }
|
101
|
+
opts.on("-w", "--wait NUM", "Maximum wait time for server to be started in seconds (use with -O)") { |time| @options[:wait] = time.to_i }
|
102
|
+
end
|
103
|
+
|
104
|
+
opts.separator ""
|
105
|
+
opts.separator "Tuning options:"
|
106
|
+
|
107
|
+
opts.on("-b", "--backend CLASS", "Backend to use, full classname") { |name| @options[:backend] = name }
|
108
|
+
opts.on("-t", "--timeout SEC", "Request or command timeout in sec " +
|
109
|
+
"(default: #{@options[:timeout]})") { |sec| @options[:timeout] = sec.to_i }
|
110
|
+
opts.on("-f", "--force", "Force the execution of the command") { @options[:force] = true }
|
111
|
+
opts.on( "--max-conns NUM", "Maximum number of open file descriptors " +
|
112
|
+
"(default: #{@options[:max_conns]})",
|
113
|
+
"Might require sudo to set higher then 1024") { |num| @options[:max_conns] = num.to_i } unless Thin.win?
|
114
|
+
opts.on( "--max-persistent-conns NUM",
|
115
|
+
"Maximum number of persistent connections",
|
116
|
+
"(default: #{@options[:max_persistent_conns]})") { |num| @options[:max_persistent_conns] = num.to_i }
|
117
|
+
opts.on( "--threaded", "Call the Rack application in threads " +
|
118
|
+
"[experimental]") { @options[:threaded] = true }
|
119
|
+
opts.on( "--no-epoll", "Disable the use of epoll") { @options[:no_epoll] = true } if Thin.linux?
|
120
|
+
|
121
|
+
opts.separator ""
|
122
|
+
opts.separator "Common options:"
|
123
|
+
|
124
|
+
opts.on_tail("-r", "--require FILE", "require the library") { |file| @options[:require] << file }
|
125
|
+
opts.on_tail("-D", "--debug", "Set debbuging on") { @options[:debug] = true }
|
126
|
+
opts.on_tail("-V", "--trace", "Set tracing on (log raw request/response)") { @options[:trace] = true }
|
127
|
+
opts.on_tail("-h", "--help", "Show this message") { puts opts; exit }
|
128
|
+
opts.on_tail('-v', '--version', "Show version") { puts Thin::SERVER; exit }
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
# Parse the options.
|
133
|
+
def parse!
|
134
|
+
parser.parse! @argv
|
135
|
+
@command = @argv.shift
|
136
|
+
@arguments = @argv
|
137
|
+
end
|
138
|
+
|
139
|
+
# Parse the current shell arguments and run the command.
|
140
|
+
# Exits on error.
|
141
|
+
def run!
|
142
|
+
if self.class.commands.include?(@command)
|
143
|
+
run_command
|
144
|
+
elsif @command.nil?
|
145
|
+
puts "Command required"
|
146
|
+
puts @parser
|
147
|
+
exit 1
|
148
|
+
else
|
149
|
+
abort "Unknown command: #{@command}. Use one of #{self.class.commands.join(', ')}"
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
# Send the command to the controller: single instance or cluster.
|
154
|
+
def run_command
|
155
|
+
load_options_from_config_file! unless CONFIGLESS_COMMANDS.include?(@command)
|
156
|
+
|
157
|
+
# PROGRAM_NAME is relative to the current directory, so make sure
|
158
|
+
# we store and expand it before changing directory.
|
159
|
+
Command.script = File.expand_path($PROGRAM_NAME)
|
160
|
+
|
161
|
+
# Change the current directory ASAP so that all relative paths are
|
162
|
+
# relative to this one.
|
163
|
+
Dir.chdir(@options[:chdir]) unless CONFIGLESS_COMMANDS.include?(@command)
|
164
|
+
|
165
|
+
@options[:require].each { |r| ruby_require r }
|
166
|
+
Logging.debug = @options[:debug]
|
167
|
+
Logging.trace = @options[:trace]
|
168
|
+
|
169
|
+
controller = case
|
170
|
+
when cluster? then Controllers::Cluster.new(@options)
|
171
|
+
when service? then Controllers::Service.new(@options)
|
172
|
+
else Controllers::Controller.new(@options)
|
173
|
+
end
|
174
|
+
|
175
|
+
if controller.respond_to?(@command)
|
176
|
+
begin
|
177
|
+
controller.send(@command, *@arguments)
|
178
|
+
rescue RunnerError => e
|
179
|
+
abort e.message
|
180
|
+
end
|
181
|
+
else
|
182
|
+
abort "Invalid options for command: #{@command}"
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
# +true+ if we're controlling a cluster.
|
187
|
+
def cluster?
|
188
|
+
@options[:only] || @options[:servers] || @options[:config]
|
189
|
+
end
|
190
|
+
|
191
|
+
# +true+ if we're acting a as system service.
|
192
|
+
def service?
|
193
|
+
@options.has_key?(:all) || @command == 'install'
|
194
|
+
end
|
195
|
+
|
196
|
+
private
|
197
|
+
def load_options_from_config_file!
|
198
|
+
if file = @options.delete(:config)
|
199
|
+
YAML.load_file(file).each { |key, value| @options[key.to_sym] = value }
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
def ruby_require(file)
|
204
|
+
if File.extname(file) == '.ru'
|
205
|
+
warn 'WARNING: Use the -R option to load a Rack config file'
|
206
|
+
@options[:rackup] = file
|
207
|
+
else
|
208
|
+
require file
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|