thin 0.5.4-x86-mswin32-60 → 0.6.0-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.

@@ -0,0 +1,64 @@
1
+ module Thin
2
+ # Rack adapter to log stats to a Rack application
3
+ module Stats
4
+ class Adapter
5
+ def initialize(app, path='/stats')
6
+ @app = app
7
+ @path = path
8
+
9
+ @requests = 0
10
+ @requests_finished = 0
11
+ @start_time = Time.now
12
+ end
13
+
14
+ def call(env)
15
+ if env['PATH_INFO'].index(@path) == 0
16
+ serve(env)
17
+ else
18
+ log(env) { @app.call(env) }
19
+ end
20
+ end
21
+
22
+ def log(env)
23
+ @requests += 1
24
+ @server = env['SERVER_SOFTWARE']
25
+ request_started_at = Time.now
26
+
27
+ response = yield
28
+
29
+ @requests_finished += 1
30
+ @last_request_path = env['PATH_INFO']
31
+ @last_request_time = Time.now - request_started_at
32
+
33
+ response
34
+ end
35
+
36
+ def serve(env)
37
+ body = '<html><body>'
38
+ body << '<h1>Server stats</h1>'
39
+ body << '<ul>'
40
+ body << "<li>#{@requests} requests</li>"
41
+ body << "<li>#{@requests_finished} requests finished</li>"
42
+ body << "<li>#{@requests - @requests_finished} errors</li>"
43
+ body << "<li>#{Time.now - @start_time} uptime</li>"
44
+ body << "<li>Running on #{@server}</li>"
45
+ body << '</ul>'
46
+ body << '<h2>Last request</h2>'
47
+ body << '<ul>'
48
+ body << "<li>#{@last_request_path}</li>"
49
+ body << "<li>Took #{@last_request_time} sec</li>"
50
+ body << '</ul>'
51
+ body << '</body></html>'
52
+
53
+ [
54
+ 200,
55
+ {
56
+ 'Content-Type' => 'text/html',
57
+ 'Content-Length' => body.size.to_s
58
+ },
59
+ body
60
+ ]
61
+ end
62
+ end
63
+ end
64
+ end
@@ -1,11 +1,11 @@
1
1
  module Thin
2
2
  module VERSION #:nodoc:
3
3
  MAJOR = 0
4
- MINOR = 5
5
- TINY = 4
6
-
4
+ MINOR = 6
5
+ TINY = 0
6
+
7
7
  STRING = [MAJOR, MINOR, TINY].join('.')
8
8
 
9
- CODENAME = 'Flying Mustard'
9
+ CODENAME = 'Big Pony'
10
10
  end
11
11
  end
Binary file
@@ -1,25 +1,24 @@
1
1
  require File.dirname(__FILE__) + '/spec_helper'
2
2
 
3
- describe Cluster do
3
+ describe Cluster, "with host and port" do
4
4
  before do
5
- @cluster = Thin::Cluster.new(:chdir => File.dirname(__FILE__) + '/rails_app',
6
- :address => '0.0.0.0',
7
- :port => 3000,
8
- :servers => 3,
9
- :timeout => 10,
10
- :log => 'thin.log',
11
- :pid => 'thin.pid'
12
- )
5
+ @cluster = Cluster.new(:chdir => File.dirname(__FILE__) + '/rails_app',
6
+ :address => '0.0.0.0',
7
+ :port => 3000,
8
+ :servers => 3,
9
+ :timeout => 10,
10
+ :log => 'thin.log',
11
+ :pid => 'thin.pid'
12
+ )
13
13
  @cluster.script = File.dirname(__FILE__) + '/../bin/thin'
14
14
  @cluster.silent = true
15
15
  end
16
16
 
17
17
  it 'should include port number in file names' do
18
- @cluster.send(:include_port_number, 'thin.log', 3000).should == 'thin.3000.log'
19
- @cluster.send(:include_port_number, 'thin.pid', 3000).should == 'thin.3000.pid'
20
- proc { @cluster.send(:include_port_number, 'thin', 3000) }.should raise_error(ArgumentError)
18
+ @cluster.send(:include_server_number, 'thin.log', 3000).should == 'thin.3000.log'
19
+ @cluster.send(:include_server_number, 'thin.pid', 3000).should == 'thin.3000.pid'
21
20
  end
22
-
21
+
23
22
  it 'should call each server' do
24
23
  calls = []
25
24
  @cluster.send(:with_each_server) do |port|
@@ -41,18 +40,69 @@ describe Cluster do
41
40
  it 'should start on specified port' do
