thin 0.6.2 → 0.6.3

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 (49) hide show
  1. data/CHANGELOG +34 -0
  2. data/bin/thin +2 -164
  3. data/example/config.ru +4 -1
  4. data/example/ramaze.ru +12 -0
  5. data/example/thin.god +70 -66
  6. data/example/vlad.rake +61 -0
  7. data/lib/rack/adapter/rails.rb +0 -3
  8. data/lib/rack/handler/thin.rb +6 -1
  9. data/lib/thin.rb +13 -4
  10. data/lib/thin/command.rb +9 -5
  11. data/lib/thin/connection.rb +5 -14
  12. data/lib/thin/connectors/connector.rb +61 -0
  13. data/lib/thin/connectors/tcp_server.rb +29 -0
  14. data/lib/thin/connectors/unix_server.rb +48 -0
  15. data/lib/thin/controllers/cluster.rb +115 -0
  16. data/lib/thin/controllers/controller.rb +85 -0
  17. data/lib/thin/controllers/service.rb +73 -0
  18. data/lib/thin/controllers/service.sh.erb +39 -0
  19. data/lib/thin/daemonizing.rb +9 -4
  20. data/lib/thin/headers.rb +2 -2
  21. data/lib/thin/runner.rb +166 -0
  22. data/lib/thin/server.rb +109 -89
  23. data/lib/thin/stats.html.erb +216 -0
  24. data/lib/thin/stats.rb +1 -249
  25. data/lib/thin/version.rb +10 -3
  26. data/spec/command_spec.rb +0 -1
  27. data/spec/configs/cluster.yml +9 -0
  28. data/spec/configs/single.yml +9 -0
  29. data/spec/{cluster_spec.rb → controllers/cluster_spec.rb} +22 -10
  30. data/spec/controllers/controller_spec.rb +85 -0
  31. data/spec/controllers/service_spec.rb +51 -0
  32. data/spec/daemonizing_spec.rb +73 -9
  33. data/spec/request/mongrel_spec.rb +39 -0
  34. data/spec/{request_spec.rb → request/parser_spec.rb} +11 -143
  35. data/spec/request/perf_spec.rb +50 -0
  36. data/spec/request/processing_spec.rb +46 -0
  37. data/spec/runner_spec.rb +135 -0
  38. data/spec/server/builder_spec.rb +38 -0
  39. data/spec/server/stopping_spec.rb +45 -0
  40. data/spec/server/tcp_spec.rb +54 -0
  41. data/spec/server/unix_socket_spec.rb +30 -0
  42. data/spec/spec_helper.rb +49 -16
  43. data/tasks/announce.rake +7 -3
  44. data/tasks/email.erb +8 -18
  45. data/tasks/gem.rake +8 -1
  46. data/tasks/stats.rake +21 -8
  47. metadata +33 -6
  48. data/lib/thin/cluster.rb +0 -123
  49. data/spec/server_spec.rb +0 -200
