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.

Files changed (137) hide show
  1. data/CHANGELOG +263 -0
  2. data/COPYING +18 -0
  3. data/README +69 -0
  4. data/Rakefile +36 -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 +452 -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 +433 -0
  29. data/lib/rack/adapter/loader.rb +79 -0
  30. data/lib/rack/adapter/rails.rb +181 -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 +127 -0
  39. data/lib/thin/controllers/controller.rb +183 -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 +174 -0
  43. data/lib/thin/headers.rb +39 -0
  44. data/lib/thin/logging.rb +54 -0
  45. data/lib/thin/request.rb +153 -0
  46. data/lib/thin/response.rb +101 -0
  47. data/lib/thin/runner.rb +209 -0
  48. data/lib/thin/server.rb +247 -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 +235 -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 +29 -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 +215 -0
  114. data/spec/request/persistent_spec.rb +35 -0
  115. data/spec/request/processing_spec.rb +45 -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 +96 -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 +74 -0
  133. data/tasks/rdoc.rake +25 -0
  134. data/tasks/site.rake +15 -0
  135. data/tasks/spec.rake +49 -0
  136. data/tasks/stats.rake +28 -0
  137. metadata +246 -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,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
@@ -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