42
41
  @cluster.should_receive(:`) do |with|
43
42
  with.should include('thin start', '--daemonize', 'thin.3001.log', 'thin.3001.pid', '--port=3001')
43
+ with.should_not include('--socket')
44
44
  ''
45
45
  end
46
46
 
47
- @cluster.start_on_port 3001
47
+ @cluster.start_server 3001
48
48
  end
49
49
 
50
50
  it 'should stop on specified port' do
51
51
  @cluster.should_receive(:`) do |with|
52
52
  with.should include('thin stop', '--daemonize', 'thin.3001.log', 'thin.3001.pid', '--port=3001')
53
+ with.should_not include('--socket')
54
+ ''
55
+ end
56
+
57
+ @cluster.stop_server 3001
58
+ end
59
+ end
60
+
61
+ describe Cluster, "with UNIX socket" do
62
+ before do
63
+ @cluster = Cluster.new(:chdir => File.dirname(__FILE__) + '/rails_app',
64
+ :socket => '/tmp/thin.sock',
65
+ :address => '0.0.0.0',
66
+ :port => 3000,
67
+ :servers => 3,
68
+ :timeout => 10,
69
+ :log => 'thin.log',
70
+ :pid => 'thin.pid'
71
+ )
72
+ @cluster.script = File.dirname(__FILE__) + '/../bin/thin'
73
+ @cluster.silent = true
74
+ end
75
+
76
+ it 'should include socket number in file names' do
77
+ @cluster.send(:include_server_number, 'thin.sock', 0).should == 'thin.0.sock'
78
+ @cluster.send(:include_server_number, 'thin', 0).should == 'thin.0'
79
+ end
80
+
81
+ it 'should call each server' do
82
+ calls = []
83
+ @cluster.send(:with_each_server) do |n|
84
+ calls << n
85
+ end
86
+ calls.should == [0, 1, 2]
87
+ end
88
+
89
+ it 'should start specified server' do
90
+ @cluster.should_receive(:`) do |with|
91
+ with.should include('thin start', '--daemonize', 'thin.1.log', 'thin.1.pid', '--socket="/tmp/thin.1.sock"')
92
+ with.should_not include('--port', '--address')
93
+ ''
94
+ end
95
+
96
+ @cluster.start_server 1
97
+ end
98
+
99
+ it 'should stop specified server' do
100
+ @cluster.should_receive(:`) do |with|
101
+ with.should include('thin stop', '--daemonize', 'thin.1.log', 'thin.1.pid', '--socket="/tmp/thin.1.sock"')
102
+ with.should_not include('--port', '--address')
53
103
  ''
54
104
  end
55
105
 
56
- @cluster.stop_on_port 3001
106
+ @cluster.stop_server 1
57
107
  end
58
108
  end
@@ -23,7 +23,7 @@ describe 'Daemonizing' do
23
23
  File.exist?(@server.pid_file).should be_true
24
24
  @pid = @server.pid
25
25
 
26
- proc { sleep 0.1 while File.exist?(@server.pid_file) }.should take_less_then(2)
26
+ proc { sleep 0.1 while File.exist?(@server.pid_file) }.should take_less_then(5)
27
27
  end
28
28
 
29
29
  it 'should redirect stdio to a log file' do
@@ -89,5 +89,6 @@ describe 'Daemonizing' do
89
89
 
90
90
  after do
91
91
  Process.kill(9, @pid.to_i) if @pid && Process.running?(@pid.to_i)
92
+ Process.kill(9, @server.pid) if @server.pid && Process.running?(@server.pid)
92
93
  end
93
94
  end
@@ -9,20 +9,14 @@ describe Headers do
9
9
  @headers['Set-Cookie'] = 'twice'
10
10
  @headers['Set-Cookie'] = 'is cooler the once'
11
11
 
12
- @headers.size.should == 2
12
+ @headers.to_s.should == "Set-Cookie: twice\r\nSet-Cookie: is cooler the once\r\n"
13
13
  end
14
14
 
15
15
  it 'should overwrite value on non duplicate fields' do
16
16
  @headers['Host'] = 'this is unique'
17
17
  @headers['Host'] = 'so is this'
18
- @headers['Host'] = 'so this will overwrite ^'
19
18
 
20
- @headers['Host'].should == 'so this will overwrite ^'
21
- end
22
-
23
- it 'should return first header value' do
24
- @headers['Set-Cookie'] = 'hi'
25
- @headers['Set-Cookie'].should == 'hi'
19
+ @headers.to_s.should == "Host: this is unique\r\n"
26
20
  end
27
21
 
28
22
  it 'should output to string' do
@@ -119,6 +119,7 @@ EOS
119
119
 
120
120
  request.body.rewind
121
121
  request.body.read.should == 'name=marc&email=macournoyer@gmail.com'
122
+ request.body.class.should == StringIO
122
123
 
123
124
  request.should validate_with_lint
124
125
  end
@@ -188,8 +189,28 @@ EOS
188
189
  request.should validate_with_lint
