thin 1.2.3-x86-mswin32
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/CHANGELOG +263 -0
- data/COPYING +18 -0
- data/README +69 -0
- data/Rakefile +36 -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 +452 -0
- data/ext/thin_parser/parser.h +49 -0
- data/ext/thin_parser/parser.rl +157 -0
- data/ext/thin_parser/thin.c +433 -0
- data/lib/rack/adapter/loader.rb +79 -0
- data/lib/rack/adapter/rails.rb +181 -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 +127 -0
- data/lib/thin/controllers/controller.rb +183 -0
- data/lib/thin/controllers/service.rb +75 -0
- data/lib/thin/controllers/service.sh.erb +39 -0
- data/lib/thin/daemonizing.rb +174 -0
- data/lib/thin/headers.rb +39 -0
- data/lib/thin/logging.rb +54 -0
- data/lib/thin/request.rb +153 -0
- data/lib/thin/response.rb +101 -0
- data/lib/thin/runner.rb +209 -0
- data/lib/thin/server.rb +247 -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 +235 -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 +29 -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 +215 -0
- data/spec/request/persistent_spec.rb +35 -0
- data/spec/request/processing_spec.rb +45 -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 +96 -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 +74 -0
- data/tasks/rdoc.rake +25 -0
- data/tasks/site.rake +15 -0
- data/tasks/spec.rake +49 -0
- data/tasks/stats.rake +28 -0
- metadata +246 -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,153 @@
|
|
1
|
+
require '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
|
+
# Freeze some HTTP header names & values
|
18
|
+
SERVER_SOFTWARE = 'SERVER_SOFTWARE'.freeze
|
19
|
+
SERVER_NAME = 'SERVER_NAME'.freeze
|
20
|
+
LOCALHOST = 'localhost'.freeze
|
21
|
+
HTTP_VERSION = 'HTTP_VERSION'.freeze
|
22
|
+
HTTP_1_0 = 'HTTP/1.0'.freeze
|
23
|
+
REMOTE_ADDR = 'REMOTE_ADDR'.freeze
|
24
|
+
CONTENT_LENGTH = 'CONTENT_LENGTH'.freeze
|
25
|
+
CONNECTION = 'HTTP_CONNECTION'.freeze
|
26
|
+
KEEP_ALIVE_REGEXP = /\bkeep-alive\b/i.freeze
|
27
|
+
CLOSE_REGEXP = /\bclose\b/i.freeze
|
28
|
+
|
29
|
+
# Freeze some Rack header names
|
30
|
+
RACK_INPUT = 'rack.input'.freeze
|
31
|
+
RACK_VERSION = 'rack.version'.freeze
|
32
|
+
RACK_ERRORS = 'rack.errors'.freeze
|
33
|
+
RACK_MULTITHREAD = 'rack.multithread'.freeze
|
34
|
+
RACK_MULTIPROCESS = 'rack.multiprocess'.freeze
|
35
|
+
RACK_RUN_ONCE = 'rack.run_once'.freeze
|
36
|
+
ASYNC_CALLBACK = 'async.callback'.freeze
|
37
|
+
ASYNC_CLOSE = 'async.close'.freeze
|
38
|
+
|
39
|
+
# CGI-like request environment variables
|
40
|
+
attr_reader :env
|
41
|
+
|
42
|
+
# Unparsed data of the request
|
43
|
+
attr_reader :data
|
44
|
+
|
45
|
+
# Request body
|
46
|
+
attr_reader :body
|
47
|
+
|
48
|
+
def initialize
|
49
|
+
@parser = Thin::HttpParser.new
|
50
|
+
@data = ''
|
51
|
+
@nparsed = 0
|
52
|
+
@body = StringIO.new
|
53
|
+
@env = {
|
54
|
+
SERVER_SOFTWARE => SERVER,
|
55
|
+
SERVER_NAME => LOCALHOST,
|
56
|
+
|
57
|
+
# Rack stuff
|
58
|
+
RACK_INPUT => @body,
|
59
|
+
|
60
|
+
RACK_VERSION => VERSION::RACK,
|
61
|
+
RACK_ERRORS => STDERR,
|
62
|
+
|
63
|
+
RACK_MULTITHREAD => false,
|
64
|
+
RACK_MULTIPROCESS => false,
|
65
|
+
RACK_RUN_ONCE => false
|
66
|
+
}
|
67
|
+
end
|
68
|
+
|
69
|
+
# Parse a chunk of data into the request environment
|
70
|
+
# Raises a +InvalidRequest+ if invalid.
|
71
|
+
# Returns +true+ if the parsing is complete.
|
72
|
+
def parse(data)
|
73
|
+
if @parser.finished? # Header finished, can only be some more body
|
74
|
+
body << data
|
75
|
+
else # Parse more header using the super parser
|
76
|
+
@data << data
|
77
|
+
raise InvalidRequest, 'Header longer than allowed' if @data.size > MAX_HEADER
|
78
|
+
|
79
|
+
@nparsed = @parser.execute(@env, @data, @nparsed)
|
80
|
+
|
81
|
+
# Transfert to a tempfile if body is very big
|
82
|
+
move_body_to_tempfile if @parser.finished? && content_length > MAX_BODY
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
if finished? # Check if header and body are complete
|
87
|
+
@data = nil
|
88
|
+
@body.rewind
|
89
|
+
true # Request is fully parsed
|
90
|
+
else
|
91
|
+
false # Not finished, need more data
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# +true+ if headers and body are finished parsing
|
96
|
+
def finished?
|
97
|
+
@parser.finished? && @body.size >= content_length
|
98
|
+
end
|
99
|
+
|
100
|
+
# Expected size of the body
|
101
|
+
def content_length
|
102
|
+
@env[CONTENT_LENGTH].to_i
|
103
|
+
end
|
104
|
+
|
105
|
+
# Returns +true+ if the client expect the connection to be persistent.
|
106
|
+
def persistent?
|
107
|
+
# Clients and servers SHOULD NOT assume that a persistent connection
|
108
|
+
# is maintained for HTTP versions less than 1.1 unless it is explicitly
|
109
|
+
# signaled. (http://www.w3.org/Protocols/rfc2616/rfc2616-sec8.html)
|
110
|
+
if @env[HTTP_VERSION] == HTTP_1_0
|
111
|
+
@env[CONNECTION] =~ KEEP_ALIVE_REGEXP
|
112
|
+
|
113
|
+
# HTTP/1.1 client intends to maintain a persistent connection unless
|
114
|
+
# a Connection header including the connection-token "close" was sent
|
115
|
+
# in the request
|
116
|
+
else
|
117
|
+
@env[CONNECTION].nil? || @env[CONNECTION] !~ CLOSE_REGEXP
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def remote_address=(address)
|
122
|
+
@env[REMOTE_ADDR] = address
|
123
|
+
end
|
124
|
+
|
125
|
+
def threaded=(value)
|
126
|
+
@env[RACK_MULTITHREAD] = value
|
127
|
+
end
|
128
|
+
|
129
|
+
def async_callback=(callback)
|
130
|
+
@env[ASYNC_CALLBACK] = callback
|
131
|
+
@env[ASYNC_CLOSE] = EventMachine::DefaultDeferrable.new
|
132
|
+
end
|
133
|
+
|
134
|
+
def async_close
|
135
|
+
@async_close ||= @env[ASYNC_CLOSE]
|
136
|
+
end
|
137
|
+
|
138
|
+
# Close any resource used by the request
|
139
|
+
def close
|
140
|
+
@body.delete if @body.class == Tempfile
|
141
|
+
end
|
142
|
+
|
143
|
+
private
|
144
|
+
def move_body_to_tempfile
|
145
|
+
current_body = @body
|
146
|
+
current_body.rewind
|
147
|
+
@body = Tempfile.new(BODY_TMPFILE)
|
148
|
+
@body.binmode
|
149
|
+
@body << current_body.read
|
150
|
+
@env[RACK_INPUT] = @body
|
151
|
+
end
|
152
|
+
end
|
153
|
+
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,209 @@
|
|
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
|
+
}
|
46
|
+
|
47
|
+
parse!
|
48
|
+
end
|
49
|
+
|
50
|
+
def parser
|
51
|
+
# NOTE: If you add an option here make sure the key in the +options+ hash is the
|
52
|
+
# same as the name of the command line option.
|
53
|
+
# +option+ keys are used to build the command line to launch other processes,
|
54
|
+
# see <tt>lib/thin/command.rb</tt>.
|
55
|
+
@parser ||= OptionParser.new do |opts|
|
56
|
+
opts.banner = "Usage: thin [options] #{self.class.commands.join('|')}"
|
57
|
+
|
58
|
+
opts.separator ""
|
59
|
+
opts.separator "Server options:"
|
60
|
+
|
61
|
+
opts.on("-a", "--address HOST", "bind to HOST address " +
|
62
|
+
"(default: #{@options[:address]})") { |host| @options[:address] = host }
|
63
|
+
opts.on("-p", "--port PORT", "use PORT (default: #{@options[:port]})") { |port| @options[:port] = port.to_i }
|
64
|
+
opts.on("-S", "--socket FILE", "bind to unix domain socket") { |file| @options[:socket] = file }
|
65
|
+
opts.on("-y", "--swiftiply [KEY]", "Run using swiftiply") { |key| @options[:swiftiply] = key }
|
66
|
+
opts.on("-A", "--adapter NAME", "Rack adapter to use (default: autodetect)",
|
67
|
+
"(#{Rack::ADAPTERS.map{|(a,b)|a}.join(', ')})") { |name| @options[:adapter] = name }
|
68
|
+
opts.on("-R", "--rackup FILE", "Load a Rack config file instead of " +
|
69
|
+
"Rack adapter") { |file| @options[:rackup] = file }
|
70
|
+
opts.on("-c", "--chdir DIR", "Change to dir before starting") { |dir| @options[:chdir] = File.expand_path(dir) }
|
71
|
+
opts.on( "--stats PATH", "Mount the Stats adapter under PATH") { |path| @options[:stats] = path }
|
72
|
+
|
73
|
+
opts.separator ""
|
74
|
+
opts.separator "Adapter options:"
|
75
|
+
opts.on("-e", "--environment ENV", "Framework environment " +
|
76
|
+
"(default: #{@options[:environment]})") { |env| @options[:environment] = env }
|
77
|
+
opts.on( "--prefix PATH", "Mount the app under PATH (start with /)") { |path| @options[:prefix] = path }
|
78
|
+
|
79
|
+
unless Thin.win? # Daemonizing not supported on Windows
|
80
|
+
opts.separator ""
|
81
|
+
opts.separator "Daemon options:"
|
82
|
+
|
83
|
+
opts.on("-d", "--daemonize", "Run daemonized in the background") { @options[:daemonize] = true }
|
84
|
+
opts.on("-l", "--log FILE", "File to redirect output " +
|
85
|
+
"(default: #{@options[:log]})") { |file| @options[:log] = file }
|
86
|
+
opts.on("-P", "--pid FILE", "File to store PID " +
|
87
|
+
"(default: #{@options[:pid]})") { |file| @options[:pid] = file }
|
88
|
+
opts.on("-u", "--user NAME", "User to run daemon as (use with -g)") { |user| @options[:user] = user }
|
89
|
+
opts.on("-g", "--group NAME", "Group to run daemon as (use with -u)") { |group| @options[:group] = group }
|
90
|
+
opts.on( "--tag NAME", "Additional text to display in process listing") { |tag| @options[:tag] = tag }
|
91
|
+
|
92
|
+
opts.separator ""
|
93
|
+
opts.separator "Cluster options:"
|
94
|
+
|
95
|
+
opts.on("-s", "--servers NUM", "Number of servers to start") { |num| @options[:servers] = num.to_i }
|
96
|
+
opts.on("-o", "--only NUM", "Send command to only one server of the cluster") { |only| @options[:only] = only.to_i }
|
97
|
+
opts.on("-C", "--config FILE", "Load options from config file") { |file| @options[:config] = file }
|
98
|
+
opts.on( "--all [DIR]", "Send command to each config files in DIR") { |dir| @options[:all] = dir } if Thin.linux?
|
99
|
+
end
|
100
|
+
|
101
|
+
opts.separator ""
|
102
|
+
opts.separator "Tuning options:"
|
103
|
+
|
104
|
+
opts.on("-b", "--backend CLASS", "Backend to use, full classname") { |name| @options[:backend] = name }
|
105
|
+
opts.on("-t", "--timeout SEC", "Request or command timeout in sec " +
|
106
|
+
"(default: #{@options[:timeout]})") { |sec| @options[:timeout] = sec.to_i }
|
107
|
+
opts.on("-f", "--force", "Force the execution of the command") { @options[:force] = true }
|
108
|
+
opts.on( "--max-conns NUM", "Maximum number of connections " +
|
109
|
+
"(default: #{@options[:max_conns]})",
|
110
|
+
"Might require sudo to set higher then 1024") { |num| @options[:max_conns] = num.to_i } unless Thin.win?
|
111
|
+
opts.on( "--max-persistent-conns NUM",
|
112
|
+
"Maximum number of persistent connections",
|
113
|
+
"(default: #{@options[:max_persistent_conns]})") { |num| @options[:max_persistent_conns] = num.to_i }
|
114
|
+
opts.on( "--threaded", "Call the Rack application in threads " +
|
115
|
+
"[experimental]") { @options[:threaded] = true }
|
116
|
+
opts.on( "--no-epoll", "Disable the use of epoll") { @options[:no_epoll] = true } if Thin.linux?
|
117
|
+
|
118
|
+
opts.separator ""
|
119
|
+
opts.separator "Common options:"
|
120
|
+
|
121
|
+
opts.on_tail("-r", "--require FILE", "require the library") { |file| @options[:require] << file }
|
122
|
+
opts.on_tail("-D", "--debug", "Set debbuging on") { @options[:debug] = true }
|
123
|
+
opts.on_tail("-V", "--trace", "Set tracing on (log raw request/response)") { @options[:trace] = true }
|
124
|
+
opts.on_tail("-h", "--help", "Show this message") { puts opts; exit }
|
125
|
+
opts.on_tail('-v', '--version', "Show version") { puts Thin::SERVER; exit }
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
# Parse the options.
|
130
|
+
def parse!
|
131
|
+
parser.parse! @argv
|
132
|
+
@command = @argv.shift
|
133
|
+
@arguments = @argv
|
134
|
+
end
|
135
|
+
|
136
|
+
# Parse the current shell arguments and run the command.
|
137
|
+
# Exits on error.
|
138
|
+
def run!
|
139
|
+
if self.class.commands.include?(@command)
|
140
|
+
run_command
|
141
|
+
elsif @command.nil?
|
142
|
+
puts "Command required"
|
143
|
+
puts @parser
|
144
|
+
exit 1
|
145
|
+
else
|
146
|
+
abort "Unknown command: #{@command}. Use one of #{self.class.commands.join(', ')}"
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
# Send the command to the controller: single instance or cluster.
|
151
|
+
def run_command
|
152
|
+
load_options_from_config_file! unless CONFIGLESS_COMMANDS.include?(@command)
|
153
|
+
|
154
|
+
# PROGRAM_NAME is relative to the current directory, so make sure
|
155
|
+
# we store and expand it before changing directory.
|
156
|
+
Command.script = File.expand_path($PROGRAM_NAME)
|
157
|
+
|
158
|
+
# Change the current directory ASAP so that all relative paths are
|
159
|
+
# relative to this one.
|
160
|
+
Dir.chdir(@options[:chdir]) unless CONFIGLESS_COMMANDS.include?(@command)
|
161
|
+
|
162
|
+
@options[:require].each { |r| ruby_require r }
|
163
|
+
Logging.debug = @options[:debug]
|
164
|
+
Logging.trace = @options[:trace]
|
165
|
+
|
166
|
+
controller = case
|
167
|
+
when cluster? then Controllers::Cluster.new(@options)
|
168
|
+
when service? then Controllers::Service.new(@options)
|
169
|
+
else Controllers::Controller.new(@options)
|
170
|
+
end
|
171
|
+
|
172
|
+
if controller.respond_to?(@command)
|
173
|
+
begin
|
174
|
+
controller.send(@command, *@arguments)
|
175
|
+
rescue RunnerError => e
|
176
|
+
abort e.message
|
177
|
+
end
|
178
|
+
else
|
179
|
+
abort "Invalid options for command: #{@command}"
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
# +true+ if we're controlling a cluster.
|
184
|
+
def cluster?
|
185
|
+
@options[:only] || @options[:servers] || @options[:config]
|
186
|
+
end
|
187
|
+
|
188
|
+
# +true+ if we're acting a as system service.
|
189
|
+
def service?
|
190
|
+
@options.has_key?(:all) || @command == 'install'
|
191
|
+
end
|
192
|
+
|
193
|
+
private
|
194
|
+
def load_options_from_config_file!
|
195
|
+
if file = @options.delete(:config)
|
196
|
+
YAML.load_file(file).each { |key, value| @options[key.to_sym] = value }
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
def ruby_require(file)
|
201
|
+
if File.extname(file) == '.ru'
|
202
|
+
warn 'WARNING: Use the -R option to load a Rack config file'
|
203
|
+
@options[:rackup] = file
|
204
|
+
else
|
205
|
+
require file
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|