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.

Files changed (57) hide show
  1. data/CHANGELOG +15 -0
  2. data/README +10 -4
  3. data/benchmark/abc +0 -0
  4. data/benchmark/runner +0 -0
  5. data/bin/thin +0 -0
  6. data/example/thin_solaris_smf.erb +36 -0
  7. data/example/thin_solaris_smf.readme.txt +150 -0
  8. data/ext/thin_parser/common.rl +3 -2
  9. data/ext/thin_parser/parser.c +15 -38
  10. data/lib/rack/adapter/loader.rb +69 -0
  11. data/lib/rack/adapter/rails.rb +17 -8
  12. data/lib/thin.rb +1 -0
  13. data/lib/thin/backends/base.rb +17 -0
  14. data/lib/thin/backends/swiftiply_client.rb +3 -2
  15. data/lib/thin/backends/tcp_server.rb +1 -1
  16. data/lib/thin/connection.rb +38 -6
  17. data/lib/thin/controllers/controller.rb +43 -17
  18. data/lib/thin/logging.rb +1 -1
  19. data/lib/thin/request.rb +8 -1
  20. data/lib/thin/runner.rb +29 -7
  21. data/lib/thin/server.rb +58 -28
  22. data/lib/thin/version.rb +3 -3
  23. data/lib/thin_backend.bundle +0 -0
  24. data/lib/thin_parser.bundle +0 -0
  25. data/spec/connection_spec.rb +7 -0
  26. data/spec/controllers/controller_spec.rb +9 -1
  27. data/spec/rack/loader_spec.rb +29 -0
  28. data/spec/{rack_rails_spec.rb → rack/rails_adapter_spec.rb} +15 -3
  29. data/spec/rails_app/public/dispatch.cgi +0 -0
  30. data/spec/rails_app/public/dispatch.fcgi +0 -0
  31. data/spec/rails_app/public/dispatch.rb +0 -0
  32. data/spec/rails_app/script/about +0 -0
  33. data/spec/rails_app/script/console +0 -0
  34. data/spec/rails_app/script/destroy +0 -0
  35. data/spec/rails_app/script/generate +0 -0
  36. data/spec/rails_app/script/performance/benchmarker +0 -0
  37. data/spec/rails_app/script/performance/profiler +0 -0
  38. data/spec/rails_app/script/performance/request +0 -0
  39. data/spec/rails_app/script/plugin +0 -0
  40. data/spec/rails_app/script/process/inspector +0 -0
  41. data/spec/rails_app/script/process/reaper +0 -0
  42. data/spec/rails_app/script/process/spawner +0 -0
  43. data/spec/rails_app/script/runner +0 -0
  44. data/spec/rails_app/script/server +0 -0
  45. data/spec/request/parser_spec.rb +22 -0
  46. data/spec/request/processing_spec.rb +9 -10
  47. data/spec/runner_spec.rb +14 -1
  48. data/spec/server/builder_spec.rb +3 -2
  49. data/spec/server/robustness_spec.rb +34 -0
  50. data/spec/server/swiftiply_spec.rb +1 -1
  51. data/spec/server/threaded_spec.rb +27 -0
  52. data/spec/server_spec.rb +69 -0
  53. data/spec/spec_helper.rb +10 -5
  54. data/tasks/gem.rake +2 -2
  55. data/tasks/spec.rake +24 -16
  56. metadata +13 -5
  57. 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', nil, app)
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 first argument.
21
+ # own Backend class and pass it as the :backend option.
22
22
  #
23
- # backend = Thin::Backends::MyFancyBackend.new('galaxy://faraway:1345')
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 :@backend, :timeout, :timeout=
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 :@backend, :maximum_connections, :maximum_connections=
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 :@backend, :maximum_persistent_connections, :maximum_persistent_connections=
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 :@backend, :host, :port
83
+ def_delegators :backend, :host, :port
76
84
 
77
85
  # UNIX domain socket on which the server is listening for connections.
78
- def_delegator :@backend, :socket
86
+ def_delegator :backend, :socket
79
87
 
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
- else
88
- Backends::TcpServer.new(host_or_socket_or_backend, port.to_i)
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 set configure
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 = 7
9
- TINY = 1
8
+ MINOR = 8
9
+ TINY = 0
10
10
 
11
11
  STRING = [MAJOR, MINOR, TINY].join('.')
12
12
 
13
- CODENAME = 'Fancy Pants'
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
@@ -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__) + '/spec_helper'
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__) + '/rails_app'
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__) + '/rails_app'
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
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
@@ -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: #{Request::MAX_BODY*2}\r\n\r\n#{'X' * Request::MAX_BODY}")
20
- request.parse('X' * Request::MAX_BODY)
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 == Request::MAX_BODY * 2
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 = R(<<-EOS.chomp, true)
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
@@ -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
- server = Server.new('0.0.0.0', 3000, :works)
5
+ app = proc {}
6
+ server = Server.new('0.0.0.0', 3000, app)
6
7
 
7
- server.app.should == :works
8
+ server.app.should == app
8
9
  end
9
10
 
10
11
  it "should build app from builder block" do