thin 0.7.0-x86-mswin32-60 → 0.7.1-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.

Files changed (47) hide show
  1. data/CHANGELOG +11 -1
  2. data/COMMITTERS +3 -0
  3. data/README +4 -0
  4. data/Rakefile +2 -0
  5. data/ext/thin_parser/thin.c +9 -0
  6. data/lib/thin.rb +5 -5
  7. data/lib/thin/backends/base.rb +114 -0
  8. data/lib/thin/{connectors → backends}/swiftiply_client.rb +6 -6
  9. data/lib/thin/{connectors → backends}/tcp_server.rb +2 -2
  10. data/lib/thin/{connectors → backends}/unix_server.rb +19 -8
  11. data/lib/thin/connection.rb +3 -3
  12. data/lib/thin/controllers/controller.rb +6 -5
  13. data/lib/thin/controllers/service.rb +3 -1
  14. data/lib/thin/daemonizing.rb +20 -7
  15. data/lib/thin/logging.rb +1 -1
  16. data/lib/thin/request.rb +5 -5
  17. data/lib/thin/server.rb +63 -78
  18. data/lib/thin/version.rb +4 -2
  19. data/lib/thin_parser.so +0 -0
  20. data/spec/{connectors → backends}/swiftiply_client_spec.rb +10 -10
  21. data/spec/{connectors → backends}/tcp_server_spec.rb +5 -5
  22. data/spec/backends/unix_server_spec.rb +37 -0
  23. data/spec/command_spec.rb +1 -0
  24. data/spec/connection_spec.rb +1 -1
  25. data/spec/controllers/controller_spec.rb +1 -0
  26. data/spec/daemonizing_spec.rb +31 -23
  27. data/spec/{request/perf_spec.rb → perf/request_perf_spec.rb} +0 -0
  28. data/spec/perf/response_perf_spec.rb +19 -0
  29. data/spec/perf/server_perf_spec.rb +39 -0
  30. data/spec/request/mongrel_spec.rb +1 -1
  31. data/spec/response_spec.rb +0 -11
  32. data/spec/runner_spec.rb +4 -4
  33. data/spec/server/builder_spec.rb +8 -3
  34. data/spec/server/pipelining_spec.rb +6 -5
  35. data/spec/server/swiftiply_spec.rb +25 -20
  36. data/spec/server/tcp_spec.rb +1 -9
  37. data/spec/server/unix_socket_spec.rb +1 -5
  38. data/spec/server_spec.rb +4 -7
  39. data/spec/spec_helper.rb +29 -5
  40. data/tasks/announce.rake +2 -2
  41. data/tasks/email.erb +13 -15
  42. data/tasks/ext.rake +28 -24
  43. data/tasks/gem.rake +8 -4
  44. data/tasks/spec.rake +14 -9
  45. metadata +19 -15
  46. data/lib/thin/connectors/connector.rb +0 -73
  47. data/spec/connectors/unix_server_spec.rb +0 -43
@@ -1,6 +1,6 @@
1
1
  module Thin
2
2
  # The uterly famous Thin HTTP server.
3
- # It listen for incoming request through a given connector
3
+ # It listen for incoming request through a given backend
4
4
  # and forward all request to +app+.
5
5
  #
6
6
  # == TCP server
@@ -16,12 +16,12 @@ module Thin
16
16
  #
17
17
  # Thin::Server.start('/tmp/thin.sock', nil, app)
18
18
  #
19
- # == Using a custom connector
19
+ # == Using a custom backend
20
20
  # You can implement your own way to connect the server to its client by creating your
21
- # own Thin::Connectors::Connector class and pass it as the first argument.
21
+ # own Backend class and pass it as the first argument.
22
22
  #
23
- # connector = Thin::Connectors::MyFancyConnector.new('galaxy://faraway:1345')
24
- # Thin::Server.start(connector, nil, app)
23
+ # backend = Thin::Backends::MyFancyBackend.new('galaxy://faraway:1345')
24
+ # Thin::Server.start(backend, nil, app)
25
25
  #
26
26
  # == Rack application (+app+)
27
27
  # All requests will be processed through +app+ that must be a valid Rack adapter.
