macournoyer-thin 1.0.1

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 (135) hide show
  1. data/CHANGELOG +233 -0
  2. data/COPYING +18 -0
  3. data/README +77 -0
  4. data/Rakefile +13 -0
  5. data/benchmark/abc +51 -0
  6. data/benchmark/benchmarker.rb +80 -0
  7. data/benchmark/runner +79 -0
  8. data/bin/thin +6 -0
  9. data/example/adapter.rb +32 -0
  10. data/example/config.ru +23 -0
  11. data/example/monit_sockets +20 -0
  12. data/example/monit_unixsock +20 -0
  13. data/example/myapp.rb +1 -0
  14. data/example/ramaze.ru +12 -0
  15. data/example/thin.god +80 -0
  16. data/example/thin_solaris_smf.erb +36 -0
  17. data/example/thin_solaris_smf.readme.txt +150 -0
  18. data/example/vlad.rake +64 -0
  19. data/ext/thin_parser/common.rl +55 -0
  20. data/ext/thin_parser/ext_help.h +14 -0
  21. data/ext/thin_parser/extconf.rb +6 -0
  22. data/ext/thin_parser/parser.c +452 -0
  23. data/ext/thin_parser/parser.h +49 -0
  24. data/ext/thin_parser/parser.rl +157 -0
  25. data/ext/thin_parser/thin.c +433 -0
  26. data/lib/rack/adapter/loader.rb +79 -0
  27. data/lib/rack/adapter/rails.rb +173 -0
  28. data/lib/rack/handler/thin.rb +18 -0
  29. data/lib/thin.rb +50 -0
  30. data/lib/thin/backends/base.rb +141 -0
  31. data/lib/thin/backends/swiftiply_client.rb +56 -0
  32. data/lib/thin/backends/tcp_server.rb +29 -0
  33. data/lib/thin/backends/unix_server.rb +51 -0
  34. data/lib/thin/command.rb +52 -0
  35. data/lib/thin/connection.rb +186 -0
  36. data/lib/thin/controllers/cluster.rb +127 -0
  37. data/lib/thin/controllers/controller.rb +183 -0
  38. data/lib/thin/controllers/service.rb +75 -0
  39. data/lib/thin/controllers/service.sh.erb +39 -0
  40. data/lib/thin/daemonizing.rb +163 -0
  41. data/lib/thin/headers.rb +39 -0
  42. data/lib/thin/logging.rb +54 -0
  43. data/lib/thin/request.rb +147 -0
  44. data/lib/thin/response.rb +99 -0
  45. data/lib/thin/runner.rb +208 -0
  46. data/lib/thin/server.rb +241 -0
  47. data/lib/thin/stats.html.erb +216 -0
  48. data/lib/thin/stats.rb +52 -0
  49. data/lib/thin/statuses.rb +43 -0
  50. data/lib/thin/version.rb +32 -0
  51. data/spec/backends/swiftiply_client_spec.rb +66 -0
  52. data/spec/backends/tcp_server_spec.rb +33 -0
  53. data/spec/backends/unix_server_spec.rb +37 -0
  54. data/spec/command_spec.rb +20 -0
  55. data/spec/configs/cluster.yml +9 -0
  56. data/spec/configs/single.yml +9 -0
  57. data/spec/connection_spec.rb +105 -0
  58. data/spec/controllers/cluster_spec.rb +212 -0
  59. data/spec/controllers/controller_spec.rb +129 -0
  60. data/spec/controllers/service_spec.rb +50 -0
  61. data/spec/daemonizing_spec.rb +192 -0
  62. data/spec/headers_spec.rb +40 -0
  63. data/spec/logging_spec.rb +46 -0
  64. data/spec/perf/request_perf_spec.rb +50 -0
  65. data/spec/perf/response_perf_spec.rb +19 -0
  66. data/spec/perf/server_perf_spec.rb +39 -0
  67. data/spec/rack/loader_spec.rb +29 -0
  68. data/spec/rack/rails_adapter_spec.rb +106 -0
  69. data/spec/rails_app/app/controllers/application.rb +10 -0
  70. data/spec/rails_app/app/controllers/simple_controller.rb +19 -0
  71. data/spec/rails_app/app/helpers/application_helper.rb +3 -0
  72. data/spec/rails_app/app/views/simple/index.html.erb +15 -0
  73. data/spec/rails_app/config/boot.rb +109 -0
  74. data/spec/rails_app/config/environment.rb +64 -0
  75. data/spec/rails_app/config/environments/development.rb +18 -0
  76. data/spec/rails_app/config/environments/production.rb +19 -0
  77. data/spec/rails_app/config/environments/test.rb +22 -0
  78. data/spec/rails_app/config/initializers/inflections.rb +10 -0
  79. data/spec/rails_app/config/initializers/mime_types.rb +5 -0
  80. data/spec/rails_app/config/routes.rb +35 -0
  81. data/spec/rails_app/public/404.html +30 -0
  82. data/spec/rails_app/public/422.html +30 -0
  83. data/spec/rails_app/public/500.html +30 -0
  84. data/spec/rails_app/public/dispatch.cgi +10 -0
  85. data/spec/rails_app/public/dispatch.fcgi +24 -0
  86. data/spec/rails_app/public/dispatch.rb +10 -0
  87. data/spec/rails_app/public/favicon.ico +0 -0
  88. data/spec/rails_app/public/images/rails.png +0 -0
  89. data/spec/rails_app/public/index.html +277 -0
  90. data/spec/rails_app/public/javascripts/application.js +2 -0
  91. data/spec/rails_app/public/javascripts/controls.js +963 -0
  92. data/spec/rails_app/public/javascripts/dragdrop.js +972 -0
  93. data/spec/rails_app/public/javascripts/effects.js +1120 -0
  94. data/spec/rails_app/public/javascripts/prototype.js +4225 -0
  95. data/spec/rails_app/public/robots.txt +5 -0
  96. data/spec/rails_app/script/about +3 -0
  97. data/spec/rails_app/script/console +3 -0
  98. data/spec/rails_app/script/destroy +3 -0
  99. data/spec/rails_app/script/generate +3 -0
  100. data/spec/rails_app/script/performance/benchmarker +3 -0
  101. data/spec/rails_app/script/performance/profiler +3 -0
  102. data/spec/rails_app/script/performance/request +3 -0
  103. data/spec/rails_app/script/plugin +3 -0
  104. data/spec/rails_app/script/process/inspector +3 -0
  105. data/spec/rails_app/script/process/reaper +3 -0
  106. data/spec/rails_app/script/process/spawner +3 -0
  107. data/spec/rails_app/script/runner +3 -0
  108. data/spec/rails_app/script/server +3 -0
  109. data/spec/request/mongrel_spec.rb +39 -0
  110. data/spec/request/parser_spec.rb +215 -0
  111. data/spec/request/persistent_spec.rb +35 -0
  112. data/spec/request/processing_spec.rb +45 -0
  113. data/spec/response_spec.rb +83 -0
  114. data/spec/runner_spec.rb +167 -0
  115. data/spec/server/builder_spec.rb +44 -0
  116. data/spec/server/pipelining_spec.rb +109 -0
  117. data/spec/server/robustness_spec.rb +34 -0
  118. data/spec/server/stopping_spec.rb +45 -0
  119. data/spec/server/swiftiply.yml +6 -0
  120. data/spec/server/swiftiply_spec.rb +32 -0
  121. data/spec/server/tcp_spec.rb +57 -0
  122. data/spec/server/threaded_spec.rb +27 -0
  123. data/spec/server/unix_socket_spec.rb +26 -0
  124. data/spec/server_spec.rb +96 -0
  125. data/spec/spec_helper.rb +219 -0
  126. data/tasks/announce.rake +22 -0
  127. data/tasks/deploy.rake +16 -0
  128. data/tasks/email.erb +30 -0
  129. data/tasks/ext.rake +42 -0
  130. data/tasks/gem.rake +108 -0
  131. data/tasks/rdoc.rake +25 -0
  132. data/tasks/site.rake +15 -0
  133. data/tasks/spec.rake +48 -0
  134. data/tasks/stats.rake +28 -0
  135. metadata +248 -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,147 @@
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
+ FORWARDED_FOR = 'HTTP_X_FORWARDED_FOR'.freeze
25
+ CONTENT_LENGTH = 'CONTENT_LENGTH'.freeze
26
+ CONNECTION = 'HTTP_CONNECTION'.freeze
27
+ KEEP_ALIVE_REGEXP = /\bkeep-alive\b/i.freeze
28
+ CLOSE_REGEXP = /\bclose\b/i.freeze
29
+
30
+ # Freeze some Rack header names
31
+ RACK_INPUT = 'rack.input'.freeze
32
+ RACK_VERSION = 'rack.version'.freeze
33
+ RACK_ERRORS = 'rack.errors'.freeze
34
+ RACK_MULTITHREAD = 'rack.multithread'.freeze
35
+ RACK_MULTIPROCESS = 'rack.multiprocess'.freeze
36
+ RACK_RUN_ONCE = 'rack.run_once'.freeze
37
+
38
+ # CGI-like request environment variables
39
+ attr_reader :env
40
+
41
+ # Unparsed data of the request
42
+ attr_reader :data
43
+
44
+ # Request body
45
+ attr_reader :body
46
+
47
+ def initialize
48
+ @parser = Thin::HttpParser.new
49
+ @data = ''
50
+ @nparsed = 0
51
+ @body = StringIO.new
52
+ @env = {
53
+ SERVER_SOFTWARE => SERVER,
54
+ SERVER_NAME => LOCALHOST,
55
+
56
+ # Rack stuff
57
+ RACK_INPUT => @body,
58
+
59
+ RACK_VERSION => VERSION::RACK,
60
+ RACK_ERRORS => STDERR,
61
+
62
+ RACK_MULTITHREAD => false,
63
+ RACK_MULTIPROCESS => false,
64
+ RACK_RUN_ONCE => false
65
+ }
66
+ end
67
+
68
+ # Parse a chunk of data into the request environment
69
+ # Raises a +InvalidRequest+ if invalid.
70
+ # Returns +true+ if the parsing is complete.
71
+ def parse(data)
72
+ if @parser.finished? # Header finished, can only be some more body
73
+ body << data
74
+ else # Parse more header using the super parser
75
+ @data << data
76
+ raise InvalidRequest, 'Header longer than allowed' if @data.size > MAX_HEADER
77
+
78
+ @nparsed = @parser.execute(@env, @data, @nparsed)
79
+
80
+ # Transfert to a tempfile if body is very big
81
+ move_body_to_tempfile if @parser.finished? && content_length > MAX_BODY
82
+ end
83
+
84
+
85
+ if finished? # Check if header and body are complete
86
+ @data = nil
87
+ @body.rewind
88
+ true # Request is fully parsed
89
+ else
90
+ false # Not finished, need more data
91
+ end
92
+ end
93
+
94
+ # +true+ if headers and body are finished parsing
95
+ def finished?
96
+ @parser.finished? && @body.size >= content_length
97
+ end
98
+
99
+ # Expected size of the body
100
+ def content_length
101
+ @env[CONTENT_LENGTH].to_i
102
+ end
103
+
104
+ # Returns +true+ if the client expect the connection to be persistent.
105
+ def persistent?
106
+ # Clients and servers SHOULD NOT assume that a persistent connection
107
+ # is maintained for HTTP versions less than 1.1 unless it is explicitly
108
+ # signaled. (http://www.w3.org/Protocols/rfc2616/rfc2616-sec8.html)
109
+ if @env[HTTP_VERSION] == HTTP_1_0
110
+ @env[CONNECTION] =~ KEEP_ALIVE_REGEXP
111
+
112
+ # HTTP/1.1 client intends to maintain a persistent connection unless
113
+ # a Connection header including the connection-token "close" was sent
114
+ # in the request
115
+ else
116
+ @env[CONNECTION].nil? || @env[CONNECTION] !~ CLOSE_REGEXP
117
+ end
118
+ end
119
+
120
+ def remote_address=(address)
121
+ @env[REMOTE_ADDR] = address
122
+ end
123
+
124
+ def forwarded_for
125
+ @env[FORWARDED_FOR]
126
+ end
127
+
128
+ def threaded=(value)
129
+ @env[RACK_MULTITHREAD] = value
130
+ end
131
+
132
+ # Close any resource used by the request
133
+ def close
134
+ @body.delete if @body.class == Tempfile
135
+ end
136
+
137
+ private
138
+ def move_body_to_tempfile
139
+ current_body = @body
140
+ current_body.rewind
141
+ @body = Tempfile.new(BODY_TMPFILE)
142
+ @body.binmode
143
+ @body << current_body.read
144
+ @env[RACK_INPUT] = @body
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,99 @@
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
+ @body.each do |chunk|
84
+ yield chunk
85
+ end
86
+ end
87
+
88
+ # Tell the client the connection should stay open
89
+ def persistent!
90
+ @persistent = true
91
+ end
92
+
93
+ # Persistent connection must be requested as keep-alive
94
+ # from the server and have a Content-Length.
95
+ def persistent?
96
+ @persistent && @headers.has_key?(CONTENT_LENGTH)
97
+ end
98
+ end
99
+ 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 "Unknown command: #{@command}. Use one of #{self.class.commands.join(', ')}"
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