public
Description: A very fast & simple Ruby web server
Homepage: http://code.macournoyer.com/thin/
Clone URL: git://github.com/macournoyer/thin.git
Search Repo:
Refactoring thin script in several classes:
* Runner: main CLI runner, parse options and send the command to the 
controller.
* Controller: Configure and control a server or cluster (Cluster class now 
inherits from Controller).
macournoyer (author)
Fri Feb 01 20:43:41 -0800 2008
commit  401ade74083354c0a180324a00d18c50e7c5f88c
tree    7e330c734283629bc53547d1d69409620916abb3
parent  2a7e1025d3e119720dac8fe61df82d2e19fb8434
...
1
2
 
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
 
...
1
 
2
3
4
 
 
5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
0
@@ -1,169 +1,7 @@
0
 #!/usr/bin/env ruby
0
-# <tt>thin start</tt>: Starts the Rails app in the current directory.
0
+# Thin command line interface script.
0
 # Run <tt>thin -h</tt> to get more usage.
0
 require File.dirname(__FILE__) + '/../lib/thin'
0
-require 'optparse'
0
-require 'yaml'
0
 
0
-COMMANDS = %w(start stop restart config)
0
-
0
-# Default options values
0
-options = {
0
- :chdir => Dir.pwd,
0
- :environment => 'development',
0
- :address => '0.0.0.0',
0
- :port => 3000,
0
- :timeout => 60,
0
- :log => 'log/thin.log',
0
- :pid => 'tmp/pids/thin.pid',
0
- :servers => 1 # no cluster
0
-}
0
-
0
-# NOTE: If you add an option here make sure the key in the +options+ hash is the
0
-# same as the name of the command line option.
0
-# +option+ keys are use to build the command line to launch a cluster,
0
-# see <tt>lib/thin/cluster.rb</tt>.
0
-opts = OptionParser.new do |opts|
0
- opts.banner = "Usage: thin [options] #{COMMANDS.join('|')}"
0
-
0
- opts.separator ""
0
- opts.separator "Server options:"
0
-
0
- opts.on("-a", "--address HOST", "bind to HOST address (default: 0.0.0.0)") { |host| options[:address] = host }
0
- opts.on("-p", "--port PORT", "use PORT (default: 3000)") { |port| options[:port] = port.to_i }
0
- opts.on("-S", "--socket PATH", "bind to unix domain socket") { |file| options[:socket] = file }
0
- opts.on("-e", "--environment ENV", "Rails environment (default: development)") { |env| options[:environment] = env }
0
- opts.on("-c", "--chdir PATH", "Change to dir before starting") { |dir| options[:chdir] = File.expand_path(dir) }
0
- opts.on("-t", "--timeout SEC", "Request or command timeout in sec",
0
- "(default: #{options[:timeout]})") { |sec| options[:timeout] = sec.to_i }
0
- opts.on( "--prefix PATH", "Mount the app under PATH (start with /)") { |path| options[:prefix] = path }
0
- opts.on( "--stats PATH", "Mount the Stats adapter under PATH") { |path| options[:stats] = path }
0
-
0
- opts.separator ""
0
- opts.separator "Daemon options:"
0
-
0
- opts.on("-d", "--daemonize", "Run daemonized in the background") { options[:daemonize] = true }
0
- opts.on("-l", "--log FILE", "File to redirect output",
0
- "(default: #{options[:log]})") { |file| options[:log] = file }
0
- opts.on("-P", "--pid FILE", "File to store PID",
0
- "(default: #{options[:pid]})") { |file| options[:pid] = file }
0
- opts.on("-u", "--user NAME", "User to run daemon as (use with -g)") { |user| options[:user] = user }
0
- opts.on("-g", "--group NAME", "Group to run daemon as (use with -u)") { |group| options[:group] = group }
0
-
0
- opts.separator ""
0
- opts.separator "Cluster options:"
0
-
0
- opts.on("-s", "--servers NUM", "Number of servers to start",
0
- "set a value >1 to start a cluster") { |num| options[:servers] = num.to_i }
0
- opts.on("-o", "--only NUM", "Send command to only one server of the cluster") { |only| options[:only] = only }
0
- opts.on("-C", "--config PATH", "Load options from a config file") { |file| options[:config] = file }
0
-
0
- opts.separator ""
0
- opts.separator "Common options:"
0
-
0
- opts.on_tail("-D", "--debug", "Set debbuging on") { $DEBUG = true }
0
- opts.on_tail("-V", "--trace", "Set tracing on") { $TRACE = true }
0
- opts.on_tail("-h", "--help", "Show this message") { puts opts; exit }
0
- opts.on_tail('-v', '--version', "Show version") { puts Thin::SERVER; exit }
0
-end
0
-
0
-
0
-# == Utilities
0
-
0
-def cluster?(options)
0
- options[:only] || (options[:servers] && options[:servers] > 1)
0
-end
0
-
0
-def load_options_from_config_file!(options)
0
- if file = options.delete(:config)
0
- YAML.load_file(file).each { |key, value| options[key.to_sym] = value }
0
- end
0
-end
0
-
0
-
0
-# == Commands definitions
0
-
0
-def start(options)
0
- load_options_from_config_file! options
0
-
0
- if cluster?(options)
0
- Thin::Cluster.new(options).start
0
- else
0
- if options[:socket]
0
- server = Thin::Server.new(options[:socket])
0
- else
0
- server = Thin::Server.new(options[:address], options[:port])
0
- end
0
-
0
- server.pid_file = options[:pid]
0
- server.log_file = options[:log]
0
- server.timeout = options[:timeout]
0
-
0
- if options[:daemonize]
0
- server.daemonize
0
- server.change_privilege options[:user], options[:group] if options[:user] && options[:group]
0
- end
0
-
0
- server.app = Rack::Adapter::Rails.new(options.merge(:root => options[:chdir]))
0
-
0
- # If a prefix is required, wrap in Rack URL mapper
0
- server.app = Rack::URLMap.new(options[:prefix] => server.app) if options[:prefix]
0
-
0
- # If a stats are required, wrap in Stats adapter
0
- server.app = Thin::Stats::Adapter.new(server.app, options[:stats]) if options[:stats]
0
-
0
- # Register restart procedure
0
- server.on_restart { Thin::Command.run(:start, options) }
0
-
0
- server.start
0
- end
0
-end
0
-
0
-def stop(options)
0
- load_options_from_config_file! options
0
-
0
- if cluster?(options)
0
- Thin::Cluster.new(options).stop
0
- else
0
- Thin::Server.kill(options[:pid], options[:timeout])
0
- end
0
-end
0
-
0
-def restart(options)
0
- load_options_from_config_file! options
0
-
0
- if cluster?(options)
0
- Thin::Cluster.new(options).restart
0
- else
0
- Thin::Server.restart(options[:pid])
0
- end
0
-end
0
-
0
-def config(options)
0
- config_file = options.delete(:config) || abort('config option required')
0
-
0
- # Stringify keys
0
- options.keys.each { |o| options[o.to_s] = options.delete(o) }
0
-
0
- File.open(config_file, 'w') { |f| f << options.to_yaml }
0
- puts "Wrote configuration to #{config_file}"
0
-end
0
-
0
-
0
-# == Runs the command
0
-
0
-opts.parse! ARGV
0
-command = ARGV[0]
0
-
0
-Dir.chdir(options[:chdir])
0
-
0
-if COMMANDS.include?(command)
0
- send(command, options)
0
-elsif command.nil?
0
- puts "Command required"
0
- puts opts
0
- exit 1
0
-else
0
- abort "Invalid command : #{command}"
0
-end
0
+Thin::Runner.new(ARGV).run!
...
14
15
16
 