@@ -55,46 +55,48 @@ module Thin
55
55
  # Application (Rack adapter) called with the request that produces the response.
56
56
  attr_accessor :app
57
57
 
58
- # Connector handling the connections to the clients.
59
- attr_accessor :connector
60
-
61
- # Maximum number of file or socket descriptors that the server may open.
62
- attr_accessor :maximum_connections
58
+ # Backend handling the connections to the clients.
59
+ attr_accessor :backend
63
60
 
64
61
  # Maximum number of seconds for incoming data to arrive before the connection
65
62
  # is dropped.
66
- def_delegators :@connector, :timeout, :timeout=
63
+ def_delegators :@backend, :timeout, :timeout=
64
+
65
+ # Maximum number of file or socket descriptors that the server may open.
66
+ def_delegators :@backend, :maximum_connections, :maximum_connections=
67
67
 
68
68
  # Maximum number of connection that can be persistent at the same time.
69
69
  # Most browser never close the connection so most of the time they are closed
70
70
  # when the timeout occur. If we don't control the number of persistent connection,
71
71
  # if would be very easy to overflow the server for a DoS attack.
72
- def_delegators :@connector, :maximum_persistent_connections, :maximum_persistent_connections=
72
+ def_delegators :@backend, :maximum_persistent_connections, :maximum_persistent_connections=
73
73
 
74
74
  # Address and port on which the server is listening for connections.
75
- def_delegators :@connector, :host, :port
75
+ def_delegators :@backend, :host, :port
76
76
 
77
77
  # UNIX domain socket on which the server is listening for connections.
78
- def_delegator :@connector, :socket
78
+ def_delegator :@backend, :socket
79
79
 
80
- def initialize(host_or_socket_or_connector, port=DEFAULT_PORT, app=nil, &block)
81
- # Try to intelligently select which connector to use.
82
- @connector = case
83
- when host_or_socket_or_connector.is_a?(Connectors::Connector)
84
- host_or_socket_or_connector
85
- when host_or_socket_or_connector.include?('/')
86
- Connectors::UnixServer.new(host_or_socket_or_connector)
80
+ def initialize(host_or_socket_or_backend, port=DEFAULT_PORT, app=nil, &block)
81
+ # Try to intelligently select which backend to use.
82
+ @backend = case
83
+ when host_or_socket_or_backend.is_a?(Backends::Base)
84
+ host_or_socket_or_backend
85
+ when host_or_socket_or_backend.include?('/')
86
+ Backends::UnixServer.new(host_or_socket_or_backend)
87
87
  else
88
- Connectors::TcpServer.new(host_or_socket_or_connector, port.to_i)
88
+ Backends::TcpServer.new(host_or_socket_or_backend, port.to_i)
89
89
  end
90
+
91
+ load_cgi_multipart_eof_fix
90
92
 
91
- @app = app
92
- @connector.server = self
93
+ @app = app
94
+ @backend.server = self
93
95
 
94
96
  # Set defaults
95
- @maximum_connections = DEFAULT_MAXIMUM_CONNECTIONS
96
- @connector.maximum_persistent_connections = DEFAULT_MAXIMUM_PERSISTENT_CONNECTIONS
97
- @connector.timeout = DEFAULT_TIMEOUT
97
+ @backend.maximum_connections = DEFAULT_MAXIMUM_CONNECTIONS
98
+ @backend.maximum_persistent_connections = DEFAULT_MAXIMUM_PERSISTENT_CONNECTIONS
99
+ @backend.timeout = DEFAULT_TIMEOUT
98
100
 
99
101
  # Allow using Rack builder as a block
100
102
  @app = Rack::Builder.new(&block).to_app if block
@@ -124,17 +126,14 @@ module Thin
124
126
 
125
127
  setup_signals
126
128
 
127
- # See http://rubyeventmachine.com/pub/rdoc/files/EPOLL.html
128
- EventMachine.epoll
129
-
130
129
  log ">> Thin web server (v#{VERSION::STRING} codename #{VERSION::CODENAME})"
131
130
  debug ">> Debugging ON"