@@ -0,0 +1,39 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+ require 'digest/sha1'
3
+
4
+ describe Request, 'legacy Mongrel tests' do
5
+ it 'should raise error on large header names' do
6
+ proc { R("GET /#{rand_data(10,120)} HTTP/1.1\r\nX-#{rand_data(1024, 1024+(1024))}: Test\r\n\r\n") }.
7
+ should raise_error(InvalidRequest)
8
+ end
9
+
10
+ it 'should raise error on large mangled field values' do
11
+ proc { R("GET /#{rand_data(10,120)} HTTP/1.1\r\nX-Test: #{rand_data(1024, 100*1024+(1024), false)}\r\n\r\n") }.
12
+ should raise_error(InvalidRequest)
13
+ end
14
+
15
+ it 'should raise error on big fat ugly headers' do
16
+ get = "GET /#{rand_data(10,120)} HTTP/1.1\r\n"
17
+ get << "X-Test: test\r\n" * (80 * 1024)
18
+ proc { R(get) }.should raise_error(InvalidRequest)
19
+ end
20
+
21
+ it 'should raise error on random garbage' do
22
+ proc { R("GET #{rand_data(1024, 1024+(1024), false)} #{rand_data(1024, 1024+(1024), false)}\r\n\r\n") }.
23
+ should raise_error(InvalidRequest)
24
+ end
25
+
26
+ private
27
+ def rand_data(min, max, readable=true)
28
+ count = min + ((rand(max)+1) *10).to_i
29
+ res = count.to_s + "/"
30
+
31
+ if readable
32
+ res << Digest::SHA1.hexdigest(rand(count * 100).to_s) * (count / 40)
33
+ else
34
+ res << Digest::SHA1.digest(rand(count * 100).to_s) * (count / 20)
35
+ end
36
+
37
+ return res
38
+ end
39
+ end
@@ -1,7 +1,6 @@
1
- require File.dirname(__FILE__) + '/spec_helper'
2
- require 'digest/sha1'
1
+ require File.dirname(__FILE__) + '/../spec_helper'
3
2
 
4
- describe Request do
3
+ describe Request, 'parser' do
5
4
  it 'should include basic headers' do
6
5
  request = R("GET / HTTP/1.1\r\nHost: localhost\r\n\r\n")
7
6
  request.env['SERVER_PROTOCOL'].should == 'HTTP/1.1'
@@ -50,27 +49,6 @@ describe Request do
50
49
  request.should validate_with_lint
51
50
  end
52
51
 
53
- it 'should raise error on large header names' do
54
- proc { R("GET /#{rand_data(10,120)} HTTP/1.1\r\nX-#{rand_data(1024, 1024+(1024))}: Test\r\n\r\n") }.
55
- should raise_error(InvalidRequest)
56
- end
57
-
58
- it 'should raise error on large mangled field values' do
59
- proc { R("GET /#{rand_data(10,120)} HTTP/1.1\r\nX-Test: #{rand_data(1024, 100*1024+(1024), false)}\r\n\r\n") }.
60
- should raise_error(InvalidRequest)
61
- end
62
-
63
- it 'should raise error on big fat ugly headers' do
64
- get = "GET /#{rand_data(10,120)} HTTP/1.1\r\n"
65
- get << "X-Test: test\r\n" * (80 * 1024)
66
- proc { R(get) }.should raise_error(InvalidRequest)
67
- end
68
-
69
- it 'should raise error on random garbage' do
70
- proc { R("GET #{rand_data(1024, 1024+(1024), false)} #{rand_data(1024, 1024+(1024), false)}\r\n\r\n") }.
71
- should raise_error(InvalidRequest)
72
- end
73
-
74
52
  it 'should parse headers from GET request' do
75
53
  request = R(<<-EOS, true)
76
54
  GET / HTTP/1.1
@@ -89,10 +67,10 @@ EOS
89
67
  request.env['SERVER_NAME'].should == 'localhost'
90
68
  request.env['SERVER_PORT'].should == '3000'
91
69
  request.env['HTTP_COOKIE'].should == 'mium=7'
92
-
70
+
93
71
  request.should validate_with_lint
94
72
  end
95
-
73
+
96
74
  it 'should parse POST request with data' do
97
75
  request = R(<<-EOS.chomp, true)
98
76
  POST /postit HTTP/1.1
@@ -120,10 +98,10 @@ EOS
120
98
  request.body.rewind
121
99
  request.body.read.should == 'name=marc&email=macournoyer@gmail.com'
122
100
  request.body.class.should == StringIO
123
-
101
+
124
102
  request.should validate_with_lint
125
103
  end
126
-
104
+
127
105
  it 'should not fuck up on stupid fucked IE6 headers' do
128
106
  body = <<-EOS
129
107
  POST /codes/58-tracking-file-downloads-automatically-in-google-analytics-with-prototype/refactors HTTP/1.0
@@ -145,10 +123,10 @@ a
145
123
  EOS
