thin 1.8.2 → 2.0.0.pre

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 (108) hide show
  1. data/.gitignore +9 -0
  2. data/CHANGELOG +29 -116
  3. data/Gemfile +8 -0
  4. data/README.md +44 -78
  5. data/Rakefile +28 -18
  6. data/bin/thin +4 -4
  7. data/examples/async.ru +21 -0
  8. data/examples/thin.conf.rb +39 -0
  9. data/lib/thin/async.rb +108 -0
  10. data/lib/thin/backends/prefork.rb +44 -0
  11. data/lib/thin/backends/single_process.rb +28 -0
  12. data/lib/thin/chunked_body.rb +28 -0
  13. data/lib/thin/configurator.rb +118 -0
  14. data/lib/thin/connection.rb +246 -172
  15. data/lib/thin/listener.rb +114 -0
  16. data/lib/thin/request.rb +94 -76
  17. data/lib/thin/response.rb +112 -45
  18. data/lib/thin/runner.rb +134 -197
  19. data/lib/thin/server.rb +203 -252
  20. data/lib/thin/system.rb +49 -0
  21. data/lib/thin/version.rb +12 -27
  22. data/lib/thin.rb +2 -44
  23. data/man/index.txt +3 -0
  24. data/man/thin-conf.5.ronn +121 -0
  25. data/man/thin.1.ronn +105 -0
  26. data/site/.gitignore +2 -0
  27. data/site/README.md +21 -0
  28. data/site/Rakefile +20 -0
  29. data/site/config.ru +4 -0
  30. data/site/public/images/grid.png +0 -0
  31. data/site/public/javascripts/dd_belatedpng.js +13 -0
  32. data/site/public/javascripts/modernizr-1.6.min.js +30 -0
  33. data/site/public/man/thin-conf.5.html +220 -0
  34. data/site/public/man/thin.1.html +177 -0
  35. data/site/site/assets/javascripts/main.coffee +2 -0
  36. data/site/site/assets/stylesheets/_config.scss +55 -0
  37. data/site/site/assets/stylesheets/main.scss +24 -0
  38. data/site/site/helpers.rb +17 -0
  39. data/site/site/layouts/base.erb +55 -0
  40. data/site/site/layouts/default.erb +17 -0
  41. data/site/site/pages/about.md +5 -0
  42. data/site/site/pages/index.erb +10 -0
  43. data/site/site/partials/.gitkeep +0 -0
  44. data/test/fixtures/big.txt +1 -0
  45. data/test/fixtures/small.txt +1 -0
  46. data/test/fixtures/thin.conf.rb +15 -0
  47. data/test/integration/async_test.rb +35 -0
  48. data/test/integration/big_request_test.rb +30 -0
  49. data/test/integration/config.ru +57 -0
  50. data/test/integration/daemonize_test.rb +26 -0
  51. data/test/integration/env_test.rb +44 -0
  52. data/test/integration/error_test.rb +37 -0
  53. data/test/integration/file_sending_test.rb +24 -0
  54. data/test/integration/keep_alive_test.rb +35 -0
  55. data/test/integration/robustness_test.rb +37 -0
  56. data/test/integration/single_process_test.rb +15 -0
  57. data/test/integration/socket_family_test.rb +38 -0
  58. data/test/integration/worker_test.rb +22 -0
  59. data/test/test_helper.rb +195 -0
  60. data/test/unit/configurator_test.rb +43 -0
  61. data/test/unit/connection_test.rb +94 -0
  62. data/test/unit/listener_test.rb +74 -0
  63. data/test/unit/request_test.rb +74 -0
  64. data/test/unit/response_test.rb +90 -0
  65. data/test/unit/server_test.rb +29 -0
  66. data/test/unit/system_test.rb +17 -0
  67. data/thin.gemspec +26 -0
  68. data/v2.todo +21 -0
  69. metadata +138 -93
  70. checksums.yaml +0 -7
  71. data/example/adapter.rb +0 -32
  72. data/example/async_app.ru +0 -126
  73. data/example/async_chat.ru +0 -247
  74. data/example/async_tailer.ru +0 -100
  75. data/example/config.ru +0 -22
  76. data/example/monit_sockets +0 -20
  77. data/example/monit_unixsock +0 -20
  78. data/example/myapp.rb +0 -1
  79. data/example/ramaze.ru +0 -12
  80. data/example/thin.god +0 -80
  81. data/example/thin_solaris_smf.erb +0 -36
  82. data/example/thin_solaris_smf.readme.txt +0 -150
  83. data/example/vlad.rake +0 -72
  84. data/ext/thin_parser/common.rl +0 -59
  85. data/ext/thin_parser/ext_help.h +0 -14
  86. data/ext/thin_parser/extconf.rb +0 -6
  87. data/ext/thin_parser/parser.c +0 -1447
  88. data/ext/thin_parser/parser.h +0 -49
  89. data/ext/thin_parser/parser.rl +0 -152
  90. data/ext/thin_parser/thin.c +0 -435
  91. data/lib/rack/adapter/loader.rb +0 -75
  92. data/lib/rack/adapter/rails.rb +0 -178
  93. data/lib/rack/handler/thin.rb +0 -38
  94. data/lib/thin/backends/base.rb +0 -169
  95. data/lib/thin/backends/swiftiply_client.rb +0 -66
  96. data/lib/thin/backends/tcp_server.rb +0 -34
  97. data/lib/thin/backends/unix_server.rb +0 -56
  98. data/lib/thin/command.rb +0 -53
  99. data/lib/thin/controllers/cluster.rb +0 -178
  100. data/lib/thin/controllers/controller.rb +0 -189
  101. data/lib/thin/controllers/service.rb +0 -76
  102. data/lib/thin/controllers/service.sh.erb +0 -39
  103. data/lib/thin/daemonizing.rb +0 -199
  104. data/lib/thin/headers.rb +0 -47
  105. data/lib/thin/logging.rb +0 -174
  106. data/lib/thin/stats.html.erb +0 -216
  107. data/lib/thin/stats.rb +0 -52
  108. data/lib/thin/statuses.rb +0 -48