132
131
  trace ">> Tracing ON"
133
-
134
- log ">> Listening on #{@connector}, CTRL+C to stop"
135
132
 
136
- @running = true
137
- EventMachine.run { @connector.connect }
133
+ log ">> Maximum connections set to #{@backend.maximum_connections}"
134
+ log ">> Listening on #{@backend}, CTRL+C to stop"
135
+
136
+ @backend.start
138
137
  end
139
138
  alias :start! :start
140
139
 
@@ -144,15 +143,11 @@ module Thin
144
143
  # new requests and wait for all current connections to finish.
145
144
  # Calling twice is the equivalent of calling <tt>stop!</tt>.
146
145
  def stop
147
- if @running
148
- @running = false
149
-
150
- # Do not accept anymore connection
151
- @connector.disconnect
152
-
153
- unless wait_for_connections_and_stop
154
- # Still some connections running, schedule a check later
155
- EventMachine.add_periodic_timer(1) { wait_for_connections_and_stop }
146
+ if running?
147
+ @backend.stop
148
+ unless @backend.empty?
149
+ log ">> Waiting for #{@backend.size} connection(s) to finish, " +
150
+ "can take up to #{timeout} sec, CTRL+C to stop now"
156
151
  end
157
152
  else
158
153
  stop!
@@ -166,14 +161,18 @@ module Thin
166
161
  def stop!
167
162
  log ">> Stopping ..."
168
163
 
169
- @connector.close_connections
170
- EventMachine.stop
171
-
172
- @connector.close
164
+ @backend.stop!
165
+ end
166
+
167
+ # == Configure the server
168
+ # The process might need to have superuser privilege to set configure
169
+ # server with optimal options.
170
+ def config
171
+ @backend.config
173
172
  end
174
173
 
175
174
  def name
176
- "thin server (#{@connector})"
175
+ "thin server (#{@backend})"
177
176
  end
178
177
  alias :to_s :name
179
178
 
@@ -181,42 +180,28 @@ module Thin
181
180
  # Note that the server might still be running and return +false+ when
182
181
  # shuting down and waiting for active connections to complete.
183
182
  def running?
184
- @running
185
- end
186
-
187
- # Set the maximum number of socket descriptors that the server may open.
188
- # The process needs to have required privilege to set it higher the 1024 on
189
- # some systems.
190
- def set_descriptor_table_size!
191
- return 0 if Thin.win? # Not supported on Windows
192
-
193
- requested_maximum_connections = @maximum_connections
194
- @maximum_connections = EventMachine.set_descriptor_table_size(requested_maximum_connections)
195
-
196
- log ">> Setting maximum connections to #{@maximum_connections}"
197
- if @maximum_connections < requested_maximum_connections
198
- log "!! Maximum connections smaller then requested, " +
199
- "run with sudo to set higher"
200
- end
201
-
202
- @maximum_connections
183
+ @backend.running?
203
184
  end
204
185
 
205
186
  protected
206
- def wait_for_connections_and_stop
207
- if @connector.empty?
208
- stop!
209
- true
210
- else
211
- log ">> Waiting for #{@connector.size} connection(s) to finish, can take up to #{timeout} sec, CTRL+C to stop now"
212
- false
213
- end
214
- end
215
-
216
187
  def setup_signals
217
188
  trap('QUIT') { stop } unless Thin.win?
218
189
  trap('INT') { stop! }
219
190
  trap('TERM') { stop! }
220
- end
191
+ end
192
+
193
+ # Taken from Mongrel cgi_multipart_eof_fix
194
+ def load_cgi_multipart_eof_fix
195
+ version = RUBY_VERSION.split('.').map { |i| i.to_i }
196
+
197
+ if version[0] <= 1 && version[1] <= 8 && version[2] <= 5 && RUBY_PLATFORM !~ /java/
198
+ begin
199
+ require 'cgi_multipart_eof_fix'
200
+ rescue LoadError
201
+ log "!! Ruby 1.8.5 is not secure please install cgi_multipart_eof_fix:"
202
+ log " gem install cgi_multipart_eof_fix"
203
+ end
204
+ end
205
+ end
221
206
  end