146
124
  request = R(body, true)
147
125
  request.env['HTTP_COOKIE2'].should == '$Version="1"'
148
-
126
+
149
127
  request.should validate_with_lint
150
128
  end
151
-
129
+
152
130
  it 'shoud accept long query string' do
153
131
  body = <<-EOS
154
132
  GET /session?open_id_complete=1&nonce=ytPOcwni&nonce=ytPOcwni&openid.assoc_handle=%7BHMAC-SHA1%7D%7B473e38fe%7D%7BJTjJxA%3D%3D%7D&openid.identity=http%3A%2F%2Fmacournoyer.myopenid.com%2F&openid.mode=id_res&openid.op_endpoint=http%3A%2F%2Fwww.myopenid.com%2Fserver&openid.response_nonce=2007-11-29T01%3A19%3A35ZGA5FUU&openid.return_to=http%3A%2F%2Flocalhost%3A3000%2Fsession%3Fopen_id_complete%3D1%26nonce%3DytPOcwni%26nonce%3DytPOcwni&openid.sig=lPIRgwpfR6JAdGGnb0ZjcY%2FWjr8%3D&openid.signed=assoc_handle%2Cidentity%2Cmode%2Cop_endpoint%2Cresponse_nonce%2Creturn_to%2Csigned%2Csreg.email%2Csreg.nickname&openid.sreg.email=macournoyer%40yahoo.ca&openid.sreg.nickname=macournoyer HTTP/1.1
@@ -156,9 +134,9 @@ Host: localhost:3000
156
134
 
157
135
  EOS
158
136
  request = R(body, true)
159
-
137
+
160
138
  request.env['QUERY_STRING'].should == 'open_id_complete=1&nonce=ytPOcwni&nonce=ytPOcwni&openid.assoc_handle=%7BHMAC-SHA1%7D%7B473e38fe%7D%7BJTjJxA%3D%3D%7D&openid.identity=http%3A%2F%2Fmacournoyer.myopenid.com%2F&openid.mode=id_res&openid.op_endpoint=http%3A%2F%2Fwww.myopenid.com%2Fserver&openid.response_nonce=2007-11-29T01%3A19%3A35ZGA5FUU&openid.return_to=http%3A%2F%2Flocalhost%3A3000%2Fsession%3Fopen_id_complete%3D1%26nonce%3DytPOcwni%26nonce%3DytPOcwni&openid.sig=lPIRgwpfR6JAdGGnb0ZjcY%2FWjr8%3D&openid.signed=assoc_handle%2Cidentity%2Cmode%2Cop_endpoint%2Cresponse_nonce%2Creturn_to%2Csigned%2Csreg.email%2Csreg.nickname&openid.sreg.email=macournoyer%40yahoo.ca&openid.sreg.nickname=macournoyer'
161
-
139
+
162
140
  request.should validate_with_lint
163
141
  end
164
142
 
@@ -171,119 +149,9 @@ Content-Length: 300
171
149
  aye
172
150
  EOS
173
151
  request = R(body, true)
174
-
152
+
175
153
  request.body.rewind
176
154
  request.body.read.should == 'aye'
177
155
  end
178
156
 
