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.
- data/CHANGELOG +40 -3
- data/README +13 -5
- data/Rakefile +2 -2
- data/bin/thin +46 -9
- data/example/adapter.rb +17 -0
- data/example/config.ru +10 -3
- data/ext/thin_parser/parser.c +122 -80
- data/ext/thin_parser/parser.rl +25 -9
- data/lib/thin.rb +1 -0
- data/lib/thin/cluster.rb +53 -26
- data/lib/thin/connection.rb +20 -3
- data/lib/thin/daemonizing.rb +1 -1
- data/lib/thin/headers.rb +7 -20
- data/lib/thin/logging.rb +1 -1
- data/lib/thin/request.rb +50 -21
- data/lib/thin/response.rb +4 -2
- data/lib/thin/server.rb +77 -36
- data/lib/thin/stats.rb +64 -0
- data/lib/thin/version.rb +4 -4
- data/lib/thin_parser.so +0 -0
- data/spec/cluster_spec.rb +65 -15
- data/spec/daemonizing_spec.rb +2 -1
- data/spec/headers_spec.rb +2 -8
- data/spec/request_spec.rb +24 -3
- data/spec/response_spec.rb +1 -1
- data/spec/server_spec.rb +95 -14
- data/spec/spec_helper.rb +25 -5
- data/tasks/announce.rake +18 -0
- data/tasks/deploy.rake +16 -0
- data/tasks/email.erb +46 -0
- data/tasks/ext.rake +38 -0
- data/tasks/gem.rake +76 -0
- data/tasks/rdoc.rake +25 -0
- data/tasks/site.rake +15 -0
- data/tasks/spec.rake +26 -0
- data/tasks/stats.rake +15 -0
- metadata +14 -3
data/lib/thin/stats.rb
ADDED
@@ -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
|
data/lib/thin/version.rb
CHANGED
data/lib/thin_parser.so
CHANGED
Binary file
|
data/spec/cluster_spec.rb
CHANGED
@@ -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 =
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
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(:
|
19
|
-
@cluster.send(:
|
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.
|
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.
|
106
|
+
@cluster.stop_server 1
|
57
107
|
end
|
58
108
|
end
|
data/spec/daemonizing_spec.rb
CHANGED
@@ -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(
|
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
|
data/spec/headers_spec.rb
CHANGED
@@ -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.
|
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
|
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
|
data/spec/request_spec.rb
CHANGED
@@ -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
|
192
|
-
body =
|
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
|
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
|
data/spec/response_spec.rb
CHANGED
@@ -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.
|
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>
|
data/spec/server_spec.rb
CHANGED
@@ -6,30 +6,27 @@ describe Server do
|
|
6
6
|
before do
|
7
7
|
app = proc do |env|
|
8
8
|
body = ''
|
9
|
-
body << env
|
10
|
-
body << env['rack.input'].read
|
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
|
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:
|
32
|
-
"Connection: close", "
|
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
|
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).
|
46
|
+
post('/', :big => big).should include(big)
|
50
47
|
end
|
51
48
|
|
52
|
-
it "should handle GET in less then #{get_request_time =
|
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 =
|
57
|
-
proc { post('/', :file => 'X' * 1000) }.should be_faster_then(
|
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
|
data/spec/spec_helper.rb
CHANGED
@@ -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
|
-
|
35
|
-
|
36
|
-
@time =
|
37
|
-
|
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
|
61
|
+
"took <#{@time.inspect} RubySeconds>, should take #{less_more} than #{@max_time} RubySeconds."
|
42
62
|
end
|
43
63
|
|
44
64
|
def negative_failure_message
|