222
207
  end
@@ -6,11 +6,13 @@ module Thin
6
6
  module VERSION #:nodoc:
7
7
  MAJOR = 0
8
8
  MINOR = 7
9
- TINY = 0
9
+ TINY = 1
10
10
 
11
11
  STRING = [MAJOR, MINOR, TINY].join('.')
12
12
 
13
- CODENAME = 'Spherical Cow'
13
+ CODENAME = 'Fancy Pants'
14
+
15
+ RACK = [0, 3].freeze # Latest Rack version that was tested
14
16
  end
15
17
 
16
18
  NAME = 'thin'.freeze
Binary file
@@ -1,22 +1,22 @@
1
1
  require File.dirname(__FILE__) + '/../spec_helper'
2
2
 
3
- describe Connectors::SwiftiplyClient do
3
+ describe Backends::SwiftiplyClient do
4
4
  before do
5
- @connector = Connectors::SwiftiplyClient.new('0.0.0.0', 3333)
6
- @connector.server = mock('server', :null_object => true)
5
+ @backend = Backends::SwiftiplyClient.new('0.0.0.0', 3333)
6
+ @backend.server = mock('server', :null_object => true)
7
7
  end
8
8
 
9
9
  it "should connect" do
10
10
  EventMachine.run do
11
- @connector.connect
11
+ @backend.connect
12
12
  EventMachine.stop
13
13
  end
14
14
  end
15
15
 
16
16
  it "should disconnect" do
17
17
  EventMachine.run do
18
- @connector.connect
19
- @connector.disconnect
18
+ @backend.connect
19
+ @backend.disconnect
20
20
  EventMachine.stop
21
21
  end
22
22
  end
@@ -25,8 +25,8 @@ end
25
25
  describe SwiftiplyConnection do
26
26
  before do
27
27
  @connection = SwiftiplyConnection.new(nil)
28
- @connection.connector = Connectors::SwiftiplyClient.new('0.0.0.0', 3333)
29
- @connection.connector.server = mock('server', :null_object => true)
28
+ @connection.backend = Backends::SwiftiplyClient.new('0.0.0.0', 3333)
29
+ @connection.backend.server = mock('server', :null_object => true)
30
30
  end
31
31
 
32
32
  it do
@@ -39,7 +39,7 @@ describe SwiftiplyConnection do
39
39
  end
40
40
 
41
41
  it "should reconnect on unbind" do
42
- @connection.connector.stub!(:running?).and_return(true)
42
+ @connection.backend.stub!(:running?).and_return(true)
43
43
  @connection.stub!(:rand).and_return(0) # Make sure we don't wait
44
44
 
45
45
  @connection.should_receive(:reconnect).with('0.0.0.0', 3333)
@@ -51,7 +51,7 @@ describe SwiftiplyConnection do
51
51
  end
52
52
 
53
53
  it "should not reconnect when not running" do
54
- @connection.connector.stub!(:running?).and_return(false)
54
+ @connection.backend.stub!(:running?).and_return(false)
55
55
  EventMachine.should_not_receive(:add_timer)
56
56
  @connection.unbind
57
57
  end
@@ -1,21 +1,21 @@
1
1
  require File.dirname(__FILE__) + '/../spec_helper'
2
2
 
3
- describe Connectors::TcpServer do
3
+ describe Backends::TcpServer do
4
4
  before do
5
- @connector = Connectors::TcpServer.new('0.0.0.0', 3333)
5
+ @backend = Backends::TcpServer.new('0.0.0.0', 3333)
6
6
  end
7
7
 
8
8
  it "should connect" do
9
9
  EventMachine.run do
10
- @connector.connect
10
+ @backend.connect
11
11
  EventMachine.stop
12
12
  end
13
13
  end
14
14
 
15
15
  it "should disconnect" do
16
16
  EventMachine.run do
17
- @connector.connect
18
- @connector.disconnect
17
+ @backend.connect
18
+ @backend.disconnect
19
19
  EventMachine.stop
20
20
  end
21
21
  end