179
- it 'should parse in chunks' do
180
- request = Request.new
181
- request.parse("POST / HTTP/1.1\r\n").should be_false
182
- request.parse("Host: localhost\r\n").should be_false
183
- request.parse("Content-Length: 9\r\n").should be_false
184
- request.parse("\r\nvery ").should be_false
185
- request.parse("cool").should be_true
186
-
187
- request.env['CONTENT_LENGTH'].should == '9'
188
- request.body.read.should == 'very cool'
189
- request.should validate_with_lint
190
- end
191
-
192
- it "should move body to tempfile when too big" do
193
- request = Request.new
194
- request.parse("POST /postit HTTP/1.1\r\nContent-Length: #{Request::MAX_BODY*2}\r\n\r\n#{'X' * Request::MAX_BODY}")
195
- request.parse('X' * Request::MAX_BODY)
196
-
197
- request.body.size.should == Request::MAX_BODY * 2
198
- request.should be_finished
199
- request.body.class.should == Tempfile
200
- end
201
-
202
- it "should delete body tempfile when closing" do
203
- body = 'X' * (Request::MAX_BODY + 1)
204
-
205
- request = R(<<-EOS.chomp, true)
206
- POST /postit HTTP/1.1
207
- Content-Length: #{body.size}
208
-
209
- #{body}
210
- EOS
211
-
212
- request.body.path.should_not be_nil
213
- request.close
214
- request.body.path.should be_nil
215
- end
216
-
217
- it "should raise error when header is too big" do
218
- big_headers = "X-Test: X\r\n" * (1024 * (80 + 32))
219
- proc { R("GET / HTTP/1.1\r\n#{big_headers}\r\n") }.should raise_error(InvalidRequest)
220
- end
221
-
222
- it "should be faster then #{max_parsing_time = 0.0002} RubySeconds" do
223
- body = <<-EOS.chomp.gsub("\n", "\r\n")
224
- POST /postit HTTP/1.1
225
- Host: localhost:3000
226
- User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-US; rv:1.8.1.9) Gecko/20071025 Firefox/2.0.0.9
227
- Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
228
- Accept-Language: en-us,en;q=0.5
229
- Accept-Encoding: gzip,deflate
230
- Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
231
- Keep-Alive: 300
232
- Connection: keep-alive
233
- Content-Type: text/html
234
- Content-Length: 37
235
-
236
- hi=there&name=marc&email=macournoyer@gmail.com
237
- EOS
238
-
239
- proc { R(body) }.should be_faster_then(max_parsing_time)
240
- end
241
-
242
- it 'should be comparable to Mongrel parser' do
243
- require 'http11'
244
-
245
- body = <<-EOS.chomp.gsub("\n", "\r\n")
246
- POST /postit HTTP/1.1
247
- Host: localhost:3000
248
- User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-US; rv:1.8.1.9) Gecko/20071025 Firefox/2.0.0.9
249
- Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
250
- Accept-Language: en-us,en;q=0.5
251
- Accept-Encoding: gzip,deflate
252
- Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
253
- Keep-Alive: 300
254
- Connection: keep-alive
255
- Content-Type: text/html
256
- Content-Length: 37
257
-
258
- hi=there&name=marc&email=macournoyer@gmail.com
259
- EOS
260
-
261
- tests = 10_000
262
- puts
263
- Benchmark.bmbm(10) do |results|
264
- results.report("mongrel:") { tests.times { Mongrel::HttpParser.new.execute({}, body.dup, 0) } }
265
- results.report("thin:") { tests.times { Thin::HttpParser.new.execute({'rack.input' => StringIO.new}, body.dup, 0) } }
266
- end
267
- end if ENV['BM']
268
-
269
- private
270
- def rand_data(min, max, readable=true)
271
- count = min + ((rand(max)+1) *10).to_i
272
- res = count.to_s + "/"
273
-
274
- if readable
275
- res << Digest::SHA1.hexdigest(rand(count * 100).to_s) * (count / 40)
276
- else
277
- res << Digest::SHA1.digest(rand(count * 100).to_s) * (count / 20)
278
- end
279
-
280
- return res
281
- end
282
-
283
- def R(raw, convert_line_feed=false)
284
- raw.gsub!("\n", "\r\n") if convert_line_feed
285
- request = Thin::Request.new
286
- request.parse(raw)
287
- request
288
- end
289
157
  end