@@ -1,178 +0,0 @@
1
- require 'cgi'
2
-
3
- # Adapter to run a Rails app with any supported Rack handler.
4
- # By default it will try to load the Rails application in the
5
- # current directory in the development environment.
6
- #
7
- # Options:
8
- # root: Root directory of the Rails app
9
- # environment: Rails environment to run in (development [default], production or test)
10
- # prefix: Set the relative URL root.
11
- #
12
- # Based on http://fuzed.rubyforge.org/ Rails adapter
13
- module Rack
14
- module Adapter
15
- class Rails
16
- FILE_METHODS = %w(GET HEAD).freeze
17
-
18
- def initialize(options = {})
19
- @root = options[:root] || Dir.pwd
20
- @env = options[:environment] || 'development'
21
- @prefix = options[:prefix]
22
-
23
- load_application
24
-
25
- @rails_app = self.class.rack_based? ? ActionController::Dispatcher.new : CgiApp.new
26
- @file_app = Rack::File.new(::File.join(RAILS_ROOT, "public"))
27
- end
28
-
29
- def load_application
30
- ENV['RAILS_ENV'] = @env
31
-
32
- require "#{@root}/config/environment"
33
- require 'dispatcher'
34
-
35
- if @prefix
36
- if ActionController::Base.respond_to?(:relative_url_root=)
37
- ActionController::Base.relative_url_root = @prefix # Rails 2.1.1
38
- else
39
- ActionController::AbstractRequest.relative_url_root = @prefix
40
- end
41
- end
42
- end
43
-
44
- def file_exist?(path)
45
- full_path = ::File.join(@file_app.root, Utils.unescape(path))
46
- ::File.file?(full_path) && ::File.readable_real?(full_path)
47
- end
48
-
49
- def call(env)
50
- path = env['PATH_INFO'].chomp('/')
51
- method = env['REQUEST_METHOD']
52
- cached_path = (path.empty? ? 'index' : path) + ActionController::Base.page_cache_extension
53
-
54
- if FILE_METHODS.include?(method)
55
- if file_exist?(path) # Serve the file if it's there
56
- return @file_app.call(env)
57
- elsif file_exist?(cached_path) # Serve the page cache if it's there
58
- env['PATH_INFO'] = cached_path
59
- return @file_app.call(env)
60
- end
61
- end
62
-
63
- # No static file, let Rails handle it
64
- @rails_app.call(env)
65
- end
66
-
67
- def self.rack_based?
68
- rails_version = ::Rails::VERSION
69
- return false if rails_version::MAJOR < 2
70
- return false if rails_version::MAJOR == 2 && rails_version::MINOR < 2
71
- return false if rails_version::MAJOR == 2 && rails_version::MINOR == 2 && rails_version::TINY < 3
72
- true # >= 2.2.3
73
- end
74
-
75
- protected
76
- # For Rails pre Rack (2.3)
77
- class CgiApp
78
- def call(env)
79
- request = Request.new(env)
80
- response = Response.new
81
- session_options = ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS
82
- cgi = CGIWrapper.new(request, response)
83
-
84
- Dispatcher.dispatch(cgi, session_options, response)
85
-
86
- response.finish
87
- end
88
- end
89
-
90
- class CGIWrapper < ::CGI
91
- def initialize(request, response, *args)
92
- @request = request
93
- @response = response
94
- @args = *args
95
- @input = request.body
96
-
97
- super *args
98
- end
99
-
100
- def header(options = 'text/html')
101
- if options.is_a?(String)
102
- @response['Content-Type'] = options unless @response['Content-Type']
103
- else
104
- @response['Content-Length'] = options.delete('Content-Length').to_s if options['Content-Length']
105
-
106
- @response['Content-Type'] = options.delete('type') || "text/html"
107
- @response['Content-Type'] += '; charset=' + options.delete('charset') if options['charset']
108
-
109
- @response['Content-Language'] = options.delete('language') if options['language']
110
- @response['Expires'] = options.delete('expires') if options['expires']
111
-
112
- @response.status = options.delete('Status') if options['Status']
113
-
114
- # Convert 'cookie' header to 'Set-Cookie' headers.
115
- # Because Set-Cookie header can appear more the once in the response body,
116
- # we store it in a line break seperated string that will be translated to
117
- # multiple Set-Cookie header by the handler.
118
- if cookie = options.delete('cookie')
119
- cookies = []
120
-
121
- case cookie
122
- when Array then cookie.each { |c| cookies << c.to_s }
123
- when Hash then cookie.each { |_, c| cookies << c.to_s }
124
- else cookies << cookie.to_s
125
- end
126
-
127
- @output_cookies.each { |c| cookies << c.to_s } if @output_cookies
128
-
129
- @response['Set-Cookie'] = [@response['Set-Cookie'], cookies].compact
130
- # See http://groups.google.com/group/rack-devel/browse_thread/thread/e8759b91a82c5a10/a8dbd4574fe97d69?#a8dbd4574fe97d69
131
- if Thin.ruby_18?
132
- @response['Set-Cookie'].flatten!
133
- else
134
- @response['Set-Cookie'] = @response['Set-Cookie'].join("\n")
135
- end
136
- end
137
-
138
- options.each { |k, v| @response[k] = v }
139
- end
140
-
141
- ''
142
- end
143
-
144
- def params
145
- @params ||= @request.params
146
- end
147
-
148
- def cookies
149
- @request.cookies
150
- end
151
-
152
- def query_string
153
- @request.query_string
154
- end
155
-
156
- # Used to wrap the normal args variable used inside CGI.
157
- def args
158
- @args
159
- end
160
-
161
- # Used to wrap the normal env_table variable used inside CGI.
162
- def env_table
163
- @request.env
164
- end
165
-
166
- # Used to wrap the normal stdinput variable used inside CGI.
167
- def stdinput
168
- @input
169
- end
170
-
171
- def stdoutput
172
- STDERR.puts 'stdoutput should not be used.'
173
- @response.body
174
- end
175
- end
176
- end
177
- end
178
- end
@@ -1,38 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "thin"
4
- require "thin/server"
5
- require "thin/logging"
6
- require "thin/backends/tcp_server"
7
-
8
- module Rack
9
- module Handler
10
- class Thin
11
- def self.run(app, **options)
12
- environment = ENV['RACK_ENV'] || 'development'
13
- default_host = environment == 'development' ? 'localhost' : '0.0.0.0'
14
-
15
- host = options.delete(:Host) || default_host
16
- port = options.delete(:Port) || 8080
17
- args = [host, port, app, options]
18
-
19
- server = ::Thin::Server.new(*args)
20
- yield server if block_given?
21
-
22
- server.start
23
- end
24
-
25
- def self.valid_options
26
- environment = ENV['RACK_ENV'] || 'development'
27
- default_host = environment == 'development' ? 'localhost' : '0.0.0.0'
28
-
29
- {
30
- "Host=HOST" => "Hostname to listen on (default: #{default_host})",
31
- "Port=PORT" => "Port to listen on (default: 8080)",
32
- }
33
- end
34
- end
35
-
36
- register :thin, ::Rack::Handler::Thin
37
- end
38
- end
@@ -1,169 +0,0 @@
1
- module Thin
2
- module Backends
3
- # A Backend connects the server to the client. It handles:
4
- # * connection/disconnection to the server
5
- # * initialization of the connections
6
- # * monitoring of the active connections.
7
- #
8
- # == Implementing your own backend
9
- # You can create your own minimal backend by inheriting this class and
10
- # defining the +connect+ and +disconnect+ method.
11
- # If your backend is not based on EventMachine you also need to redefine
12
- # the +start+, +stop+, <tt>stop!</tt> and +config+ methods.
13
- class Base
14
- # Server serving the connections throught the backend
15
- attr_accessor :server
16
-
17
- # Maximum time for incoming data to arrive
18
- attr_accessor :timeout
19
-
20
- # Maximum number of file or socket descriptors that the server may open.
21
- attr_accessor :maximum_connections
22
-
23
- # Maximum number of connections that can be persistent
24
- attr_accessor :maximum_persistent_connections
25
-
26
- #allows setting of the eventmachine threadpool size
27
- attr_reader :threadpool_size
28
- def threadpool_size=(size)
29
- @threadpool_size = size
30
- EventMachine.threadpool_size = size
31
- end
32
-
33
- # Allow using threads in the backend.
34
- attr_writer :threaded
35
- def threaded?; @threaded end
36
-
37
- # Allow using SSL in the backend.
38
- attr_writer :ssl, :ssl_options
39
- def ssl?; @ssl end
40
-
41
- # Number of persistent connections currently opened
42
- attr_accessor :persistent_connection_count
43
-
44
- # Disable the use of epoll under Linux
45
- attr_accessor :no_epoll
46
-
47
- def initialize
48
- @connections = {}
49
- @timeout = Server::DEFAULT_TIMEOUT
50
- @persistent_connection_count = 0
51
- @maximum_connections = Server::DEFAULT_MAXIMUM_CONNECTIONS
52
- @maximum_persistent_connections = Server::DEFAULT_MAXIMUM_PERSISTENT_CONNECTIONS
53
- @no_epoll = false
54
- @running = false
55
- @ssl = nil
56
- @started_reactor = false
57
- @stopping = false
58
- @threaded = nil
59
- end
60
-
61
- # Start the backend and connect it.
62
- def start
63
- @stopping = false
64
- starter = proc do
65
- connect
66
- yield if block_given?
67
- @running = true
68
- end
69
-
70
- # Allow for early run up of eventmachine.
71
- if EventMachine.reactor_running?
72
- starter.call
73
- else
74
- @started_reactor = true
75
- EventMachine.run(&starter)
76
- end
77
- end
78
-
79
- # Stop of the backend from accepting new connections.
80
- def stop
81
- @running = false
82
- @stopping = true
83
-
84
- # Do not accept anymore connection
85
- disconnect
86
- # Close idle persistent connections
87
- @connections.each_value { |connection| connection.close_connection if connection.idle? }
88
- stop! if @connections.empty?
89
- end
90
-
91
- # Force stop of the backend NOW, too bad for the current connections.
92
- def stop!
93
- @running = false
94
- @stopping = false
95
-
96
- EventMachine.stop if @started_reactor && EventMachine.reactor_running?
97
- @connections.each_value { |connection| connection.close_connection }
98
- close
99
- end
100
-
101
- # Configure the backend. This method will be called before droping superuser privileges,
102
- # so you can do crazy stuff that require godlike powers here.
103
- def config
104
- # See http://rubyeventmachine.com/pub/rdoc/files/EPOLL.html
105
- EventMachine.epoll unless @no_epoll
106
-
107
- # Set the maximum number of socket descriptors that the server may open.
108
- # The process needs to have required privilege to set it higher the 1024 on
109
- # some systems.
110
- @maximum_connections = EventMachine.set_descriptor_table_size(@maximum_connections) unless Thin.win?
111
- end
112
-
113
- # Free up resources used by the backend.
114
- def close
115
- end
116
-
117
- # Returns +true+ if the backend is connected and running.
118
- def running?
119
- @running
120
- end
121
-
122
- def started_reactor?
123
- @started_reactor
124
- end
125
-
126
- # Called by a connection when it's unbinded.
127
- def connection_finished(connection)
128
- @persistent_connection_count -= 1 if connection.can_persist?
129
- @connections.delete(connection.__id__)
130
-
131
- # Finalize gracefull stop if there's no more active connection.
132
- stop! if @stopping && @connections.empty?
133
- end
134
-
135
- # Returns +true+ if no active connection.
136
- def empty?
137
- @connections.empty?
138
- end
139
-
140
- # Number of active connections.
141
- def size
142
- @connections.size
143
- end
144
-
145
- protected
146
- # Initialize a new connection to a client.
147
- def initialize_connection(connection)
148
- connection.backend = self
149
- connection.app = @server.app
150
- connection.comm_inactivity_timeout = @timeout
151
- connection.threaded = @threaded
152
-
153
- if @ssl
154
- connection.start_tls(@ssl_options)
155
- end
156
-
157
- # We control the number of persistent connections by keeping
158
- # a count of the total one allowed yet.
159
- if @persistent_connection_count < @maximum_persistent_connections
160
- connection.can_persist!
161
- @persistent_connection_count += 1
162
- end
163
-
164
- @connections[connection.__id__] = connection
165
- end
166
-
167
- end
168
- end
169
- end
@@ -1,66 +0,0 @@
1
- module Thin
2
- module Backends
3
- # Backend to act as a Swiftiply client (http://swiftiply.swiftcore.org).
4
- class SwiftiplyClient < Base
5
- attr_accessor :key
6
-
7
- attr_accessor :host, :port
8
-
9
- def initialize(host, port, options = {})
10
- @host = host
11
- @port = port.to_i
12
- @key = options[:swiftiply].to_s
13
- super()
14
- end
15
-
16
- # Connect the server
17
- def connect
18
- EventMachine.connect(@host, @port, SwiftiplyConnection, &method(:initialize_connection))
19
- end
20
-
21
- # Stops the server
22
- def disconnect
23
- EventMachine.stop
24
- end
25
-
26
- def to_s
27
- "#{@host}:#{@port} swiftiply"
28
- end
29
- end
30
- end
31
-
32
- class SwiftiplyConnection < Connection
33
- def connection_completed
34
- send_data swiftiply_handshake(@backend.key)
35
- end
36
-
37
- def persistent?
38
- true
39
- end
40
-
41
- def unbind
42
- super
43
- EventMachine.add_timer(rand(2)) { reconnect(@backend.host, @backend.port) } if @backend.running?
44
- end
45
-
46
- protected
47
- def swiftiply_handshake(key)
48
- 'swiftclient' << host_ip.collect { |x| sprintf('%02x', x.to_i) }.join << sprintf('%04x', @backend.port) << sprintf('%02x', key.length) << key
49
- end
50
-
51
- # For some reason Swiftiply request the current host
52
- def host_ip
53
- begin
54
- if defined?(Addrinfo)
55
- # ruby 2.0+
56
- # TODO: ipv6 support here?
57
- Addrinfo.getaddrinfo(@backend.host, @backend.port, :PF_INET, :STREAM).first.ip_address.split('.').map(&:to_i)
58
- else
59
- Socket.gethostbyname(@backend.host)[3].unpack('CCCC')
60
- end
61
- rescue
62
- [0, 0, 0, 0]
63
- end
64
- end
65
- end
66
- end
@@ -1,34 +0,0 @@
1
- module Thin
2
- module Backends
3
- # Backend to act as a TCP socket server.
4
- class TcpServer < Base
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
- binary_name = EventMachine.get_sockname( @signature )
18
- port_name = Socket.unpack_sockaddr_in( binary_name )
19
- @port = port_name[0]
20
- @host = port_name[1]
21
- @signature
22
- end
23
-
24
- # Stops the server
25
- def disconnect
26
- EventMachine.stop_server(@signature)
27
- end
28
-
29
- def to_s
30
- "#{@host}:#{@port}"
31
- end
32
- end
33
- end
34
- end
@@ -1,56 +0,0 @@
1
- module Thin
2
- module Backends
3
- # Backend to act as a UNIX domain socket server.
4
- class UnixServer < Base
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 domain 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
- old_umask = File.umask(0)
18
- begin
19
- EventMachine.start_unix_domain_server(@socket, UnixConnection, &method(:initialize_connection))
20
- # HACK EventMachine.start_unix_domain_server doesn't return the connection signature
21
- # so we have to go in the internal stuff to find it.
22
- @signature = EventMachine.instance_eval{@acceptors.keys.first}
23
- ensure
24
- File.umask(old_umask)
25
- end
26
- end
27
-
28
- # Stops the server
29
- def disconnect
30
- EventMachine.stop_server(@signature)
31
- end
32
-
33
- # Free up resources used by the backend.
34
- def close
35
- remove_socket_file
36
- end
37
-
38
- def to_s
39
- @socket
40
- end
41
-
42
- protected
43
- def remove_socket_file
44
- File.delete(@socket) if @socket && File.exist?(@socket)
45
- end
46
- end
47
- end
48
-
49
- # Connection through a UNIX domain socket.
50
- class UnixConnection < Connection
51
- protected
52
- def socket_address
53
- '127.0.0.1' # Unix domain sockets can only be local
54
- end
55
- end
56
- end
data/lib/thin/command.rb DELETED
@@ -1,53 +0,0 @@
1
- require 'open3'
2
-
3
- module Thin
4
- # Run a command through the +thin+ command-line script.
5
- class Command
6
- include Logging
7
-
8
- class << self
9
- # Path to the +thin+ script used to control the servers.
10
- # Leave this to default to use the one in the path.
11
- attr_accessor :script
12
- end
13
-
14
- def initialize(name, options={})
15
- @name = name
16
- @options = options
17
- end
18
-
19
- def self.run(*args)
20
- new(*args).run
21
- end
22
-
23
- # Send the command to the +thin+ script
24
- def run
25
- shell_cmd = shellify
26
- trace shell_cmd
27
- trap('INT') {} # Ignore INT signal to pass CTRL+C to subprocess
28
- Open3.popen3(shell_cmd) do |stdin, stdout, stderr|
29
- log_info stdout.gets until stdout.eof?
30
- log_info stderr.gets until stderr.eof?
31
- end
32
- end
33
-
34
- # Turn into a runnable shell command
35
- def shellify
36
- shellified_options = @options.inject([]) do |args, (name, value)|
37
- option_name = name.to_s.tr("_", "-")
38
- case value
39
- when NilClass,
40
- TrueClass then args << "--#{option_name}"
41
- when FalseClass
42
- when Array then value.each { |v| args << "--#{option_name}=#{v.inspect}" }
43
- else args << "--#{option_name}=#{value.inspect}"
44
- end
45
- args
46
- end
47
-
48
- raise ArgumentError, "Path to thin script can't be found, set Command.script" unless self.class.script
49
-
50
- "#{self.class.script} #{@name} #{shellified_options.compact.join(' ')}"
51
- end
52
- end
53
- end