189
190
  end
190
191
 
191
- it "should be faster then #{max_parsing_time = 0.2} ms" do
192
- body = <<-EOS.chomp
192
+ it "should move body to tempfile when too big" do
193
+ body = 'X' * (Request::MAX_BODY + 1)
194
+
195
+ request = R(<<-EOS.chomp, true)
196
+ POST /postit HTTP/1.1
197
+ Host: localhost:3000
198
+ Content-Type: text/html
199
+ Content-Length: #{body.size}
200
+
201
+ #{body}
202
+ EOS
203
+
204
+ request.body.class.should == Tempfile
205
+ end
206
+
207
+ it "should raise error when header is too big" do
208
+ big_headers = "X-Test: X\r\n" * (1024 * (80 + 32))
209
+ proc { R("GET / HTTP/1.1\r\n#{big_headers}\r\n") }.should raise_error(InvalidRequest)
210
+ end
211
+
212
+ it "should be faster then #{max_parsing_time = 0.0002} RubySeconds" do
213
+ body = <<-EOS.chomp.gsub("\n", "\r\n")
193
214
  POST /postit HTTP/1.1
194
215
  Host: localhost:3000
195
216
  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
@@ -205,7 +226,7 @@ Content-Length: 37
205
226
  hi=there&name=marc&email=macournoyer@gmail.com
206
227
  EOS
207
228
 
208
- proc { R(body, true) }.should be_faster_then(max_parsing_time)
229
+ proc { R(body) }.should be_faster_then(max_parsing_time)
209
230
  end
210
231
 
211
232
  it 'should be comparable to Mongrel parser' do
@@ -52,7 +52,7 @@ describe Response do
52
52
  out.should include("\r\n\r\n<html></html>")
53
53
  end
54
54
 
55
- it "should be faster then #{max_parsing_time = 0.07} ms" do
55
+ it "should be faster then #{max_parsing_time = 0.00011} RubySecond" do
56
56
  @response.body << <<-EOS
57
57
  <html><head><title>Dir listing</title></head>
58
58
  <body><h1>Listing stuff</h1><ul>
@@ -6,30 +6,27 @@ describe Server do
6
6
  before do
7
7
  app = proc do |env|
8
8
  body = ''
9
- body << env['QUERY_STRING'].to_s
10
- body << env['rack.input'].read.to_s
9
+ body << env.inspect
10
+ body << env['rack.input'].read
11
11
  [200, { 'Content-Type' => 'text/html', 'Content-Length' => body.size.to_s }, body]
12
12
  end
13
13
  server = Thin::Server.new('0.0.0.0', 3333, app)
14
14
  server.timeout = 3
15
15
  server.silent = true
16
16
 
17
- server.start
18
- @thread = Thread.new do
19
- server.listen!
20
- end
17
+ @thread = Thread.new { server.start }
21
18
  sleep 0.1 until @thread.status == 'sleep'
22
19
  end
23
20
 
24
21
  it 'should GET from Net::HTTP' do
25
- get('/?cthis').should == 'cthis'
22
+ get('/?cthis').should include('cthis')
26
23
  end
27
24
 
28
25
  it 'should GET from TCPSocket' do
29
26
  raw('0.0.0.0', 3333, "GET /?this HTTP/1.1\r\n\r\n").
30
27
  should include("HTTP/1.1 200 OK",
31
- "Content-Type: text/html", "Content-Length: 4",
32
- "Connection: close", "\r\n\r\nthis")
28
+ "Content-Type: text/html", "Content-Length: ",
29
+ "Connection: close", "this")
33
30
  end
34
31
 
35
32
  it 'should return empty string on incomplete headers' do
@@ -41,20 +38,24 @@ describe Server do
41
38
  end
42
39
 
43
40
  it 'should POST from Net::HTTP' do
44
- post('/', :arg => 'pirate').should == 'arg=pirate'
41
+ post('/', :arg => 'pirate').should include('arg=pirate')
45
42
  end
46
43
 
47
44
  it 'should handle big POST' do
48
45
  big = 'X' * (20 * 1024)
49
- post('/', :big => big).size.should == big.size + 4
46
+ post('/', :big => big).should include(big)
50
47
  end
51
48
 
52
- it "should handle GET in less then #{get_request_time = 5} ms" do
49
+ it "should handle GET in less then #{get_request_time = 0.004} RubySecond" do
53
50
  proc { get('/') }.should be_faster_then(get_request_time)
54
51
  end
55
52
 
