thin 1.2.6-x86-mingw32

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.
Files changed (137) hide show
  1. data/CHANGELOG +273 -0
  2. data/COPYING +18 -0
  3. data/README +69 -0
  4. data/Rakefile +39 -0
  5. data/benchmark/abc +51 -0
  6. data/benchmark/benchmarker.rb +80 -0
  7. data/benchmark/runner +82 -0
  8. data/bin/thin +6 -0
  9. data/example/adapter.rb +32 -0
  10. data/example/async_app.ru +126 -0
  11. data/example/async_chat.ru +247 -0
  12. data/example/async_tailer.ru +100 -0
  13. data/example/config.ru +22 -0
  14. data/example/monit_sockets +20 -0
  15. data/example/monit_unixsock +20 -0
  16. data/example/myapp.rb +1 -0
  17. data/example/ramaze.ru +12 -0
  18. data/example/thin.god +80 -0
  19. data/example/thin_solaris_smf.erb +36 -0
  20. data/example/thin_solaris_smf.readme.txt +150 -0
  21. data/example/vlad.rake +64 -0
  22. data/ext/thin_parser/common.rl +55 -0
  23. data/ext/thin_parser/ext_help.h +14 -0
  24. data/ext/thin_parser/extconf.rb +6 -0
  25. data/ext/thin_parser/parser.c +1185 -0
  26. data/ext/thin_parser/parser.h +49 -0
  27. data/ext/thin_parser/parser.rl +157 -0
  28. data/ext/thin_parser/thin.c +436 -0
  29. data/lib/rack/adapter/loader.rb +91 -0
  30. data/lib/rack/adapter/rails.rb +180 -0
  31. data/lib/thin.rb +46 -0
  32. data/lib/thin/backends/base.rb +141 -0
  33. data/lib/thin/backends/swiftiply_client.rb +56 -0
  34. data/lib/thin/backends/tcp_server.rb +29 -0
  35. data/lib/thin/backends/unix_server.rb +51 -0
  36. data/lib/thin/command.rb +53 -0
  37. data/lib/thin/connection.rb +222 -0
  38. data/lib/thin/controllers/cluster.rb +178 -0
  39. data/lib/thin/controllers/controller.rb +182 -0
  40. data/lib/thin/controllers/service.rb +75 -0
  41. data/lib/thin/controllers/service.sh.erb +39 -0
  42. data/lib/thin/daemonizing.rb +176 -0
  43. data/lib/thin/headers.rb +39 -0
  44. data/lib/thin/logging.rb +54 -0
  45. data/lib/thin/request.rb +157 -0
  46. data/lib/thin/response.rb +101 -0
  47. data/lib/thin/runner.rb +212 -0
  48. data/lib/thin/server.rb +248 -0
  49. data/lib/thin/stats.html.erb +216 -0
  50. data/lib/thin/stats.rb +52 -0
  51. data/lib/thin/statuses.rb +43 -0
  52. data/lib/thin/version.rb +32 -0
  53. data/lib/thin_parser.so +0 -0
  54. data/spec/backends/swiftiply_client_spec.rb +66 -0
  55. data/spec/backends/tcp_server_spec.rb +33 -0
  56. data/spec/backends/unix_server_spec.rb +37 -0
  57. data/spec/command_spec.rb +25 -0
  58. data/spec/configs/cluster.yml +9 -0
  59. data/spec/configs/single.yml +9 -0
  60. data/spec/connection_spec.rb +106 -0
  61. data/spec/controllers/cluster_spec.rb +267 -0
  62. data/spec/controllers/controller_spec.rb +129 -0
  63. data/spec/controllers/service_spec.rb +50 -0
  64. data/spec/daemonizing_spec.rb +192 -0
  65. data/spec/headers_spec.rb +40 -0
  66. data/spec/logging_spec.rb +46 -0
  67. data/spec/perf/request_perf_spec.rb +50 -0
  68. data/spec/perf/response_perf_spec.rb +19 -0
  69. data/spec/perf/server_perf_spec.rb +39 -0
  70. data/spec/rack/loader_spec.rb +42 -0
  71. data/spec/rack/rails_adapter_spec.rb +106 -0
  72. data/spec/rails_app/app/controllers/application.rb +10 -0
  73. data/spec/rails_app/app/controllers/simple_controller.rb +19 -0
  74. data/spec/rails_app/app/helpers/application_helper.rb +3 -0
  75. data/spec/rails_app/app/views/simple/index.html.erb +15 -0
  76. data/spec/rails_app/config/boot.rb +109 -0
  77. data/spec/rails_app/config/environment.rb +64 -0
  78. data/spec/rails_app/config/environments/development.rb +18 -0
  79. data/spec/rails_app/config/environments/production.rb +19 -0
  80. data/spec/rails_app/config/environments/test.rb +22 -0
  81. data/spec/rails_app/config/initializers/inflections.rb +10 -0
  82. data/spec/rails_app/config/initializers/mime_types.rb +5 -0
  83. data/spec/rails_app/config/routes.rb +35 -0
  84. data/spec/rails_app/public/404.html +30 -0
  85. data/spec/rails_app/public/422.html +30 -0
  86. data/spec/rails_app/public/500.html +30 -0
  87. data/spec/rails_app/public/dispatch.cgi +10 -0
  88. data/spec/rails_app/public/dispatch.fcgi +24 -0
  89. data/spec/rails_app/public/dispatch.rb +10 -0
  90. data/spec/rails_app/public/favicon.ico +0 -0
  91. data/spec/rails_app/public/images/rails.png +0 -0
  92. data/spec/rails_app/public/index.html +277 -0
  93. data/spec/rails_app/public/javascripts/application.js +2 -0
  94. data/spec/rails_app/public/javascripts/controls.js +963 -0
  95. data/spec/rails_app/public/javascripts/dragdrop.js +972 -0
  96. data/spec/rails_app/public/javascripts/effects.js +1120 -0
  97. data/spec/rails_app/public/javascripts/prototype.js +4225 -0
  98. data/spec/rails_app/public/robots.txt +5 -0
  99. data/spec/rails_app/script/about +3 -0
  100. data/spec/rails_app/script/console +3 -0
  101. data/spec/rails_app/script/destroy +3 -0
  102. data/spec/rails_app/script/generate +3 -0
  103. data/spec/rails_app/script/performance/benchmarker +3 -0
  104. data/spec/rails_app/script/performance/profiler +3 -0
  105. data/spec/rails_app/script/performance/request +3 -0
  106. data/spec/rails_app/script/plugin +3 -0
  107. data/spec/rails_app/script/process/inspector +3 -0
  108. data/spec/rails_app/script/process/reaper +3 -0
  109. data/spec/rails_app/script/process/spawner +3 -0
  110. data/spec/rails_app/script/runner +3 -0
  111. data/spec/rails_app/script/server +3 -0
  112. data/spec/request/mongrel_spec.rb +39 -0
  113. data/spec/request/parser_spec.rb +243 -0
  114. data/spec/request/persistent_spec.rb +35 -0
  115. data/spec/request/processing_spec.rb +50 -0
  116. data/spec/response_spec.rb +91 -0
  117. data/spec/runner_spec.rb +168 -0
  118. data/spec/server/builder_spec.rb +44 -0
  119. data/spec/server/pipelining_spec.rb +110 -0
  120. data/spec/server/robustness_spec.rb +34 -0
  121. data/spec/server/stopping_spec.rb +55 -0
  122. data/spec/server/swiftiply.yml +6 -0
  123. data/spec/server/swiftiply_spec.rb +32 -0
  124. data/spec/server/tcp_spec.rb +57 -0
  125. data/spec/server/threaded_spec.rb +27 -0
  126. data/spec/server/unix_socket_spec.rb +26 -0
  127. data/spec/server_spec.rb +100 -0
  128. data/spec/spec_helper.rb +219 -0
  129. data/tasks/announce.rake +22 -0
  130. data/tasks/deploy.rake +13 -0
  131. data/tasks/email.erb +30 -0
  132. data/tasks/gem.rake +66 -0
  133. data/tasks/rdoc.rake +25 -0
  134. data/tasks/site.rake +15 -0
  135. data/tasks/spec.rake +43 -0
  136. data/tasks/stats.rake +28 -0
  137. metadata +219 -0
@@ -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
@@ -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
@@ -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
@@ -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