thin 1.2.6-x86-mingw32

Sign up to get free protection for your applications and to get access to all the features.
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