17
18
19
20
21
 
22
23
24
...
14
15
16
17
18
19
20
21
22
23
24
25
26
0
@@ -14,11 +14,13 @@
0
   autoload :Cluster, 'thin/cluster'
0
   autoload :Command, 'thin/command'
0
   autoload :Connection, 'thin/connection'
0
+ autoload :Controller, 'thin/controller'
0
   autoload :Daemonizable, 'thin/daemonizing'
0
   autoload :Logging, 'thin/logging'
0
   autoload :Headers, 'thin/headers'
0
   autoload :Request, 'thin/request'
0
   autoload :Response, 'thin/response'
0
+ autoload :Runner, 'thin/runner'
0
   autoload :Server, 'thin/server'
0
   autoload :Stats, 'thin/stats'
0
 end
...
3
4
5
6
7
8
9
10
11
12
 
13
14
15
16
17
18
 
19
20
21
22
23
24
25
26
27
...
3
4
5
 
 
 
 
 
 
 
6
7
8
 
 
 
 
9
10
11
12
13
14
 
15
16
17
0
@@ -3,25 +3,15 @@
0
   # * Generate start and stop commands and run them.
0
   # * Inject the port or socket number in the pid and log filenames.
0
   # Servers are started throught the +thin+ command-line script.
0
- class Cluster
0
- include Logging
0
-
0
- # Path to the +thin+ script used to control the servers.
0
- # Leave this to default to use the one in the path.
0
- attr_accessor :script
0
-
0
+ class Cluster < Controller
0
     # Number of servers in the cluster.
