thin 0.7.1 → 0.8.0
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 +15 -0
- data/README +10 -4
- data/benchmark/abc +0 -0
- data/benchmark/runner +0 -0
- data/bin/thin +0 -0
- data/example/thin_solaris_smf.erb +36 -0
- data/example/thin_solaris_smf.readme.txt +150 -0
- data/ext/thin_parser/common.rl +3 -2
- data/ext/thin_parser/parser.c +15 -38
- data/lib/rack/adapter/loader.rb +69 -0
- data/lib/rack/adapter/rails.rb +17 -8
- data/lib/thin.rb +1 -0
- data/lib/thin/backends/base.rb +17 -0
- data/lib/thin/backends/swiftiply_client.rb +3 -2
- data/lib/thin/backends/tcp_server.rb +1 -1
- data/lib/thin/connection.rb +38 -6
- data/lib/thin/controllers/controller.rb +43 -17
- data/lib/thin/logging.rb +1 -1
- data/lib/thin/request.rb +8 -1
- data/lib/thin/runner.rb +29 -7
- data/lib/thin/server.rb +58 -28
- data/lib/thin/version.rb +3 -3
- data/lib/thin_backend.bundle +0 -0
- data/lib/thin_parser.bundle +0 -0
- data/spec/connection_spec.rb +7 -0
- data/spec/controllers/controller_spec.rb +9 -1
- data/spec/rack/loader_spec.rb +29 -0
- data/spec/{rack_rails_spec.rb → rack/rails_adapter_spec.rb} +15 -3
- data/spec/rails_app/public/dispatch.cgi +0 -0
- data/spec/rails_app/public/dispatch.fcgi +0 -0
- data/spec/rails_app/public/dispatch.rb +0 -0
- data/spec/rails_app/script/about +0 -0
- data/spec/rails_app/script/console +0 -0
- data/spec/rails_app/script/destroy +0 -0
- data/spec/rails_app/script/generate +0 -0
- data/spec/rails_app/script/performance/benchmarker +0 -0
- data/spec/rails_app/script/performance/profiler +0 -0
- data/spec/rails_app/script/performance/request +0 -0
- data/spec/rails_app/script/plugin +0 -0
- data/spec/rails_app/script/process/inspector +0 -0
- data/spec/rails_app/script/process/reaper +0 -0
- data/spec/rails_app/script/process/spawner +0 -0
- data/spec/rails_app/script/runner +0 -0
- data/spec/rails_app/script/server +0 -0
- data/spec/request/parser_spec.rb +22 -0
- data/spec/request/processing_spec.rb +9 -10
- data/spec/runner_spec.rb +14 -1
- data/spec/server/builder_spec.rb +3 -2
- data/spec/server/robustness_spec.rb +34 -0
- data/spec/server/swiftiply_spec.rb +1 -1
- data/spec/server/threaded_spec.rb +27 -0
- data/spec/server_spec.rb +69 -0
- data/spec/spec_helper.rb +10 -5
- data/tasks/gem.rake +2 -2
- data/tasks/spec.rake +24 -16
- metadata +13 -5
- data/doc/benchmarks.txt +0 -86
data/lib/thin/server.rb
CHANGED
@@ -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 backend
|
3
|
+
# It listen for incoming request through a given +backend+
|
4
4
|
# and forward all request to +app+.
|
5
5
|
#
|
6
6
|
# == TCP server
|
@@ -14,14 +14,13 @@ module Thin
|
|
14
14
|
# as the first argument. Eg.: /tmp/thin.sock. If the first argument contains a <tt>/</tt>
|
15
15
|
# it will be assumed to be a UNIX socket.
|
16
16
|
#
|
17
|
-
# Thin::Server.start('/tmp/thin.sock',
|
17
|
+
# Thin::Server.start('/tmp/thin.sock', app)
|
18
18
|
#
|
19
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 Backend class and pass it as the
|
21
|
+
# own Backend class and pass it as the :backend option.
|
22
22
|
#
|
23
|
-
#
|
24
|
-
# Thin::Server.start(backend, nil, app)
|
23
|
+
# Thin::Server.start('galaxy://faraway', 1345, app, :backend => Thin::Backends::MyFancyBackend)
|
25
24
|
#
|
26
25
|
# == Rack application (+app+)
|
27
26
|
# All requests will be processed through +app+ that must be a valid Rack adapter.
|
@@ -41,6 +40,11 @@ module Thin
|
|
41
40
|
# end
|
42
41
|
# end
|
43
42
|
#
|
43
|
+
# == Controlling with signals
|
44
|
+
# * QUIT: Gracefull shutdown (see Server#stop)
|
45
|
+
# * INT and TERM: Force shutdown (see Server#stop!)
|
46
|
+
# Disable signals by passing <tt>:signals => false</tt>
|
47
|
+
#
|
44
48
|
class Server
|
45
49
|
include Logging
|
46
50
|
include Daemonizable
|
@@ -48,6 +52,7 @@ module Thin
|
|
48
52
|
|
49
53
|
# Default values
|
50
54
|
DEFAULT_TIMEOUT = 30 #sec
|
55
|
+
DEFAULT_HOST = '0.0.0.0'
|
51
56
|
DEFAULT_PORT = 3000
|
52
57
|
DEFAULT_MAXIMUM_CONNECTIONS = 1024
|
53
58
|
DEFAULT_MAXIMUM_PERSISTENT_CONNECTIONS = 512
|
@@ -60,37 +65,44 @@ module Thin
|
|
60
65
|
|
61
66
|
# Maximum number of seconds for incoming data to arrive before the connection
|
62
67
|
# is dropped.
|
63
|
-
def_delegators
|
68
|
+
def_delegators :backend, :timeout, :timeout=
|
64
69
|
|
65
70
|
# Maximum number of file or socket descriptors that the server may open.
|
66
|
-
def_delegators
|
71
|
+
def_delegators :backend, :maximum_connections, :maximum_connections=
|
67
72
|
|
68
73
|
# Maximum number of connection that can be persistent at the same time.
|
69
74
|
# Most browser never close the connection so most of the time they are closed
|
70
75
|
# when the timeout occur. If we don't control the number of persistent connection,
|
71
76
|
# if would be very easy to overflow the server for a DoS attack.
|
72
|
-
def_delegators
|
77
|
+
def_delegators :backend, :maximum_persistent_connections, :maximum_persistent_connections=
|
78
|
+
|
79
|
+
# Allow using threads in the backend.
|
80
|
+
def_delegators :backend, :threaded?, :threaded=
|
73
81
|
|
74
82
|
# Address and port on which the server is listening for connections.
|
75
|
-
def_delegators
|
83
|
+
def_delegators :backend, :host, :port
|
76
84
|
|
77
85
|
# UNIX domain socket on which the server is listening for connections.
|
78
|
-
def_delegator
|
86
|
+
def_delegator :backend, :socket
|
79
87
|
|
80
|
-
def initialize(
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
88
|
+
def initialize(*args, &block)
|
89
|
+
host, port, options = DEFAULT_HOST, DEFAULT_PORT, {}
|
90
|
+
|
91
|
+
args.each do |arg|
|
92
|
+
case arg
|
93
|
+
when String then host = arg
|
94
|
+
when Fixnum then port = arg
|
95
|
+
when Hash then options = arg
|
96
|
+
else
|
97
|
+
@app = arg if arg.respond_to?(:call)
|
98
|
+
end
|
89
99
|
end
|
90
100
|
|
101
|
+
# Try to intelligently select which backend to use.
|
102
|
+
@backend = select_backend(host, port, options)
|
103
|
+
|
91
104
|
load_cgi_multipart_eof_fix
|
92
105
|
|
93
|
-
@app = app
|
94
106
|
@backend.server = self
|
95
107
|
|
96
108
|
# Set defaults
|
@@ -103,6 +115,8 @@ module Thin
|
|
103
115
|
|
104
116
|
# If in debug mode, wrap in logger adapter
|
105
117
|
@app = Rack::CommonLogger.new(@app) if Logging.debug?
|
118
|
+
|
119
|
+
setup_signals unless options[:signals].class == FalseClass
|
106
120
|
end
|
107
121
|
|
108
122
|
# Lil' shortcut to turn this:
|
@@ -118,18 +132,14 @@ module Thin
|
|
118
132
|
end
|
119
133
|
|
120
134
|
# Start the server and listen for connections.
|
121
|
-
# Also register signals:
|
122
|
-
# * INT calls +stop+ to shutdown gracefully.
|
123
|
-
# * TERM calls <tt>stop!</tt> to force shutdown.
|
124
135
|
def start
|
125
136
|
raise ArgumentError, 'app required' unless @app
|
126
137
|
|
127
|
-
setup_signals
|
128
|
-
|
129
138
|
log ">> Thin web server (v#{VERSION::STRING} codename #{VERSION::CODENAME})"
|
130
139
|
debug ">> Debugging ON"
|
131
140
|
trace ">> Tracing ON"
|
132
141
|
|
142
|
+
log ">> Threaded mode #{@backend.threaded? ? 'ON' : 'OFF'}"
|
133
143
|
log ">> Maximum connections set to #{@backend.maximum_connections}"
|
134
144
|
log ">> Listening on #{@backend}, CTRL+C to stop"
|
135
145
|
|
@@ -165,12 +175,14 @@ module Thin
|
|
165
175
|
end
|
166
176
|
|
167
177
|
# == Configure the server
|
168
|
-
# The process might need to have superuser privilege to
|
178
|
+
# The process might need to have superuser privilege to configure
|
169
179
|
# server with optimal options.
|
170
180
|
def config
|
171
181
|
@backend.config
|
172
182
|
end
|
173
|
-
|
183
|
+
|
184
|
+
# Name of the server and type of backend used.
|
185
|
+
# This is also the name of the process in which Thin is running as a daemon.
|
174
186
|
def name
|
175
187
|
"thin server (#{@backend})"
|
176
188
|
end
|
@@ -183,14 +195,32 @@ module Thin
|
|
183
195
|
@backend.running?
|
184
196
|
end
|
185
197
|
|
186
|
-
protected
|
198
|
+
protected
|
199
|
+
# Register signals:
|
200
|
+
# * INT calls +stop+ to shutdown gracefully.
|
201
|
+
# * TERM calls <tt>stop!</tt> to force shutdown.
|
187
202
|
def setup_signals
|
188
203
|
trap('QUIT') { stop } unless Thin.win?
|
189
204
|
trap('INT') { stop! }
|
190
205
|
trap('TERM') { stop! }
|
191
206
|
end
|
192
207
|
|
208
|
+
def select_backend(host, port, options)
|
209
|
+
case
|
210
|
+
when options.has_key?(:backend)
|
211
|
+
raise ArgumentError, ":backend must be a class" unless options[:backend].is_a?(Class)
|
212
|
+
options[:backend].new(host, port, options)
|
213
|
+
when options.has_key?(:swiftiply)
|
214
|
+
Backends::SwiftiplyClient.new(host, port, options)
|
215
|
+
when host.include?('/')
|
216
|
+
Backends::UnixServer.new(host)
|
217
|
+
else
|
218
|
+
Backends::TcpServer.new(host, port)
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
193
222
|
# Taken from Mongrel cgi_multipart_eof_fix
|
223
|
+
# Ruby 1.8.5 has a security bug in cgi.rb, we need to patch it.
|
194
224
|
def load_cgi_multipart_eof_fix
|
195
225
|
version = RUBY_VERSION.split('.').map { |i| i.to_i }
|
196
226
|
|
data/lib/thin/version.rb
CHANGED
@@ -5,12 +5,12 @@ module Thin
|
|
5
5
|
|
6
6
|
module VERSION #:nodoc:
|
7
7
|
MAJOR = 0
|
8
|
-
MINOR =
|
9
|
-
TINY =
|
8
|
+
MINOR = 8
|
9
|
+
TINY = 0
|
10
10
|
|
11
11
|
STRING = [MAJOR, MINOR, TINY].join('.')
|
12
12
|
|
13
|
-
CODENAME = '
|
13
|
+
CODENAME = 'Dodgy Dentist'
|
14
14
|
|
15
15
|
RACK = [0, 3].freeze # Latest Rack version that was tested
|
16
16
|
end
|
Binary file
|
Binary file
|
data/spec/connection_spec.rb
CHANGED
@@ -69,4 +69,11 @@ describe Connection do
|
|
69
69
|
@connection.response.persistent!
|
70
70
|
@connection.should_not be_persistent
|
71
71
|
end
|
72
|
+
|
73
|
+
it "should set request env as rack.multithread" do
|
74
|
+
@connection.threaded = true
|
75
|
+
@connection.post_init
|
76
|
+
|
77
|
+
@connection.request.env["rack.multithread"].should == true
|
78
|
+
end
|
72
79
|
end
|
@@ -10,7 +10,8 @@ describe Controller, 'start' do
|
|
10
10
|
:log => 'thin.log',
|
11
11
|
:timeout => 60,
|
12
12
|
:max_conns => 2000,
|
13
|
-
:max_persistent_conns => 1000
|
13
|
+
:max_persistent_conns => 1000,
|
14
|
+
:adapter => 'rails')
|
14
15
|
|
15
16
|
@server = OpenStruct.new
|
16
17
|
@adapter = OpenStruct.new
|
@@ -67,6 +68,13 @@ describe Controller, 'start' do
|
|
67
68
|
|
68
69
|
@server.app.class.should == Proc
|
69
70
|
end
|
71
|
+
|
72
|
+
it "should set server as threaded" do
|
73
|
+
@controller.options[:threaded] = true
|
74
|
+
@controller.start
|
75
|
+
|
76
|
+
@server.threaded.should be_true
|
77
|
+
end
|
70
78
|
end
|
71
79
|
|
72
80
|
describe Controller do
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper'
|
2
|
+
|
3
|
+
describe Rack::Adapter do
|
4
|
+
before do
|
5
|
+
@rails_path = File.dirname(__FILE__) + '/../rails_app'
|
6
|
+
end
|
7
|
+
|
8
|
+
it "should guess rails app from dir" do
|
9
|
+
Rack::Adapter.guess(@rails_path).should == :rails
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should return nil when can't guess from dir" do
|
13
|
+
proc { Rack::Adapter.guess('.') }.should raise_error(Rack::AdapterNotFound)
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should load Rails adapter" do
|
17
|
+
Rack::Adapter::Rails.should_receive(:new)
|
18
|
+
Rack::Adapter.for(:rails, :chdir => @rails_path)
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should load File adapter" do
|
22
|
+
Rack::File.should_receive(:new)
|
23
|
+
Rack::Adapter.for(:file)
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should raise error when adapter can't be found" do
|
27
|
+
proc { Rack::Adapter.for(:fart, {}) }.should raise_error(Rack::AdapterNotFound)
|
28
|
+
end
|
29
|
+
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require File.dirname(__FILE__) + '
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper'
|
2
2
|
require 'rack/mock'
|
3
3
|
|
4
4
|
begin
|
@@ -6,7 +6,7 @@ begin
|
|
6
6
|
|
7
7
|
describe Rack::Adapter::Rails do
|
8
8
|
before do
|
9
|
-
@rails_app_path = File.dirname(__FILE__) + '
|
9
|
+
@rails_app_path = File.dirname(__FILE__) + '/../rails_app'
|
10
10
|
@request = Rack::MockRequest.new(Rack::Adapter::Rails.new(:root => @rails_app_path))
|
11
11
|
end
|
12
12
|
|
@@ -56,6 +56,18 @@ begin
|
|
56
56
|
res.body.should == 'cached'
|
57
57
|
end
|
58
58
|
|
59
|
+
it "should not serve page cache on POST request" do
|
60
|
+
res = @request.get("/simple/cached?value=cached")
|
61
|
+
|
62
|
+
res.should be_ok
|
63
|
+
res.body.should == 'cached'
|
64
|
+
|
65
|
+
res = @request.post("/simple/cached?value=notcached")
|
66
|
+
|
67
|
+
res.should be_ok
|
68
|
+
res.body.should == 'notcached'
|
69
|
+
end
|
70
|
+
|
59
71
|
it "handles multiple cookies" do
|
60
72
|
res = @request.get('/simple/set_cookie?name=a&value=1')
|
61
73
|
|
@@ -70,7 +82,7 @@ begin
|
|
70
82
|
|
71
83
|
describe Rack::Adapter::Rails, 'with prefix' do
|
72
84
|
before do
|
73
|
-
@rails_app_path = File.dirname(__FILE__) + '
|
85
|
+
@rails_app_path = File.dirname(__FILE__) + '/../rails_app'
|
74
86
|
@prefix = '/nowhere'
|
75
87
|
@request = Rack::MockRequest.new(
|
76
88
|
Rack::URLMap.new(
|
File without changes
|
File without changes
|
File without changes
|
data/spec/rails_app/script/about
CHANGED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
data/spec/request/parser_spec.rb
CHANGED
@@ -154,4 +154,26 @@ EOS
|
|
154
154
|
request.body.read.should == 'aye'
|
155
155
|
end
|
156
156
|
|
157
|
+
it "should parse ie6 urls" do
|
158
|
+
%w(/some/random/path"
|
159
|
+
/some/random/path>
|
160
|
+
/some/random/path<
|
161
|
+
/we/love/you/ie6?q=<"">
|
162
|
+
/url?<="&>="
|
163
|
+
/mal"formed"?
|
164
|
+
).each do |path|
|
165
|
+
parser = HttpParser.new
|
166
|
+
req = {}
|
167
|
+
sorta_safe = %(GET #{path} HTTP/1.1\r\n\r\n)
|
168
|
+
nread = parser.execute(req, sorta_safe, 0)
|
169
|
+
|
170
|
+
sorta_safe.size.should == nread
|
171
|
+
parser.should be_finished
|
172
|
+
parser.should_not have_error
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
it "should fails on heders larger then MAX_HEADER" do
|
177
|
+
proc { R("GET / HTTP/1.1\r\nFoo: #{'X' * Request::MAX_HEADER}\r\n\r\n") }.should raise_error(InvalidRequest)
|
178
|
+
end
|
157
179
|
end
|
@@ -15,24 +15,23 @@ describe Request, 'processing' do
|
|
15
15
|
end
|
16
16
|
|
17
17
|
it "should move body to tempfile when too big" do
|
18
|
+
len = Request::MAX_BODY + 2
|
18
19
|
request = Request.new
|
19
|
-
request.parse("POST /postit HTTP/1.1\r\nContent-Length: #{
|
20
|
-
request.parse('X' *
|
20
|
+
request.parse("POST /postit HTTP/1.1\r\nContent-Length: #{len}\r\n\r\n#{'X' * (len/2)}")
|
21
|
+
request.parse('X' * (len/2))
|
21
22
|
|
22
|
-
request.body.size.should ==
|
23
|
+
request.body.size.should == len
|
23
24
|
request.should be_finished
|
24
25
|
request.body.class.should == Tempfile
|
25
26
|
end
|
26
27
|
|
27
28
|
it "should delete body tempfile when closing" do
|
28
29
|
body = 'X' * (Request::MAX_BODY + 1)
|
29
|
-
|
30
|
-
request =
|
31
|
-
POST /postit HTTP/1.1
|
32
|
-
Content-Length: #{body.size}
|
33
|
-
|
34
|
-
#{body}
|
35
|
-
EOS
|
30
|
+
|
31
|
+
request = Request.new
|
32
|
+
request.parse("POST /postit HTTP/1.1\r\n")
|
33
|
+
request.parse("Content-Length: #{body.size}\r\n\r\n")
|
34
|
+
request.parse(body)
|
36
35
|
|
37
36
|
request.body.path.should_not be_nil
|
38
37
|
request.close
|
data/spec/runner_spec.rb
CHANGED
@@ -61,7 +61,20 @@ describe Runner do
|
|
61
61
|
|
62
62
|
it "should consider as a cluster with :only option" do
|
63
63
|
Runner.new(%w(start --only 3000)).should be_a_cluster
|
64
|
-
end
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should warn when require a rack config file" do
|
67
|
+
STDERR.stub!(:write)
|
68
|
+
STDERR.should_receive(:write).with(/WARNING:/)
|
69
|
+
|
70
|
+
runner = Runner.new(%w(start -r config.ru))
|
71
|
+
|
72
|
+
runner.options[:rackup].should == 'config.ru'
|
73
|
+
end
|
74
|
+
|
75
|
+
it "should require file" do
|
76
|
+
proc { Runner.new(%w(start -r unexisting)) }.should raise_error(LoadError)
|
77
|
+
end
|
65
78
|
end
|
66
79
|
|
67
80
|
describe Runner, 'with config file' do
|
data/spec/server/builder_spec.rb
CHANGED
@@ -2,9 +2,10 @@ require File.dirname(__FILE__) + '/../spec_helper'
|
|
2
2
|
|
3
3
|
describe Server, 'app builder' do
|
4
4
|
it "should build app from constructor" do
|
5
|
-
|
5
|
+
app = proc {}
|
6
|
+
server = Server.new('0.0.0.0', 3000, app)
|
6
7
|
|
7
|
-
server.app.should ==
|
8
|
+
server.app.should == app
|
8
9
|
end
|
9
10
|
|
10
11
|
it "should build app from builder block" do
|