@@ -0,0 +1,37 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe Backends::UnixServer do
4
+ before do
5
+ @backend = Backends::UnixServer.new('/tmp/thin-test.sock')
6
+ end
7
+
8
+ it "should connect" do
9
+ EventMachine.run do
10
+ @backend.connect
11
+ EventMachine.stop
12
+ end
13
+ end
14
+
15
+ it "should disconnect" do
16
+ EventMachine.run do
17
+ @backend.connect
18
+ @backend.disconnect
19
+ EventMachine.stop
20
+ end
21
+ end
22
+
23
+ it "should remove socket file on close" do
24
+ @backend.close
25
+ File.exist?('/tmp/thin-test.sock').should be_false
26
+ end
27
+ end
28
+
29
+ describe UnixConnection do
30
+ before do
31
+ @connection = UnixConnection.new(nil)
32
+ end
33
+
34
+ it "should return 127.0.0.1 as remote_address" do
35
+ @connection.remote_address.should == '127.0.0.1'
36
+ end
37
+ end
@@ -2,6 +2,7 @@ require File.dirname(__FILE__) + '/spec_helper'
2
2
 
3
3
  describe Command do
4
4
  before do
5
+ Command.script = 'thin'
5
6
  @command = Command.new(:start, :port => 3000, :daemonize => true, :log => 'hi.log')
6
7
  end
7
8
 
@@ -51,7 +51,7 @@ describe Connection do
51
51
  end
52
52
 
53
53
  it "should return remote_address" do
54
- @connection.stub!(:get_peername).and_return("\020\002?E\177\000\000\001\000\000\000\000\000\000\000\000")
54
+ @connection.stub!(:get_peername).and_return(Socket.pack_sockaddr_in(3000, '127.0.0.1'))
55
55
  @connection.remote_address.should == '127.0.0.1'
56
56
  end
57
57
 
@@ -16,6 +16,7 @@ describe Controller, 'start' do
16
16
  @adapter = OpenStruct.new
17
17
 
18
18
  Server.should_receive(:new).with('0.0.0.0', 3000).and_return(@server)
19
+ @server.should_receive(:config)
19
20
  Rack::Adapter::Rails.stub!(:new).and_return(@adapter)
20
21
  end
21
22
 
@@ -8,22 +8,22 @@ class TestServer
8
8
  end
9
9
 
10
10
  def name
11
- 'thin'
11
+ 'Thin test server'
12
12
  end
13
13
  end
14
14
 
15
15
  describe 'Daemonizing' do
16
-
17
- before(:all) do
16
+ before :all do
18
17
  @logfile = File.dirname(__FILE__) + '/../log/daemonizing_test.log'
18
+ @pidfile = 'test.pid'
19
19
  File.delete(@logfile) if File.exist?(@logfile)
20
- @child_processes = []
20
+ File.delete(@pidfile) if File.exist?(@pidfile)
21
21
  end
22
22
 
23
- before(:each) do
23
+ before :each do
24
24
  @server = TestServer.new
25
25
  @server.log_file = @logfile
26
- @server.pid_file = 'test.pid'
26
+ @server.pid_file = @pidfile
27
27
  @pid = nil
28
28
  end
29
29
 
@@ -34,7 +34,6 @@ describe 'Daemonizing' do
34
34
 
35
35
  it 'should create a pid file' do
36
36
  @pid = fork do
37
- @child_processes << Process.pid
38
37
  @server.daemonize
39
38
  sleep 1
40
39
  end
@@ -49,7 +48,6 @@ describe 'Daemonizing' do
49
48
 
50
49
  it 'should redirect stdio to a log file' do
51
50
  @pid = fork do
52
- @child_processes << Process.pid
53
51
  @server.log_file = 'daemon_test.log'
54
52
  @server.daemonize
55
53
 
@@ -61,6 +59,8 @@ describe 'Daemonizing' do
61
59
  # Wait for the file to close and magical stuff to happen
62
60
  proc { sleep 0.1 until File.exist?('daemon_test.log') }.should take_less_then(3)
63
61
  sleep 0.5
62
+
63
+ @pid = @server.pid
64
64
 
65
65
  log = File.read('daemon_test.log')