0
     attr_accessor :size
0
-
0
- # Command line options passed to the thin script
0
- attr_accessor :options
0
-
0
+
0
     # Create a new cluster of servers launched using +options+.
0
     def initialize(options)
0
       @options = options.merge(:daemonize => true)
0
       @size = @options.delete(:servers)
0
       @only = @options.delete(:only)
0
- @script = $PROGRAM_NAME
0
       
0
       if socket
0
         @options.delete(:address)
...
3
4
5
6
7
8
 
 
 
 
 
 
9
10
11
12
13
14
15
16
...
34
35
36
37
 
38
39
40
...
3
4
5
 
 
 
6
7
8
9
10
11
12
13
14
15
 
16
17
18
...
36
37
38
 
39
40
41
42
0
@@ -3,14 +3,16 @@
0
   class Command
0
     include Logging
0
     
0
- # Path to the +thin+ script used to control the servers.
0
- # Leave this to default to use the one in the path.
0
- attr_accessor :script
0
+ class << self
0
+ # Path to the +thin+ script used to control the servers.
0
+ # Leave this to default to use the one in the path.
0
+ attr_accessor :script
0
+ @script = $PROGRAM_NAME
0
+ end
0
     
0
     def initialize(name, options={})
0
       @name = name
0
       @options = options
0
- @script = $PROGRAM_NAME
0
     end
0
     
0
     def self.run(*args)
0
@@ -34,7 +36,7 @@
0
         else "--#{name.to_s.tr('_', '-')}=#{value.inspect}"
0
         end
0
       end
0
- "#{@script} #{@name} #{shellified_options.compact.join(' ')}"
0
+ "#{self.class.script} #{@name} #{shellified_options.compact.join(' ')}"
0
     end
0
   end
0
 end
