thin 0.6.2-x86-mswin32-60 → 0.6.3-x86-mswin32-60
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.
- data/CHANGELOG +34 -1
- data/bin/thin +2 -164
- data/example/config.ru +4 -1
- data/example/ramaze.ru +12 -0
- data/example/thin.god +70 -66
- data/lib/rack/adapter/rails.rb +0 -3
- data/lib/rack/handler/thin.rb +6 -1
- data/lib/thin.rb +13 -4
- data/lib/thin/command.rb +9 -5
- data/lib/thin/connection.rb +5 -14
- data/lib/thin/connectors/connector.rb +61 -0
- data/lib/thin/connectors/tcp_server.rb +29 -0
- data/lib/thin/connectors/unix_server.rb +48 -0
- data/lib/thin/controllers/cluster.rb +115 -0
- data/lib/thin/controllers/controller.rb +85 -0
- data/lib/thin/controllers/service.rb +73 -0
- data/lib/thin/controllers/service.sh.erb +39 -0
- data/lib/thin/daemonizing.rb +9 -4
- data/lib/thin/headers.rb +2 -2
- data/lib/thin/runner.rb +166 -0
- data/lib/thin/server.rb +109 -89
- data/lib/thin/stats.html.erb +216 -0
- data/lib/thin/stats.rb +1 -249
- data/lib/thin/version.rb +10 -3
- data/lib/thin_parser.so +0 -0
- data/spec/command_spec.rb +0 -1
- data/spec/configs/cluster.yml +9 -0
- data/spec/configs/single.yml +9 -0
- data/spec/{cluster_spec.rb → controllers/cluster_spec.rb} +22 -10
- data/spec/controllers/controller_spec.rb +85 -0
- data/spec/controllers/service_spec.rb +51 -0
- data/spec/daemonizing_spec.rb +73 -9
- data/spec/request/mongrel_spec.rb +39 -0
- data/spec/{request_spec.rb → request/parser_spec.rb} +11 -143
- data/spec/request/perf_spec.rb +50 -0
- data/spec/request/processing_spec.rb +46 -0
- data/spec/runner_spec.rb +135 -0
- data/spec/server/builder_spec.rb +38 -0
- data/spec/server/stopping_spec.rb +45 -0
- data/spec/server/tcp_spec.rb +54 -0
- data/spec/server/unix_socket_spec.rb +30 -0
- data/spec/spec_helper.rb +49 -16
- data/tasks/announce.rake +7 -3
- data/tasks/email.erb +8 -18
- data/tasks/stats.rake +21 -8
- metadata +33 -7
- data/lib/thin/cluster.rb +0 -123
- data/spec/server_spec.rb +0 -200
data/lib/thin/connection.rb
CHANGED
@@ -1,17 +1,15 @@
|
|
1
1
|
require 'socket'
|
2
2
|
|
3
3
|
module Thin
|
4
|
+
# Connection between the server and client.
|
4
5
|
class Connection < EventMachine::Connection
|
5
6
|
include Logging
|
6
7
|
|
7
8
|
# Rack application served by this connection.
|
8
9
|
attr_accessor :app
|
9
10
|
|
10
|
-
#
|
11
|
-
attr_accessor :
|
12
|
-
|
13
|
-
# Server owning the connection
|
14
|
-
attr_accessor :server
|
11
|
+
# Connector to the server
|
12
|
+
attr_accessor :connector
|
15
13
|
|
16
14
|
def post_init
|
17
15
|
@request = Request.new
|
@@ -52,19 +50,12 @@ module Thin
|
|
52
50
|
end
|
53
51
|
|
54
52
|
def unbind
|
55
|
-
@
|
53
|
+
@connector.connection_finished(self)
|
56
54
|
end
|
57
55
|
|
58
56
|
protected
|
59
57
|
def remote_address
|
60
|
-
|
61
|
-
remote_addr
|
62
|
-
elsif @unix_socket
|
63
|
-
# FIXME not sure about this, does it even make sense on a UNIX socket?
|
64
|
-
Socket.unpack_sockaddr_un(get_peername)
|
65
|
-
else
|
66
|
-
Socket.unpack_sockaddr_in(get_peername)[1]
|
67
|
-
end
|
58
|
+
@request.env[Request::FORWARDED_FOR] || Socket.unpack_sockaddr_in(get_peername)[1]
|
68
59
|
end
|
69
60
|
end
|
70
61
|
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Thin
|
2
|
+
module Connectors
|
3
|
+
# A Connector connect the server to the client. It handles:
|
4
|
+
# * connection/disconnection to the server
|
5
|
+
# * initialization of the connections
|
6
|
+
# * manitoring of the active connections.
|
7
|
+
class Connector
|
8
|
+
include Logging
|
9
|
+
|
10
|
+
# Server serving the connections throught the connector
|
11
|
+
attr_reader :server
|
12
|
+
|
13
|
+
# Maximum time for incoming data to arrive
|
14
|
+
attr_accessor :timeout
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
@connections = []
|
18
|
+
@timeout = 60 # sec
|
19
|
+
end
|
20
|
+
|
21
|
+
# Free up resources used by the connector.
|
22
|
+
def close
|
23
|
+
end
|
24
|
+
|
25
|
+
def server=(server)
|
26
|
+
@server = server
|
27
|
+
@silent = @server.silent
|
28
|
+
end
|
29
|
+
|
30
|
+
# Initialize a new connection to a client.
|
31
|
+
def initialize_connection(connection)
|
32
|
+
connection.connector = self
|
33
|
+
connection.app = @server.app
|
34
|
+
connection.comm_inactivity_timeout = @timeout
|
35
|
+
connection.silent = @silent
|
36
|
+
|
37
|
+
@connections << connection
|
38
|
+
end
|
39
|
+
|
40
|
+
# Close all active connections.
|
41
|
+
def close_connections
|
42
|
+
@connections.each { |connection| connection.close_connection }
|
43
|
+
end
|
44
|
+
|
45
|
+
# Called by a connection when it's unbinded.
|
46
|
+
def connection_finished(connection)
|
47
|
+
@connections.delete(connection)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Returns +true+ if no active connection.
|
51
|
+
def empty?
|
52
|
+
@connections.empty?
|
53
|
+
end
|
54
|
+
|
55
|
+
# Number of active connections.
|
56
|
+
def size
|
57
|
+
@connections.size
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Thin
|
2
|
+
module Connectors
|
3
|
+
# Connectior to act as a TCP socket server.
|
4
|
+
class TcpServer < Connector
|
5
|
+
# Address and port on which the server is listening for connections.
|
6
|
+
attr_accessor :host, :port
|
7
|
+
|
8
|
+
def initialize(host, port)
|
9
|
+
@host = host
|
10
|
+
@port = port
|
11
|
+
super()
|
12
|
+
end
|
13
|
+
|
14
|
+
# Connect the server
|
15
|
+
def connect
|
16
|
+
@signature = EventMachine.start_server(@host, @port, Connection, &method(:initialize_connection))
|
17
|
+
end
|
18
|
+
|
19
|
+
# Stops the server
|
20
|
+
def disconnect
|
21
|
+
EventMachine.stop_server(@signature)
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_s
|
25
|
+
"#{@host}:#{@port}"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Thin
|
2
|
+
module Connectors
|
3
|
+
# Connectior to act as a UNIX domain socket server.
|
4
|
+
class UnixServer < Connector
|
5
|
+
# UNIX domain socket on which the server is listening for connections.
|
6
|
+
attr_accessor :socket
|
7
|
+
|
8
|
+
def initialize(socket)
|
9
|
+
raise PlatformNotSupported, 'UNIX sockets not available on Windows' if Thin.win?
|
10
|
+
@socket = socket
|
11
|
+
super()
|
12
|
+
end
|
13
|
+
|
14
|
+
# Connect the server
|
15
|
+
def connect
|
16
|
+
at_exit { remove_socket_file } # In case it crashes
|
17
|
+
@signature = EventMachine.start_unix_domain_server(@socket, UnixConnection, &method(:initialize_connection))
|
18
|
+
end
|
19
|
+
|
20
|
+
# Stops the server
|
21
|
+
def disconnect
|
22
|
+
EventMachine.stop_server(@signature)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Free up resources used by the connector.
|
26
|
+
def close
|
27
|
+
remove_socket_file
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_s
|
31
|
+
@socket
|
32
|
+
end
|
33
|
+
|
34
|
+
protected
|
35
|
+
def remove_socket_file
|
36
|
+
File.delete(@socket) if @socket && File.exist?(@socket)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class UnixConnection < Connection
|
42
|
+
protected
|
43
|
+
def remote_address
|
44
|
+
# FIXME not sure about this, does it even make sense on a UNIX socket?
|
45
|
+
Socket.unpack_sockaddr_un(get_peername)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
module Thin
|
2
|
+
module Controllers
|
3
|
+
# Control a set of servers.
|
4
|
+
# * Generate start and stop commands and run them.
|
5
|
+
# * Inject the port or socket number in the pid and log filenames.
|
6
|
+
# Servers are started throught the +thin+ command-line script.
|
7
|
+
class Cluster < Controller
|
8
|
+
# Number of servers in the cluster.
|
9
|
+
attr_accessor :size
|
10
|
+
|
11
|
+
# Create a new cluster of servers launched using +options+.
|
12
|
+
def initialize(options)
|
13
|
+
@options = options.merge(:daemonize => true)
|
14
|
+
@size = @options.delete(:servers)
|
15
|
+
@only = @options.delete(:only)
|
16
|
+
|
17
|
+
if socket
|
18
|
+
@options.delete(:address)
|
19
|
+
@options.delete(:port)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def first_port; @options[:port] end
|
24
|
+
def address; @options[:address] end
|
25
|
+
def socket; @options[:socket] end
|
26
|
+
def pid_file; @options[:pid] end
|
27
|
+
def log_file; @options[:log] end
|
28
|
+
|
29
|
+
# Start the servers
|
30
|
+
def start
|
31
|
+
with_each_server { |port| start_server port }
|
32
|
+
end
|
33
|
+
|
34
|
+
# Start a single server
|
35
|
+
def start_server(number)
|
36
|
+
log "Starting server on #{server_id(number)} ... "
|
37
|
+
|
38
|
+
run :start, @options, number
|
39
|
+
end
|
40
|
+
|
41
|
+
# Stop the servers
|
42
|
+
def stop
|
43
|
+
with_each_server { |n| stop_server n }
|
44
|
+
end
|
45
|
+
|
46
|
+
# Stop a single server
|
47
|
+
def stop_server(number)
|
48
|
+
log "Stopping server on #{server_id(number)} ... "
|
49
|
+
|
50
|
+
run :stop, @options, number
|
51
|
+
end
|
52
|
+
|
53
|
+
# Stop and start the servers.
|
54
|
+
def restart
|
55
|
+
stop
|
56
|
+
sleep 0.1 # Let's breath a bit shall we ?
|
57
|
+
start
|
58
|
+
end
|
59
|
+
|
60
|
+
def server_id(number)
|
61
|
+
if socket
|
62
|
+
socket_for(number)
|
63
|
+
else
|
64
|
+
[address, number].join(':')
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def log_file_for(number)
|
69
|
+
include_server_number log_file, number
|
70
|
+
end
|
71
|
+
|
72
|
+
def pid_file_for(number)
|
73
|
+
include_server_number pid_file, number
|
74
|
+
end
|
75
|
+
|
76
|
+
def socket_for(number)
|
77
|
+
include_server_number socket, number
|
78
|
+
end
|
79
|
+
|
80
|
+
def pid_for(number)
|
81
|
+
File.read(pid_file_for(number)).chomp.to_i
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
# Send the command to the +thin+ script
|
86
|
+
def run(cmd, options, number)
|
87
|
+
cmd_options = options.dup
|
88
|
+
cmd_options.merge!(:pid => pid_file_for(number), :log => log_file_for(number))
|
89
|
+
if socket
|
90
|
+
cmd_options.merge!(:socket => socket_for(number))
|
91
|
+
else
|
92
|
+
cmd_options.merge!(:port => number)
|
93
|
+
end
|
94
|
+
Command.run(cmd, cmd_options)
|
95
|
+
end
|
96
|
+
|
97
|
+
def with_each_server
|
98
|
+
if @only
|
99
|
+
yield @only
|
100
|
+
else
|
101
|
+
@size.times do |n|
|
102
|
+
yield socket ? n : (first_port + n)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# Add the server port or number in the filename
|
108
|
+
# so each instance get its own file
|
109
|
+
def include_server_number(path, number)
|
110
|
+
ext = File.extname(path)
|
111
|
+
path.gsub(/#{ext}$/, ".#{number}#{ext}")
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module Thin
|
4
|
+
module Controllers
|
5
|
+
# Raised when a mandatory option is missing to run a command.
|
6
|
+
class OptionRequired < RuntimeError
|
7
|
+
def initialize(option)
|
8
|
+
super("#{option} option required")
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
# Controls a Thin server.
|
13
|
+
# Allow to start, stop, restart and configure a single thin server.
|
14
|
+
class Controller
|
15
|
+
include Logging
|
16
|
+
|
17
|
+
# Command line options passed to the thin script
|
18
|
+
attr_accessor :options
|
19
|
+
|
20
|
+
def initialize(options)
|
21
|
+
@options = options
|
22
|
+
end
|
23
|
+
|
24
|
+
def start
|
25
|
+
if @options[:socket]
|
26
|
+
server = Server.new(@options[:socket])
|
27
|
+
else
|
28
|
+
server = Server.new(@options[:address], @options[:port])
|
29
|
+
end
|
30
|
+
|
31
|
+
server.pid_file = @options[:pid]
|
32
|
+
server.log_file = @options[:log]
|
33
|
+
server.timeout = @options[:timeout]
|
34
|
+
|
35
|
+
if @options[:daemonize]
|
36
|
+
server.daemonize
|
37
|
+
server.change_privilege @options[:user], @options[:group] if @options[:user] && @options[:group]
|
38
|
+
end
|
39
|
+
|
40
|
+
# If a Rack config file is specified we eval it inside a Rack::Builder block to create
|
41
|
+
# a Rack adapter from it. DHH was hacker of the year a couple years ago so we default
|
42
|
+
# to Rails adapter.
|
43
|
+
if @options[:rackup]
|
44
|
+
rackup_code = File.read(@options[:rackup])
|
45
|
+
server.app = eval("Rack::Builder.new {( #{rackup_code}\n )}.to_app", nil, @options[:rackup])
|
46
|
+
else
|
47
|
+
server.app = Rack::Adapter::Rails.new(@options.merge(:root => @options[:chdir]))
|
48
|
+
end
|
49
|
+
|
50
|
+
# If a prefix is required, wrap in Rack URL mapper
|
51
|
+
server.app = Rack::URLMap.new(@options[:prefix] => server.app) if @options[:prefix]
|
52
|
+
|
53
|
+
# If a stats are required, wrap in Stats adapter
|
54
|
+
server.app = Stats::Adapter.new(server.app, @options[:stats]) if @options[:stats]
|
55
|
+
|
56
|
+
# Register restart procedure
|
57
|
+
server.on_restart { Command.run(:start, @options) }
|
58
|
+
|
59
|
+
server.start
|
60
|
+
end
|
61
|
+
|
62
|
+
def stop
|
63
|
+
raise OptionRequired, :pid unless @options[:pid]
|
64
|
+
|
65
|
+
Server.kill(@options[:pid], @options[:timeout] || 60)
|
66
|
+
end
|
67
|
+
|
68
|
+
def restart
|
69
|
+
raise OptionRequired, :pid unless @options[:pid]
|
70
|
+
|
71
|
+
Server.restart(@options[:pid])
|
72
|
+
end
|
73
|
+
|
74
|
+
def config
|
75
|
+
config_file = @options.delete(:config) || raise(OptionRequired, :config)
|
76
|
+
|
77
|
+
# Stringify keys
|
78
|
+
@options.keys.each { |o| @options[o.to_s] = @options.delete(o) }
|
79
|
+
|
80
|
+
File.open(config_file, 'w') { |f| f << @options.to_yaml }
|
81
|
+
log ">> Wrote configuration to #{config_file}"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'erb'
|
2
|
+
|
3
|
+
module Thin
|
4
|
+
module Controllers
|
5
|
+
# System service controller to launch all servers which
|
6
|
+
# config files are in a directory.
|
7
|
+
class Service < Controller
|
8
|
+
INITD_PATH = '/etc/init.d/thin'
|
9
|
+
DEFAULT_CONFIG_PATH = '/etc/thin'
|
10
|
+
TEMPLATE = File.dirname(__FILE__) + '/service.sh.erb'
|
11
|
+
|
12
|
+
def initialize(options)
|
13
|
+
@options = options
|
14
|
+
|
15
|
+
raise PlatformNotSupported, 'Running as a service only supported on Linux' unless Thin.linux?
|
16
|
+
end
|
17
|
+
|
18
|
+
def config_path
|
19
|
+
@options[:all] || DEFAULT_CONFIG_PATH
|
20
|
+
end
|
21
|
+
|
22
|
+
def start
|
23
|
+
run :start
|
24
|
+
end
|
25
|
+
|
26
|
+
def stop
|
27
|
+
run :stop
|
28
|
+
end
|
29
|
+
|
30
|
+
def restart
|
31
|
+
run :restart
|
32
|
+
end
|
33
|
+
|
34
|
+
def install(config_files_path=DEFAULT_CONFIG_PATH)
|
35
|
+
if File.exist?(INITD_PATH)
|
36
|
+
log ">> Thin service already installed at #{INITD_PATH}"
|
37
|
+
else
|
38
|
+
log ">> Installing thin service at #{INITD_PATH} ..."
|
39
|
+
sh "mkdir -p #{File.dirname(INITD_PATH)}"
|
40
|
+
log "writing #{INITD_PATH}"
|
41
|
+
File.open(INITD_PATH, 'w') do |f|
|
42
|
+
f << ERB.new(File.read(TEMPLATE)).result(binding)
|
43
|
+
end
|
44
|
+
sh "chmod +x #{INITD_PATH}" # Make executable
|
45
|
+
end
|
46
|
+
|
47
|
+
sh "mkdir -p #{config_files_path}"
|
48
|
+
|
49
|
+
log ''
|
50
|
+
log "To configure thin to start at system boot:"
|
51
|
+
log "on RedHat like systems:"
|
52
|
+
log " sudo /sbin/chkconfig --level 345 #{NAME} on"
|
53
|
+
log "on Debian like systems (Ubuntu):"
|
54
|
+
log " sudo /usr/sbin/update-rc.d -f #{NAME} defaults"
|
55
|
+
log ''
|
56
|
+
log "Then put your config files in #{config_files_path}"
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
def run(command)
|
61
|
+
Dir[config_path + '/*'].each do |config|
|
62
|
+
log "[#{command}] #{config} ..."
|
63
|
+
Command.run(command, :config => config, :daemonize => true)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def sh(cmd)
|
68
|
+
log cmd
|
69
|
+
system(cmd)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|