56
- it "should handle POST in less then #{post_request_time = 6} ms" do
57
- proc { post('/', :file => 'X' * 1000) }.should be_faster_then(get_request_time)
53
+ it "should handle POST in less then #{post_request_time = 0.007} RubySecond" do
54
+ proc { post('/', :file => 'X' * 1000) }.should be_faster_then(post_request_time)
55
+ end
56
+
57
+ it "should retreive remote address" do
58
+ get('/').should include('"REMOTE_ADDR"=>"127.0.0.1"')
58
59
  end
59
60
 
60
61
  after do
@@ -77,4 +78,84 @@ describe Server do
77
78
  def post(url, params={})
78
79
  Net::HTTP.post_form(URI.parse('http://0.0.0.0:3333' + url), params).body
79
80
  end
81
+ end
82
+
83
+ describe Server, 'app configuration' do
84
+ it "should build app from constructor" do
85
+ server = Server.new('0.0.0.0', 3000, :works)
86
+
87
+ server.app.should == :works
88
+ end
89
+
90
+ it "should build app from builder block" do
91
+ server = Server.new '0.0.0.0', 3000 do
92
+ run(proc { |env| :works })
93
+ end
94
+
95
+ server.app.call({}).should == :works
96
+ end
97
+
98
+ it "should use middlewares in builder block" do
99
+ server = Server.new '0.0.0.0', 3000 do
100
+ use Rack::ShowExceptions
101
+ run(proc { |env| :works })
102
+ end
103
+
104
+ server.app.class.should == Rack::ShowExceptions
105
+ server.app.call({}).should == :works
106
+ end
107
+
108
+ it "should work with Rack url mapper" do
109
+ server = Server.new '0.0.0.0', 3000 do
110
+ map '/test' do
111
+ run(proc { |env| :works })
112
+ end
113
+ end
114
+
115
+ server.app.call({})[0].should == 404
116
+ server.app.call({'PATH_INFO' => '/test'}).should == :works
117
+ end
118
+ end
119
+
120
+ describe Server, "on UNIX domain socket" do
121
+ before do
122
+ app = proc do |env|
123
+ [200, { 'Content-Type' => 'text/html' }, [env.inspect]]
124
+ end
125
+ server = Thin::Server.new('/tmp/thin_test.sock', nil, app)
126
+ server.timeout = 3
127
+ server.silent = true
128
+
129
+ @thread = Thread.new { server.start }
130
+ sleep 0.1 until @thread.status == 'sleep'
131
+ end
132
+
133
+ it "should accept GET request" do
134
+ get("/?this").should include('this')
135
+ end
136
+
137
+ it "should retreive remote address" do
138
+ get('/').should include('"REMOTE_ADDR"=>""') # Is that right?
139
+ end
140
+
141
+ it "should handle GET in less then #{get_request_time = 0.002} RubySecond" do
142
+ proc { get('/') }.should be_faster_then(get_request_time)
143
+ end
144
+
145
+ after do
146
+ @thread.kill
147
+ end
148
+
149
+ private
150
+ def get(url)
151
+ send_data("GET #{url} HTTP/1.1\r\n\r\n")
152
+ end
153
+
154
+ def send_data(data)
155
+ socket = UNIXSocket.new('/tmp/thin_test.sock')
156
+ socket.write data
157
+ out = socket.read
158
+ socket.close
159
+ out
160
+ end
80
161
  end
@@ -4,6 +4,7 @@ require 'spec'
4
4
  require 'benchmark'
5
5
  require 'timeout'
6
6
  require 'fileutils'
7
+ require 'benchmark_unit'
7
8
 
8
9
  include Thin
9
10
 
@@ -31,14 +32,33 @@ module Matchers
31
32
  @max_time = max_time
32
33
  end
33
34
 
34
- def matches?(target)
35
- @target = target
36
- @time = Benchmark.measure { @target.call }.real * 1000
37
- @time <= @max_time
35
+ # Base on benchmark_unit/assertions#compare_benchmarks
36
+ def matches?(proc)
37
+ @time, multiplier = 0, 1
38
+
39
+ while (@time < 0.01) do
40
+ @time = Benchmark::Unit.measure do
41
+ multiplier.times &proc
42
+ end
43
+ multiplier *= 10
44
+ end
45
+
46
+ multiplier /= 10
47
+
48
+ iterations = (Benchmark::Unit::CLOCK_TARGET / @time).to_i * multiplier
49
+ iterations = 1 if iterations < 1
50
+
51
+ total = Benchmark::Unit.measure do
52
+ iterations.times &proc
53
+ end
54
+
55
+ @time = total / iterations
56
+
57
+ @time < @max_time
38
58
  end
39
59
 
40
60
  def failure_message(less_more=:less)
41
- "took #{@time} ms, should take #{less_more} then #{@max_time} ms"
61
+ "took <#{@time.inspect} RubySeconds>, should take #{less_more} than #{@max_time} RubySeconds."
42
62
  end
43
63
 
44
64
  def negative_failure_message