...
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
0
@@ -1 +1,76 @@
0
+require 'yaml'
0
+
0
+module Thin
0
+ # Raised when a mandatory option is missing to run a command.
0
+ class OptionRequired < RuntimeError
0
+ def initialize(option)
0
+ super("#{option} option required")
0
+ end
0
+ end
0
+
0
+ # Control a Thin server.
0
+ # Allow to start, stop, restart and configure a single thin server.
0
+ class Controller
0
+ include Logging
0
+
0
+ # Command line options passed to the thin script
0
+ attr_accessor :options
0
+
0
+ def initialize(options)
0
+ @options = options
0
+ end
0
+
0
+ def start
0
+ if @options[:socket]
0
+ server = Server.new(@options[:socket])
0
+ else
0
+ server = Server.new(@options[:address], @options[:port])
0
+ end
0
+
0
+ server.pid_file = @options[:pid]
0
+ server.log_file = @options[:log]
0
+ server.timeout = @options[:timeout]
0
+
0
+ if @options[:daemonize]
0
+ server.daemonize
0
+ server.change_privilege @options[:user], @options[:group] if @options[:user] && @options[:group]
0
+ end
0
+
0
+ server.app = Rack::Adapter::Rails.new(@options.merge(:root => @options[:chdir]))
0
+
0
+ # If a prefix is required, wrap in Rack URL mapper
0
+ server.app = Rack::URLMap.new(@options[:prefix] => server.app) if @options[:prefix]
0
+
0
+ # If a stats are required, wrap in Stats adapter
0
+ server.app = Stats::Adapter.new(server.app, @options[:stats]) if @options[:stats]
0
+
0
+ # Register restart procedure
0
+ server.on_restart { Command.run(:start, @options) }
0
+
0
+ server.start
0
+ end
0
+
0
+ def stop
0
+ raise OptionRequired, :pid unless @options[:pid]
0
+
0
+ Server.kill(@options[:pid], @options[:timeout] || 60)
0
+ end
0
+
0
+ def restart
0
+ raise OptionRequired, :pid unless @options[:pid]
0
+
0
+ Server.restart(@options[:pid])
0
+ end
0
+
0
+ def config
0
+ config_file = @options.delete(:config) || raise(OptionRequired, :config)
0
+
0
+ # Stringify keys
0
+ @options.keys.each { |o| @options[o.to_s] = @options.delete(o) }
0
+
0
+ File.open(config_file, 'w') { |f| f << @options.to_yaml }
0
+ log ">> Wrote configuration to #{config_file}"
0
+ end
0
+ end
0
+end
...
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
0
@@ -1 +1,125 @@
0
+require 'optparse'
0
+require 'yaml'
0
+
0
+module Thin
0
+ # CLI runner.
0
+ # Parse options and send command to the correct Controller.
0
+ class Runner
0
+ COMMANDS = %w(start stop restart config)
0
+
0
+ def initialize(argv)
0
+ @argv = argv
0
+
0
+ # Default options values
0
+ @options = {
0
+ :chdir => Dir.pwd,
0
+ :environment => 'development',
0
+ :address => '0.0.0.0',
0
+ :port => 3000,
0
+ :timeout => 60,
0
+ :log => 'log/thin.log',
0
+ :pid => 'tmp/pids/thin.pid',
0
+ :servers => 1 # no cluster
0
+ }
0
+
0
+ @parser = parser
0
+ end
0
+
0
+ def parser
0
+ # NOTE: If you add an option here make sure the key in the +options+ hash is the
0
+ # same as the name of the command line option.
0
+ # +option+ keys are used to build the command line to launch other processes,
0
+ # see <tt>lib/thin/command.rb</tt>.
0
+ OptionParser.new do |opts|
0
+ opts.banner = "Usage: thin [options] #{COMMANDS.join('|')}"
0
+
0
+ opts.separator ""
0
+ opts.separator "Server options:"
0
+
0
+ opts.on("-a", "--address HOST", "bind to HOST address " +
0
+ "(default: #{@options[:address]})") { |host| @options[:address] = host }
0
+ opts.on("-p", "--port PORT", "use PORT (default: #{@options[:port]})") { |port| @options[:port] = port.to_i }
0
+ opts.on("-S", "--socket PATH", "bind to unix domain socket") { |file| @options[:socket] = file }
0
+ opts.on("-e", "--environment ENV", "Rails environment " +
0
+ "(default: #{@options[:environment]})") { |env| @options[:environment] = env }
0
+ opts.on("-c", "--chdir PATH", "Change to dir before starting") { |dir| @options[:chdir] = File.expand_path(dir) }
0
+ opts.on("-t", "--timeout SEC", "Request or command timeout in sec " +
0
+ "(default: #{@options[:timeout]})") { |sec| @options[:timeout] = sec.to_i }
0
+ opts.on( "--prefix PATH", "Mount the app under PATH (start with /)") { |path| @options[:prefix] = path }
0
+ opts.on( "--stats PATH", "Mount the Stats adapter under PATH") { |path| @options[:stats] = path }
0
+
0
+ opts.separator ""
0
+ opts.separator "Daemon options:"
0
+
0
+ opts.on("-d", "--daemonize", "Run daemonized in the background") { @options[:daemonize] = true }
0
+ opts.on("-l", "--log FILE", "File to redirect output " +
0
+ "(default: #{@options[:log]})") { |file| @options[:log] = file }
0
+ opts.on("-P", "--pid FILE", "File to store PID " +
0
+ "(default: #{@options[:pid]})") { |file| @options[:pid] = file }
0
+ opts.on("-u", "--user NAME", "User to run daemon as (use with -g)") { |user| @options[:user] = user }
0
+ opts.on("-g", "--group NAME", "Group to run daemon as (use with -u)") { |group| @options[:group] = group }
0
+
0
+ opts.separator ""
0
+ opts.separator "Cluster options:"
0
+
0
+ opts.on("-s", "--servers NUM", "Number of servers to start",
0
+ "set a value >1 to start a cluster") { |num| @options[:servers] = num.to_i }
0
+ opts.on("-o", "--only NUM", "Send command to only one server of the cluster") { |only| @options[:only] = only }
0
+ opts.on("-C", "--config PATH", "Load options from a config file") { |file| @options[:config] = file }
0
+
0
+ opts.separator ""
0
+ opts.separator "Common options:"
0
+
0
+ opts.on_tail("-D", "--debug", "Set debbuging on") { $DEBUG = true }
0
+ opts.on_tail("-V", "--trace", "Set tracing on") { $TRACE = true }
0
+ opts.on_tail("-h", "--help", "Show this message") { puts opts; exit }
0
+ opts.on_tail('-v', '--version', "Show version") { puts Thin::SERVER; exit }
0
+ end
0
+ end
0
+
0
+ # Parse the current shell arguments and run the command.
0
+ # Exits on error.
0
+ def run!
0
+ @parser.parse! @argv
0
+ command = @argv[0]
0
+
0
+ Dir.chdir(@options[:chdir])
0
+
0
+ if COMMANDS.include?(command)
0
+ run_command(command)
0
+ elsif command.nil?
0
+ puts "Command required"
0
+ puts @parser
0
+ exit 1
0
+ else
0
+ abort "Invalid command : #{command}"
0
+ end
0
+ end
0
+
0
+ # Send the command to the controller: single instance or cluster.
0
+ def run_command(command)
0
+ load_options_from_config_file! unless command == 'config'
0
+
0
+ if cluster?
0
+ controller = Cluster.new(@options)
0
+ else
0
+ controller = Controller.new(@options)
0
+ end
0
+
0
+ controller.send(command)
0
+ end
0
+
0
+ # +true+ if we're controlling a cluster.
0
+ def cluster?
0
+ @options[:only] || (@options[:servers] && @options[:servers] > 1)
0
+ end
0
+
0
+ private
0
+ def load_options_from_config_file!
0
+ if file = @options.delete(:config)
0
+ YAML.load_file(file).each { |key, value| @options[key.to_sym] = value }
0
+ end
0
+ end
0
+ end
0
+end
...
10
11
12
13
14
15
16
...
60
61
62
63
64
65
66
...
111
112
113
114
115
116
117
...
10
11
12
 