@@ -0,0 +1,50 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe Request, 'performance' do
4
+ it "should be faster then #{max_parsing_time = 0.0002} RubySeconds" do
5
+ body = <<-EOS.chomp.gsub("\n", "\r\n")
6
+ POST /postit HTTP/1.1
7
+ Host: localhost:3000
8
+ User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-US; rv:1.8.1.9) Gecko/20071025 Firefox/2.0.0.9
9
+ Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
10
+ Accept-Language: en-us,en;q=0.5
11
+ Accept-Encoding: gzip,deflate
12
+ Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
13
+ Keep-Alive: 300
14
+ Connection: keep-alive
15
+ Content-Type: text/html
16
+ Content-Length: 37
17
+
18
+ hi=there&name=marc&email=macournoyer@gmail.com
19
+ EOS
20
+
21
+ proc { R(body) }.should be_faster_then(max_parsing_time)
22
+ end
23
+
24
+ it 'should be comparable to Mongrel parser' do
25
+ require 'http11'
26
+
27
+ body = <<-EOS.chomp.gsub("\n", "\r\n")
28
+ POST /postit HTTP/1.1
29
+ Host: localhost:3000
30
+ User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-US; rv:1.8.1.9) Gecko/20071025 Firefox/2.0.0.9
31
+ Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
32
+ Accept-Language: en-us,en;q=0.5
33
+ Accept-Encoding: gzip,deflate
34
+ Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
35
+ Keep-Alive: 300
36
+ Connection: keep-alive
37
+ Content-Type: text/html
38
+ Content-Length: 37
39
+
40
+ hi=there&name=marc&email=macournoyer@gmail.com
41
+ EOS
42
+
43
+ tests = 10_000
44
+ puts
45
+ Benchmark.bmbm(10) do |results|
46
+ results.report("mongrel:") { tests.times { Mongrel::HttpParser.new.execute({}, body.dup, 0) } }
47
+ results.report("thin:") { tests.times { Thin::HttpParser.new.execute({'rack.input' => StringIO.new}, body.dup, 0) } }
48
+ end
49
+ end if ENV['BM']
50
+ end
@@ -0,0 +1,46 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe Request, 'processing' do
4
+ it 'should parse in chunks' do
5
+ request = Request.new
6
+ request.parse("POST / HTTP/1.1\r\n").should be_false
7
+ request.parse("Host: localhost\r\n").should be_false
8
+ request.parse("Content-Length: 9\r\n").should be_false
9
+ request.parse("\r\nvery ").should be_false
10
+ request.parse("cool").should be_true
11
+
12
+ request.env['CONTENT_LENGTH'].should == '9'
13
+ request.body.read.should == 'very cool'
14
+ request.should validate_with_lint
15
+ end
16
+
17
+ it "should move body to tempfile when too big" do
18
+ request = Request.new
19
+ request.parse("POST /postit HTTP/1.1\r\nContent-Length: #{Request::MAX_BODY*2}\r\n\r\n#{'X' * Request::MAX_BODY}")
20
+ request.parse('X' * Request::MAX_BODY)
21
+
22
+ request.body.size.should == Request::MAX_BODY * 2
23
+ request.should be_finished
24
+ request.body.class.should == Tempfile
25
+ end
26
+
27
+ it "should delete body tempfile when closing" do
28
+ body = 'X' * (Request::MAX_BODY + 1)
29
+
30
+ request = R(<<-EOS.chomp, true)
31
+ POST /postit HTTP/1.1
32
+ Content-Length: #{body.size}
33
+
34
+ #{body}
35
+ EOS
36
+
37
+ request.body.path.should_not be_nil
38
+ request.close
39
+ request.body.path.should be_nil
40
+ end
41
+
42
+ it "should raise error when header is too big" do
43
+ big_headers = "X-Test: X\r\n" * (1024 * (80 + 32))
44
+ proc { R("GET / HTTP/1.1\r\n#{big_headers}\r\n") }.should raise_error(InvalidRequest)
45
+ end
46
+ end
@@ -0,0 +1,135 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe Runner do
4
+ it "should parse options" do
5
+ runner = Runner.new(%w(start --pid test.pid --port 5000))
6
+
7
+ runner.options[:pid].should == 'test.pid'
8
+ runner.options[:port].should == 5000
9
+ end
10
+
11
+ it "should parse specified command" do
12
+ Runner.new(%w(start)).command.should == 'start'
13
+ Runner.new(%w(stop)).command.should == 'stop'
14
+ Runner.new(%w(restart)).command.should == 'restart'
15
+ end
16
+
17
+ it "should abort on unknow command" do
18
+ runner = Runner.new(%w(poop))
19
+
20
+ runner.should_receive(:abort)
21
+ runner.run!
22
+ end
23
+
24
+ it "should exit on empty command" do
25
+ runner = Runner.new([])
26
+
27
+ runner.should_receive(:exit).with(1)
28
+
29
+ silence_stream(STDOUT) do
30
+ runner.run!
31
+ end
32
+ end
33
+
34
+ it "should use Controller when controlling a single server" do
35
+ runner = Runner.new(%w(start))
36
+
37
+ controller = mock('controller')
38
+ controller.should_receive(:start)
39
+ Controller.should_receive(:new).and_return(controller)
40
+
41
+ runner.run!
42
+ end
43
+
44
+ it "should use Cluster controller when controlling multiple servers" do
45
+ runner = Runner.new(%w(start --servers 3))
46
+
47
+ controller = mock('cluster')
48
+ controller.should_receive(:start)
49
+ Cluster.should_receive(:new).and_return(controller)
50
+
51
+ runner.run!
52
+ end
53
+
54
+ it "should default to single server controller" do
55
+ Runner.new(%w(start)).should_not be_a_cluster
56
+ end
57
+
58
+ it "should consider as a cluster with :servers option" do
59
+ Runner.new(%w(start --servers 3)).should be_a_cluster
60
+ end
61
+
62
+ it "should consider as a cluster with :only option" do
63
+ Runner.new(%w(start --only 3000)).should be_a_cluster
64
+ end
65
+ end
66
+
67
+ describe Runner, 'with config file' do
68
+ before do
69
+ @runner = Runner.new(%w(start --config spec/configs/cluster.yml))
70
+ end
71
+
72
+ it "should load options from file with :config option" do
73
+ @runner.send :load_options_from_config_file!
74
+
75
+ @runner.options[:environment].should == 'production'
76
+ @runner.options[:chdir].should == 'spec/rails_app'
77
+ @runner.options[:port].should == 5000
78
+ @runner.options[:servers].should == 3
79
+ end
80
+
81
+ it "should change directory after loading config" do
82
+ @orig_dir = Dir.pwd
83
+
84
+ controller = mock('controller')
85
+ controller.should_receive(:respond_to?).with('start').and_return(true)
86
+ controller.should_receive(:start)
87
+ Cluster.should_receive(:new).and_return(controller)
88
+ expected_dir = File.expand_path('spec/rails_app')
89
+
90
+ begin
91
+ silence_stream(STDERR) do
92
+ @runner.run!
93
+ end
94
+
95
+ Dir.pwd.should == expected_dir
96
+
97
+ ensure
98
+ # any other spec using relative paths should work as expected
99
+ Dir.chdir(@orig_dir)
100
+ end
101
+ end
102
+ end
103
+
104
+ describe Runner, "service" do
105
+ before do
106
+ Thin.stub!(:linux?).and_return(true)
107
+
108
+ @controller = mock('service')
109
+ Service.stub!(:new).and_return(@controller)
110
+ end
111
+
112
+ it "should use Service controller when controlling all servers" do
113
+ runner = Runner.new(%w(start --all))
114
+
115
+ @controller.should_receive(:start)
116
+
117
+ runner.run!
118
+ end
119
+
120
+ it "should call install with arguments" do
121
+ runner = Runner.new(%w(install /etc/cool))
122
+
123
+ @controller.should_receive(:install).with('/etc/cool')
124
+
125
+ runner.run!
126
+ end
127
+
128
+ it "should call install without arguments" do
129
+ runner = Runner.new(%w(install))
130
+
131
+ @controller.should_receive(:install).with()
132
+
133
+ runner.run!
134
+ end
135
+ end