66
66
  log.should include('simple puts', 'STDERR.puts', 'STDOUT.puts')
@@ -70,7 +70,6 @@ describe 'Daemonizing' do
70
70
 
71
71
  it 'should change privilege' do
72
72
  @pid = fork do
73
- @child_processes << Process.pid
74
73
  @server.daemonize
75
74
  @server.change_privilege('root', 'admin')
76
75
  end
@@ -80,33 +79,37 @@ describe 'Daemonizing' do
80
79
 
81
80
  it 'should kill process in pid file' do
82
81
  @pid = fork do
83
- @child_processes << Process.pid
84
82
  @server.daemonize
85
- loop { sleep 1 }
83
+ loop { sleep 3 }
86
84
  end
87
85
 
88
86
  server_should_start_in_less_then 3
87
+
88
+ @pid = @server.pid
89
89
 
90
90
  silence_stream STDOUT do
91
91
  TestServer.kill(@server.pid_file, 1)
92
92
  end
93
93
 
94
- File.exist?(@server.pid_file).should_not be_true
94
+ File.exist?(@server.pid_file).should be_false
95
95
  end
96
96
 
97
97
  it 'should send kill signal if timeout' do
98
98
  @pid = fork do
99
- @child_processes << Process.pid
100
99
  @server.should_receive(:stop) # pretend we cannot handle the INT signal
101
100
  @server.daemonize
102
101
  sleep 5
103
102
  end
104
103
 
105
104
  server_should_start_in_less_then 10
105
+
106
+ @pid = @server.pid
106
107
 
107
108
  silence_stream STDOUT do
108
109
  TestServer.kill(@server.pid_file, 1)
109
110
  end
111
+
112
+ sleep 1
110
113
 
111
114
  File.exist?(@server.pid_file).should be_false
112
115
  Process.running?(@pid).should be_false
@@ -114,13 +117,14 @@ describe 'Daemonizing' do
114
117
 
115
118
  it "should restart" do
116
119
  @pid = fork do
117
- @child_processes << Process.pid
118
120
  @server.on_restart {}
119
121
  @server.daemonize
120
122
  sleep 5
121
123
  end
122
124
 
123
125
  server_should_start_in_less_then 10
126
+
127
+ @pid = @server.pid
124
128
 
125
129
  silence_stream STDOUT do
126
130
  TestServer.restart(@server.pid_file)
@@ -129,31 +133,35 @@ describe 'Daemonizing' do
129
133
  proc { sleep 0.1 while File.exist?(@server.pid_file) }.should take_less_then(10)
130
134
  end
131
135
 
132
- it "should exit if pid file already exist" do
136
+ it "should exit and raise if pid file already exist" do
133
137
  @pid = fork do
134
- @child_processes << Process.pid
135
138
  @server.daemonize
136
139
  sleep 5
137
140
  end
138
141
  server_should_start_in_less_then 10
142
+
143
+ @pid = @server.pid
139
144
 
140
145
  proc { @server.daemonize }.should raise_error(PidFileExist)
141
146
 
142
147
  File.exist?(@server.pid_file).should be_true
143
148
  end
144
149
 
145
- after(:each) do
150
+ it "should should delete pid file if stale" do
151
+ # Create a file w/ a PID that does not exist
152
+ File.open(@server.pid_file, 'w') { |f| f << 999999999 }
153
+
154
+ @server.send(:remove_stale_pid_file)
155
+
156
+ File.exist?(@server.pid_file).should be_false
157
+ end
158
+
159
+ after do
146
160
  Process.kill(9, @pid.to_i) if @pid && Process.running?(@pid.to_i)
147
161
  Process.kill(9, @server.pid) if @server.pid && Process.running?(@server.pid)
148
162
  File.delete(@server.pid_file) rescue nil
149
163
  end
150
164
 
151
- after(:all) do
152
- @child_processes.each do |pid|
153
- Process.kill(9, pid) rescue nil
154
- end
155
- end
156
-
157
165
  private
158
166
  def server_should_start_in_less_then(sec=10)
159
167
  proc { sleep 0.1 until File.exist?(@server.pid_file) }.should take_less_then(10)