13
14
15
...
59
60
61
 
62
63
64
...
109
110
111
 
112
113
114
0
@@ -10,7 +10,6 @@
0
                            :log => 'thin.log',
0
                            :pid => 'thin.pid'
0
                           )
0
- @cluster.script = File.dirname(__FILE__) + '/../bin/thin'
0
     @cluster.silent = true
0
   end
0
     
0
@@ -60,7 +59,6 @@
0
                            :log => 'thin.log',
0
                            :pid => 'thin.pid'
0
                           )
0
- @cluster.script = File.dirname(__FILE__) + '/../bin/thin'
0
     @cluster.silent = true
0
   end
0
   
0
@@ -111,7 +109,6 @@
0
                            :pid => 'thin.pid',
0
                            :only => 3001
0
                           )
0
- @cluster.script = File.dirname(__FILE__) + '/../bin/thin'
0
     @cluster.silent = true
0
   end
0
   
...
3
4
5
6
7
8
9
...
3
4
5
 
6
7
8
0
@@ -3,7 +3,6 @@
0
 describe Command do
0
   before do
0
     @command = Command.new(:start, :port => 3000, :daemonize => true, :log => 'hi.log', :pid => nil)
0
- @command.script = File.dirname(__FILE__) + '/../bin/thin'
0
     @command.silent = true
0
   end
0
   
...
11
12
13
 
14
15
16
...
11
12
13
14
15
16
17
0
@@ -11,6 +11,7 @@
0
 include Thin
0
 
0
 FileUtils.mkdir_p File.dirname(__FILE__) + '/../log'
0
+Command.script = File.dirname(__FILE__) + '/../bin/thin'
0
 
0
 class TestRequest < Thin::Request
0
   def initialize(path, verb='GET', params={})

Comments

    No one has commented yet.