grockit-thin 0.8.2

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 (136) hide show
  1. data/CHANGELOG +220 -0
  2. data/COMMITTERS +3 -0
  3. data/COPYING +18 -0
  4. data/README +77 -0
  5. data/Rakefile +13 -0
  6. data/benchmark/abc +51 -0
  7. data/benchmark/benchmarker.rb +80 -0
  8. data/benchmark/runner +79 -0
  9. data/bin/thin +6 -0
  10. data/example/adapter.rb +32 -0
  11. data/example/config.ru +23 -0
  12. data/example/monit_sockets +20 -0
  13. data/example/monit_unixsock +20 -0
  14. data/example/myapp.rb +1 -0
  15. data/example/ramaze.ru +12 -0
  16. data/example/thin.god +80 -0
  17. data/example/thin_solaris_smf.erb +36 -0
  18. data/example/thin_solaris_smf.readme.txt +150 -0
  19. data/example/vlad.rake +61 -0
  20. data/ext/thin_parser/common.rl +55 -0
  21. data/ext/thin_parser/ext_help.h +14 -0
  22. data/ext/thin_parser/extconf.rb +6 -0
  23. data/ext/thin_parser/parser.c +1218 -0
  24. data/ext/thin_parser/parser.h +49 -0
  25. data/ext/thin_parser/parser.rl +159 -0
  26. data/ext/thin_parser/thin.c +433 -0
  27. data/lib/rack/adapter/loader.rb +78 -0
  28. data/lib/rack/adapter/rails.rb +167 -0
  29. data/lib/rack/handler/thin.rb +18 -0
  30. data/lib/thin.rb +49 -0
  31. data/lib/thin/backends/base.rb +135 -0
  32. data/lib/thin/backends/swiftiply_client.rb +56 -0
  33. data/lib/thin/backends/tcp_server.rb +29 -0
  34. data/lib/thin/backends/unix_server.rb +51 -0
  35. data/lib/thin/command.rb +52 -0
  36. data/lib/thin/connection.rb +178 -0
  37. data/lib/thin/controllers/cluster.rb +121 -0
  38. data/lib/thin/controllers/controller.rb +182 -0
  39. data/lib/thin/controllers/service.rb +75 -0
  40. data/lib/thin/controllers/service.sh.erb +39 -0
  41. data/lib/thin/daemonizing.rb +163 -0
  42. data/lib/thin/headers.rb +31 -0
  43. data/lib/thin/logging.rb +54 -0
  44. data/lib/thin/request.rb +144 -0
  45. data/lib/thin/response.rb +96 -0
  46. data/lib/thin/runner.rb +208 -0
  47. data/lib/thin/server.rb +241 -0
  48. data/lib/thin/stats.html.erb +216 -0
  49. data/lib/thin/stats.rb +52 -0
  50. data/lib/thin/statuses.rb +43 -0
  51. data/lib/thin/version.rb +32 -0
  52. data/spec/backends/swiftiply_client_spec.rb +66 -0
  53. data/spec/backends/tcp_server_spec.rb +33 -0
  54. data/spec/backends/unix_server_spec.rb +37 -0
  55. data/spec/command_spec.rb +20 -0
  56. data/spec/configs/cluster.yml +9 -0
  57. data/spec/configs/single.yml +9 -0
  58. data/spec/connection_spec.rb +105 -0
  59. data/spec/controllers/cluster_spec.rb +179 -0
  60. data/spec/controllers/controller_spec.rb +121 -0
  61. data/spec/controllers/service_spec.rb +50 -0
  62. data/spec/daemonizing_spec.rb +192 -0
  63. data/spec/headers_spec.rb +29 -0
  64. data/spec/logging_spec.rb +46 -0
  65. data/spec/perf/request_perf_spec.rb +50 -0
  66. data/spec/perf/response_perf_spec.rb +19 -0
  67. data/spec/perf/server_perf_spec.rb +39 -0
  68. data/spec/rack/loader_spec.rb +29 -0
  69. data/spec/rack/rails_adapter_spec.rb +106 -0
  70. data/spec/rails_app/app/controllers/application.rb +10 -0
  71. data/spec/rails_app/app/controllers/simple_controller.rb +19 -0
  72. data/spec/rails_app/app/helpers/application_helper.rb +3 -0
  73. data/spec/rails_app/app/views/simple/index.html.erb +15 -0
  74. data/spec/rails_app/config/boot.rb +109 -0
  75. data/spec/rails_app/config/environment.rb +64 -0
  76. data/spec/rails_app/config/environments/development.rb +18 -0
  77. data/spec/rails_app/config/environments/production.rb +19 -0
  78. data/spec/rails_app/config/environments/test.rb +22 -0
  79. data/spec/rails_app/config/initializers/inflections.rb +10 -0
  80. data/spec/rails_app/config/initializers/mime_types.rb +5 -0
  81. data/spec/rails_app/config/routes.rb +35 -0
  82. data/spec/rails_app/public/404.html +30 -0
  83. data/spec/rails_app/public/422.html +30 -0
  84. data/spec/rails_app/public/500.html +30 -0
  85. data/spec/rails_app/public/dispatch.cgi +10 -0
  86. data/spec/rails_app/public/dispatch.fcgi +24 -0
  87. data/spec/rails_app/public/dispatch.rb +10 -0
  88. data/spec/rails_app/public/favicon.ico +0 -0
  89. data/spec/rails_app/public/images/rails.png +0 -0
  90. data/spec/rails_app/public/index.html +277 -0
  91. data/spec/rails_app/public/javascripts/application.js +2 -0
  92. data/spec/rails_app/public/javascripts/controls.js +963 -0
  93. data/spec/rails_app/public/javascripts/dragdrop.js +972 -0
  94. data/spec/rails_app/public/javascripts/effects.js +1120 -0
  95. data/spec/rails_app/public/javascripts/prototype.js +4225 -0
  96. data/spec/rails_app/public/robots.txt +5 -0
  97. data/spec/rails_app/script/about +3 -0
  98. data/spec/rails_app/script/console +3 -0
  99. data/spec/rails_app/script/destroy +3 -0
  100. data/spec/rails_app/script/generate +3 -0
  101. data/spec/rails_app/script/performance/benchmarker +3 -0
  102. data/spec/rails_app/script/performance/profiler +3 -0
  103. data/spec/rails_app/script/performance/request +3 -0
  104. data/spec/rails_app/script/plugin +3 -0
  105. data/spec/rails_app/script/process/inspector +3 -0
  106. data/spec/rails_app/script/process/reaper +3 -0
  107. data/spec/rails_app/script/process/spawner +3 -0
  108. data/spec/rails_app/script/runner +3 -0
  109. data/spec/rails_app/script/server +3 -0
  110. data/spec/request/mongrel_spec.rb +39 -0
  111. data/spec/request/parser_spec.rb +191 -0
  112. data/spec/request/persistent_spec.rb +35 -0
  113. data/spec/request/processing_spec.rb +45 -0
  114. data/spec/response_spec.rb +76 -0
  115. data/spec/runner_spec.rb +167 -0
  116. data/spec/server/builder_spec.rb +44 -0
  117. data/spec/server/pipelining_spec.rb +109 -0
  118. data/spec/server/robustness_spec.rb +34 -0
  119. data/spec/server/stopping_spec.rb +45 -0
  120. data/spec/server/swiftiply.yml +6 -0
  121. data/spec/server/swiftiply_spec.rb +32 -0
  122. data/spec/server/tcp_spec.rb +57 -0
  123. data/spec/server/threaded_spec.rb +27 -0
  124. data/spec/server/unix_socket_spec.rb +26 -0
  125. data/spec/server_spec.rb +96 -0
  126. data/spec/spec_helper.rb +219 -0
  127. data/tasks/announce.rake +22 -0
  128. data/tasks/deploy.rake +16 -0
  129. data/tasks/email.erb +30 -0
  130. data/tasks/ext.rake +42 -0
  131. data/tasks/gem.rake +102 -0
  132. data/tasks/rdoc.rake +25 -0
  133. data/tasks/site.rake +15 -0
  134. data/tasks/spec.rake +48 -0
  135. data/tasks/stats.rake +28 -0
  136. metadata +240 -0
@@ -0,0 +1,31 @@
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
+ @out << HEADER_FORMAT % [key, value]
20
+ end
21
+ end
22
+
23
+ def has_key?(key)
24
+ @sent[key]
25
+ end
26
+
27
+ def to_s
28
+ @out.join
29
+ end
30
+ end
31
+ 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,144 @@
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
+ HTTP_VERSION = 'HTTP_VERSION'.freeze
20
+ HTTP_1_0 = 'HTTP/1.0'.freeze
21
+ REMOTE_ADDR = 'REMOTE_ADDR'.freeze
22
+ FORWARDED_FOR = 'HTTP_X_FORWARDED_FOR'.freeze
23
+ CONTENT_LENGTH = 'CONTENT_LENGTH'.freeze
24
+ CONNECTION = 'HTTP_CONNECTION'.freeze
25
+ KEEP_ALIVE_REGEXP = /\bkeep-alive\b/i.freeze
26
+ CLOSE_REGEXP = /\bclose\b/i.freeze
27
+
28
+ # Freeze some Rack header names
29
+ RACK_INPUT = 'rack.input'.freeze
30
+ RACK_VERSION = 'rack.version'.freeze
31
+ RACK_ERRORS = 'rack.errors'.freeze
32
+ RACK_MULTITHREAD = 'rack.multithread'.freeze
33
+ RACK_MULTIPROCESS = 'rack.multiprocess'.freeze
34
+ RACK_RUN_ONCE = 'rack.run_once'.freeze
35
+
36
+ # CGI-like request environment variables
37
+ attr_reader :env
38
+
39
+ # Unparsed data of the request
40
+ attr_reader :data
41
+
42
+ # Request body
43
+ attr_reader :body
44
+
45
+ def initialize
46
+ @parser = HttpParser.new
47
+ @data = ''
48
+ @nparsed = 0
49
+ @body = StringIO.new
50
+ @env = {
51
+ SERVER_SOFTWARE => SERVER,
52
+
53
+ # Rack stuff
54
+ RACK_INPUT => @body,
55
+
56
+ RACK_VERSION => VERSION::RACK,
57
+ RACK_ERRORS => STDERR,
58
+
59
+ RACK_MULTITHREAD => false,
60
+ RACK_MULTIPROCESS => false,
61
+ RACK_RUN_ONCE => false
62
+ }
63
+ end
64
+
65
+ # Parse a chunk of data into the request environment
66
+ # Raises a +InvalidRequest+ if invalid.
67
+ # Returns +true+ if the parsing is complete.
68
+ def parse(data)
69
+ if @parser.finished? # Header finished, can only be some more body
70
+ body << data
71
+ else # Parse more header using the super parser
72
+ @data << data
73
+ raise InvalidRequest, 'Header longer than allowed' if @data.size > MAX_HEADER
74
+
75
+ @nparsed = @parser.execute(@env, @data, @nparsed)
76
+
77
+ # Transfert to a tempfile if body is very big
78
+ move_body_to_tempfile if @parser.finished? && content_length > MAX_BODY
79
+ end
80
+
81
+
82
+ if finished? # Check if header and body are complete
83
+ @data = nil
84
+ @body.rewind
85
+ true # Request is fully parsed
86
+ else
87
+ false # Not finished, need more data
88
+ end
89
+ end
90
+
91
+ # +true+ if headers and body are finished parsing
92
+ def finished?
93
+ @parser.finished? && @body.size >= content_length
94
+ end
95
+
96
+ # Expected size of the body
97
+ def content_length
98
+ @env[CONTENT_LENGTH].to_i
99
+ end
100
+
101
+ # Returns +true+ if the client expect the connection to be persistent.
102
+ def persistent?
103
+ # Clients and servers SHOULD NOT assume that a persistent connection
104
+ # is maintained for HTTP versions less than 1.1 unless it is explicitly
105
+ # signaled. (http://www.w3.org/Protocols/rfc2616/rfc2616-sec8.html)
106
+ if @env[HTTP_VERSION] == HTTP_1_0
107
+ @env[CONNECTION] =~ KEEP_ALIVE_REGEXP
108
+
109
+ # HTTP/1.1 client intends to maintain a persistent connection unless
110
+ # a Connection header including the connection-token "close" was sent
111
+ # in the request
112
+ else
113
+ @env[CONNECTION].nil? || @env[CONNECTION] !~ CLOSE_REGEXP
114
+ end
115
+ end
116
+
117
+ def remote_address=(address)
118
+ @env[REMOTE_ADDR] = address
119
+ end
120
+
121
+ def forwarded_for
122
+ @env[FORWARDED_FOR]
123
+ end
124
+
125
+ def threaded=(value)
126
+ @env[RACK_MULTITHREAD] = value
127
+ end
128
+
129
+ # Close any resource used by the request
130
+ def close
131
+ @body.delete if @body.class == Tempfile
132
+ end
133
+
134
+ private
135
+ def move_body_to_tempfile
136
+ current_body = @body
137
+ current_body.rewind
138
+ @body = Tempfile.new(BODY_TMPFILE)
139
+ @body.binmode
140
+ @body << current_body.read
141
+ @env[RACK_INPUT] = @body
142
+ end
143
+ end
144
+ end
@@ -0,0 +1,96 @@
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
+ def headers=(key_value_pairs)
46
+ key_value_pairs.each do |k, vs|
47
+ vs.each { |v| @headers[k] = v.chomp }
48
+ end
49
+ end
50
+
51
+ else
52
+
53
+ # Ruby 1.9 doesn't have a String#each anymore.
54
+ # Rack spec doesn't take care of that yet, for now we just use
55
+ # +each+ but fallback to +each_line+ on strings.
56
+ # I wish we could remove that condition.
57
+ # To be reviewed when a new Rack spec comes out.
58
+ def headers=(key_value_pairs)
59
+ key_value_pairs.each do |k, vs|
60
+ if vs.is_a?(String)
61
+ vs.each_line { |v| @headers[k] = v.chomp }
62
+ else
63
+ vs.each { |v| @headers[k] = v.chomp }
64
+ end
65
+ end
66
+ end
67
+
68
+ end
69
+
70
+ # Close any resource used by the response
71
+ def close
72
+ @body.close if @body.respond_to?(:close)
73
+ end
74
+
75
+ # Yields each chunk of the response.
76
+ # To control the size of each chunk
77
+ # define your own +each+ method on +body+.
78
+ def each
79
+ yield head
80
+ @body.each do |chunk|
81
+ yield chunk
82
+ end
83
+ end
84
+
85
+ # Tell the client the connection should stay open
86
+ def persistent!
87
+ @persistent = true
88
+ end
89
+
90
+ # Persistent connection must be requested as keep-alive
91
+ # from the server and have a Content-Length.
92
+ def persistent?
93
+ @persistent && @headers.has_key?(CONTENT_LENGTH)
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,208 @@
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
+
91
+ opts.separator ""
92
+ opts.separator "Cluster options:"
93
+
94
+ opts.on("-s", "--servers NUM", "Number of servers to start") { |num| @options[:servers] = num.to_i }
95
+ opts.on("-o", "--only NUM", "Send command to only one server of the cluster") { |only| @options[:only] = only }
96
+ opts.on("-C", "--config FILE", "Load options from config file") { |file| @options[:config] = file }
97
+ opts.on( "--all [DIR]", "Send command to each config files in DIR") { |dir| @options[:all] = dir } if Thin.linux?
98
+ end
99
+
100
+ opts.separator ""
101
+ opts.separator "Tuning options:"
102
+
103
+ opts.on("-b", "--backend CLASS", "Backend to use, full classname") { |name| @options[:backend] = name }
104
+ opts.on("-t", "--timeout SEC", "Request or command timeout in sec " +
105
+ "(default: #{@options[:timeout]})") { |sec| @options[:timeout] = sec.to_i }
106
+ opts.on("-f", "--force", "Force the execution of the command") { @options[:force] = true }
107
+ opts.on( "--max-conns NUM", "Maximum number of connections " +
108
+ "(default: #{@options[:max_conns]})",
109
+ "Might require sudo to set higher then 1024") { |num| @options[:max_conns] = num.to_i } unless Thin.win?
110
+ opts.on( "--max-persistent-conns NUM",
111
+ "Maximum number of persistent connections",
112
+ "(default: #{@options[:max_persistent_conns]})") { |num| @options[:max_persistent_conns] = num.to_i }
113
+ opts.on( "--threaded", "Call the Rack application in threads " +
114
+ "[experimental]") { @options[:threaded] = true }
115
+ opts.on( "--no-epoll", "Disable the use of epoll") { @options[:no_epoll] = true } if Thin.linux?
116
+
117
+ opts.separator ""
118
+ opts.separator "Common options:"
119
+
120
+ opts.on_tail("-r", "--require FILE", "require the library") { |file| @options[:require] << file }
121
+ opts.on_tail("-D", "--debug", "Set debbuging on") { @options[:debug] = true }
122
+ opts.on_tail("-V", "--trace", "Set tracing on (log raw request/response)") { @options[:trace] = true }
123
+ opts.on_tail("-h", "--help", "Show this message") { puts opts; exit }
124
+ opts.on_tail('-v', '--version', "Show version") { puts Thin::SERVER; exit }
125
+ end
126
+ end
127
+
128
+ # Parse the options.
129
+ def parse!
130
+ parser.parse! @argv
131
+ @command = @argv.shift
132
+ @arguments = @argv
133
+ end
134
+
135
+ # Parse the current shell arguments and run the command.
136
+ # Exits on error.
137
+ def run!
138
+ if self.class.commands.include?(@command)
139
+ run_command
140
+ elsif @command.nil?
141
+ puts "Command required"
142
+ puts @parser
143
+ exit 1
144
+ else
145
+ abort "Invalid command: #{@command}"
146
+ end
147
+ end
148
+
149
+ # Send the command to the controller: single instance or cluster.
150
+ def run_command
151
+ load_options_from_config_file! unless CONFIGLESS_COMMANDS.include?(@command)
152
+
153
+ # PROGRAM_NAME is relative to the current directory, so make sure
154
+ # we store and expand it before changing directory.
155
+ Command.script = File.expand_path($PROGRAM_NAME)
156
+
157
+ # Change the current directory ASAP so that all relative paths are
158
+ # relative to this one.
159
+ Dir.chdir(@options[:chdir]) unless CONFIGLESS_COMMANDS.include?(@command)
160
+
161
+ @options[:require].each { |r| ruby_require r }
162
+ Logging.debug = @options[:debug]
163
+ Logging.trace = @options[:trace]
164
+
165
+ controller = case
166
+ when cluster? then Controllers::Cluster.new(@options)
167
+ when service? then Controllers::Service.new(@options)
168
+ else Controllers::Controller.new(@options)
169
+ end
170
+
171
+ if controller.respond_to?(@command)
172
+ begin
173
+ controller.send(@command, *@arguments)
174
+ rescue RunnerError => e
175
+ abort e.message
176
+ end
177
+ else
178
+ abort "Invalid options for command: #{@command}"
179
+ end
180
+ end
181
+
182
+ # +true+ if we're controlling a cluster.
183
+ def cluster?
184
+ @options[:only] || @options[:servers] || @options[:config]
185
+ end
186
+
187
+ # +true+ if we're acting a as system service.
188
+ def service?
189
+ @options.has_key?(:all) || @command == 'install'
190
+ end
191
+
192
+ private
193
+ def load_options_from_config_file!
194
+ if file = @options.delete(:config)
195
+ YAML.load_file(file).each { |key, value| @options[key.to_sym] = value }
196
+ end
197
+ end
198
+
199
+ def ruby_require(file)
200
+ if File.extname(file) == '.ru'
201
+ warn 'WARNING: Use the -R option to load a Rack config file'
202
+ @options[:rackup] = file
203
+ else
204
+ require file
205
+ end
206
+ end
207
+ end
208
+ end