thin 1.2.4-x86-mswin32 → 1.2.6-x86-mswin32

Sign up to get free protection for your applications and to get access to all the features.
@@ -207,6 +207,9 @@ static void header_done(void *data, const char *at, size_t length)
207
207
  if (rb_hash_aref(req, global_query_string) == Qnil) {
208
208
  rb_hash_aset(req, global_query_string, global_empty);
209
209
  }
210
+ if (rb_hash_aref(req, global_path_info) == Qnil) {
211
+ rb_hash_aset(req, global_path_info, global_empty);
212
+ }
210
213
 
211
214
  /* set some constants */
212
215
  rb_hash_aset(req, global_server_protocol, global_server_protocol_value);
@@ -8,6 +8,7 @@ module Rack
8
8
  # NOTE: If a framework has a file that is not unique, make sure to place
9
9
  # it at the end.
10
10
  ADAPTERS = [
11
+ [:rack, 'config.ru'],
11
12
  [:rails, 'config/environment.rb'],
12
13
  [:ramaze, 'start.rb'],
13
14
  [:halcyon, 'runner.ru'],
@@ -29,9 +30,20 @@ module Rack
29
30
  raise AdapterNotFound, "No adapter found for #{dir}"
30
31
  end
31
32
 
33
+ # Load a Rack application from a Rack config file (.ru).
34
+ def self.load(config)
35
+ rackup_code = ::File.read(config)
36
+ eval("Rack::Builder.new {( #{rackup_code}\n )}.to_app", TOPLEVEL_BINDING, config)
37
+ end
38
+
32
39
  # Loads an adapter identified by +name+ using +options+ hash.
33
40
  def self.for(name, options={})
41
+ ENV['RACK_ENV'] = options[:environment]
42
+
34
43
  case name.to_sym
44
+ when :rack
45
+ return load(::File.join(options[:chdir], "config.ru"))
46
+
35
47
  when :rails
36
48
  return Rails.new(options.merge(:root => options[:chdir]))
37
49
 
@@ -32,9 +32,8 @@ module Rack
32
32
  end
33
33
 
34
34
  def rack_based?
35
- ActionController.const_defined?(:Dispatcher) &&
36
- (ActionController::Dispatcher.instance_methods.include?(:call) ||
37
- ActionController::Dispatcher.instance_methods.include?("call"))
35
+ rails_version = ::Rails::VERSION
36
+ rails_version::MAJOR >= 2 && rails_version::MINOR >= 2 && rails_version::TINY >= 3
38
37
  end
39
38
 
40
39
  def load_application
data/lib/thin.rb CHANGED
@@ -3,44 +3,44 @@ require 'timeout'
3
3
  require 'stringio'
4
4
  require 'time'
5
5
  require 'forwardable'
6
-
7
6
  require 'openssl'
8
7
  require 'eventmachine'
9
-
10
- require 'thin/version'
11
- require 'thin/statuses'
8
+ require 'rack'
12
9
 
13
10
  module Thin
14
- autoload :Command, 'thin/command'
15
- autoload :Connection, 'thin/connection'
16
- autoload :Daemonizable, 'thin/daemonizing'
17
- autoload :Logging, 'thin/logging'
18
- autoload :Headers, 'thin/headers'
19
- autoload :Request, 'thin/request'
20
- autoload :Response, 'thin/response'
21
- autoload :Runner, 'thin/runner'
22
- autoload :Server, 'thin/server'
23
- autoload :Stats, 'thin/stats'
11
+ ROOT = File.expand_path(File.dirname(__FILE__))
12
+
13
+ autoload :Command, "#{ROOT}/thin/command"
14
+ autoload :Connection, "#{ROOT}/thin/connection"
15
+ autoload :Daemonizable, "#{ROOT}/thin/daemonizing"
16
+ autoload :Logging, "#{ROOT}/thin/logging"
17
+ autoload :Headers, "#{ROOT}/thin/headers"
18
+ autoload :Request, "#{ROOT}/thin/request"
19
+ autoload :Response, "#{ROOT}/thin/response"
20
+ autoload :Runner, "#{ROOT}/thin/runner"
21
+ autoload :Server, "#{ROOT}/thin/server"
22
+ autoload :Stats, "#{ROOT}/thin/stats"
24
23
 
25
24
  module Backends
26
- autoload :Base, 'thin/backends/base'
27
- autoload :SwiftiplyClient, 'thin/backends/swiftiply_client'
28
- autoload :TcpServer, 'thin/backends/tcp_server'
29
- autoload :UnixServer, 'thin/backends/unix_server'
25
+ autoload :Base, "#{ROOT}/thin/backends/base"
26
+ autoload :SwiftiplyClient, "#{ROOT}/thin/backends/swiftiply_client"
27
+ autoload :TcpServer, "#{ROOT}/thin/backends/tcp_server"
28
+ autoload :UnixServer, "#{ROOT}/thin/backends/unix_server"
30
29
  end
31
30
 
32
31
  module Controllers
33
- autoload :Cluster, 'thin/controllers/cluster'
34
- autoload :Controller, 'thin/controllers/controller'
35
- autoload :Service, 'thin/controllers/service'
32
+ autoload :Cluster, "#{ROOT}/thin/controllers/cluster"
33
+ autoload :Controller, "#{ROOT}/thin/controllers/controller"
34
+ autoload :Service, "#{ROOT}/thin/controllers/service"
36
35
  end
37
36
  end
38
37
 
39
- require 'rack'
40
- require 'rack/adapter/loader'
38
+ require "#{Thin::ROOT}/thin/version"
39
+ require "#{Thin::ROOT}/thin/statuses"
40
+ require "#{Thin::ROOT}/rack/adapter/loader"
41
41
 
42
42
  module Rack
43
43
  module Adapter
44
- autoload :Rails, 'rack/adapter/rails'
44
+ autoload :Rails, "#{Thin::ROOT}/rack/adapter/rails"
45
45
  end
46
46
  end
@@ -1,4 +1,9 @@
1
+ require 'socket'
2
+
1
3
  module Thin
4
+ # An exception class to handle the event that server didn't start on time
5
+ class RestartTimeout < RuntimeError; end
6
+
2
7
  module Controllers
3
8
  # Control a set of servers.
4
9
  # * Generate start and stop commands and run them.
@@ -7,7 +12,10 @@ module Thin
7
12
  class Cluster < Controller
8
13
  # Cluster only options that should not be passed in the command sent
9
14
  # to the indiviual servers.
10
- CLUSTER_OPTIONS = [:servers, :only]
15
+ CLUSTER_OPTIONS = [:servers, :only, :onebyone, :wait]
16
+
17
+ # Maximum wait time for the server to be restarted
18
+ DEFAULT_WAIT_TIME = 30 # seconds
11
19
 
12
20
  # Create a new cluster of servers launched using +options+.
13
21
  def initialize(options)
@@ -15,7 +23,7 @@ module Thin
15
23
  # Cluster can only contain daemonized servers
16
24
  @options.merge!(:daemonize => true)
17
25
  end
18
-
26
+
19
27
  def first_port; @options[:port] end
20
28
  def address; @options[:address] end
21
29
  def socket; @options[:socket] end
@@ -23,7 +31,9 @@ module Thin
23
31
  def log_file; @options[:log] end
24
32
  def size; @options[:servers] end
25
33
  def only; @options[:only] end
26
-
34
+ def onebyone; @options[:onebyone] end
35
+ def wait; @options[:wait] end
36
+
27
37
  def swiftiply?
28
38
  @options.has_key?(:swiftiply)
29
39
  end
@@ -54,9 +64,50 @@ module Thin
54
64
 
55
65
  # Stop and start the servers.
56
66
  def restart
57
- stop
58
- sleep 0.1 # Let's breath a bit shall we ?
59
- start
67
+ unless onebyone
68
+ # Let's do a normal restart by defaults
69
+ stop
70
+ sleep 0.1 # Let's breath a bit shall we ?
71
+ start
72
+ else
73
+ with_each_server do |n|
74
+ stop_server(n)
75
+ sleep 0.1 # Let's breath a bit shall we ?
76
+ start_server(n)
77
+ wait_until_server_started(n)
78
+ end
79
+ end
80
+ end
81
+
82
+ def test_socket(number)
83
+ if socket
84
+ UNIXSocket.new(socket_for(number))
85
+ else
86
+ TCPSocket.new(address, number)
87
+ end
88
+ rescue
89
+ nil
90
+ end
91
+
92
+ # Make sure the server is running before moving on to the next one.
93
+ def wait_until_server_started(number)
94
+ log "Waiting for server to start ..."
95
+ STDOUT.flush # Need this to make sure user got the message
96
+
97
+ tries = 0
98
+ loop do
99
+ if test_socket = test_socket(number)
100
+ test_socket.close
101
+ break
102
+ elsif tries < wait
103
+ sleep 1
104
+ tries += 1
105
+ else
106
+ raise RestartTimeout, "The server didn't start in time. Please look at server's log file " +
107
+ "for more information, or set the value of 'wait' in your config " +
108
+ "file to be higher (defaults: 30)."
109
+ end
110
+ end
60
111
  end
61
112
 
62
113
  def server_id(number)
@@ -172,8 +172,7 @@ module Thin
172
172
  Kernel.load(@options[:rackup])
173
173
  Object.const_get(File.basename(@options[:rackup], '.rb').capitalize.to_sym)
174
174
  when /\.ru$/
175
- rackup_code = File.read(@options[:rackup])
176
- eval("Rack::Builder.new {( #{rackup_code}\n )}.to_app", TOPLEVEL_BINDING, @options[:rackup])
175
+ Rack::Adapter.load(@options[:rackup])
177
176
  else
178
177
  raise "Invalid rackup file. please specify either a .ru or .rb file"
179
178
  end
@@ -36,18 +36,21 @@ module Thin
36
36
  def daemonize
37
37
  raise PlatformNotSupported, 'Daemonizing is not supported on Windows' if Thin.win?
38
38
  raise ArgumentError, 'You must specify a pid_file to daemonize' unless @pid_file
39
-
39
+
40
40
  remove_stale_pid_file
41
41
 
42
42
  pwd = Dir.pwd # Current directory is changed during daemonization, so store it
43
43
 
44
+ # HACK we need to create the directory before daemonization to prevent a bug under 1.9
45
+ # ignoring all signals when the directory is created after daemonization.
46
+ FileUtils.mkdir_p File.dirname(@pid_file)
47
+
44
48
  Daemonize.daemonize(File.expand_path(@log_file), name)
45
49
 
46
50
  Dir.chdir(pwd)
47
51
 
48
52
  write_pid_file
49
53
 
50
- trap('HUP') { restart }
51
54
  at_exit do
52
55
  log ">> Exiting!"
53
56
  remove_pid_file
@@ -153,7 +156,6 @@ module Thin
153
156
 
154
157
  def write_pid_file
155
158
  log ">> Writing PID to #{@pid_file}"
156
- FileUtils.mkdir_p File.dirname(@pid_file)
157
159
  open(@pid_file,"w") { |f| f.write(Process.pid) }
158
160
  File.chmod(0644, @pid_file)
159
161
  end
data/lib/thin/request.rb CHANGED
@@ -1,4 +1,4 @@
1
- require 'thin_parser'
1
+ require "#{Thin::ROOT}/thin_parser"
2
2
  require 'tempfile'
3
3
 
4
4
  module Thin
@@ -13,7 +13,11 @@ module Thin
13
13
  MAX_BODY = 1024 * (80 + 32)
14
14
  BODY_TMPFILE = 'thin-body'.freeze
15
15
  MAX_HEADER = 1024 * (80 + 32)
16
-
16
+
17
+ INITIAL_BODY = ''
18
+ # Force external_encoding of request's body to ASCII_8BIT
19
+ INITIAL_BODY.encode!(Encoding::ASCII_8BIT) if INITIAL_BODY.respond_to?(:encode!)
20
+
17
21
  # Freeze some HTTP header names & values
18
22
  SERVER_SOFTWARE = 'SERVER_SOFTWARE'.freeze
19
23
  SERVER_NAME = 'SERVER_NAME'.freeze
@@ -49,7 +53,7 @@ module Thin
49
53
  @parser = Thin::HttpParser.new
50
54
  @data = ''
51
55
  @nparsed = 0
52
- @body = StringIO.new
56
+ @body = StringIO.new(INITIAL_BODY.dup)
53
57
  @env = {
54
58
  SERVER_SOFTWARE => SERVER,
55
59
  SERVER_NAME => LOCALHOST,
data/lib/thin/runner.rb CHANGED
@@ -41,7 +41,8 @@ module Thin
41
41
  :pid => 'tmp/pids/thin.pid',
42
42
  :max_conns => Server::DEFAULT_MAXIMUM_CONNECTIONS,
43
43
  :max_persistent_conns => Server::DEFAULT_MAXIMUM_PERSISTENT_CONNECTIONS,
44
- :require => []
44
+ :require => [],
45
+ :wait => Controllers::Cluster::DEFAULT_WAIT_TIME
45
46
  }
46
47
 
47
48
  parse!
@@ -96,6 +97,8 @@ module Thin
96
97
  opts.on("-o", "--only NUM", "Send command to only one server of the cluster") { |only| @options[:only] = only.to_i }
97
98
  opts.on("-C", "--config FILE", "Load options from config file") { |file| @options[:config] = file }
98
99
  opts.on( "--all [DIR]", "Send command to each config files in DIR") { |dir| @options[:all] = dir } if Thin.linux?
100
+ opts.on("-O", "--onebyone", "Restart the cluster one by one (only works with restart command)") { @options[:onebyone] = true }
101
+ opts.on("-w", "--wait NUM", "Maximum wait time for server to be started in seconds (use with -O)") { |time| @options[:wait] = time.to_i }
99
102
  end
100
103
 
101
104
  opts.separator ""
@@ -105,7 +108,7 @@ module Thin
105
108
  opts.on("-t", "--timeout SEC", "Request or command timeout in sec " +
106
109
  "(default: #{@options[:timeout]})") { |sec| @options[:timeout] = sec.to_i }
107
110
  opts.on("-f", "--force", "Force the execution of the command") { @options[:force] = true }
108
- opts.on( "--max-conns NUM", "Maximum number of connections " +
111
+ opts.on( "--max-conns NUM", "Maximum number of open file descriptors " +
109
112
  "(default: #{@options[:max_conns]})",
110
113
  "Might require sudo to set higher then 1024") { |num| @options[:max_conns] = num.to_i } unless Thin.win?
111
114
  opts.on( "--max-persistent-conns NUM",
data/lib/thin/server.rb CHANGED
@@ -208,11 +208,12 @@ module Thin
208
208
  protected
209
209
  # Register signals:
210
210
  # * INT calls +stop+ to shutdown gracefully.
211
- # * TERM calls <tt>stop!</tt> to force shutdown.
211
+ # * TERM calls <tt>stop!</tt> to force shutdown.
212
212
  def setup_signals
213
213
  trap('QUIT') { stop } unless Thin.win?
214
214
  trap('INT') { stop! }
215
215
  trap('TERM') { stop! }
216
+ trap('HUP') { restart }
216
217
  end
217
218
 
218
219
  def select_backend(host, port, options)
data/lib/thin/version.rb CHANGED
@@ -6,11 +6,11 @@ module Thin
6
6
  module VERSION #:nodoc:
7
7
  MAJOR = 1
8
8
  MINOR = 2
9
- TINY = 4
9
+ TINY = 6
10
10
 
11
11
  STRING = [MAJOR, MINOR, TINY].join('.')
12
12
 
13
- CODENAME = "Flaming Astroboy".freeze
13
+ CODENAME = "Crazy Delicious".freeze
14
14
 
15
15
  RACK = [1, 0].freeze # Rack protocol version
16
16
  end
data/lib/thin_parser.so CHANGED
Binary file
@@ -232,4 +232,36 @@ describe Cluster, "with Swiftiply" do
232
232
  def options_for_swiftiply(number)
233
233
  { :address => '0.0.0.0', :port => 3000, :daemonize => true, :log => "thin.#{number}.log", :timeout => 10, :pid => "thin.#{number}.pid", :chdir => "/rails_app", :swiftiply => true }
234
234
  end
235
+ end
236
+
237
+ describe Cluster, "rolling restart" do
238
+ before do
239
+ @cluster = Cluster.new(:chdir => '/rails_app',
240
+ :address => '0.0.0.0',
241
+ :port => 3000,
242
+ :servers => 2,
243
+ :timeout => 10,
244
+ :log => 'thin.log',
245
+ :pid => 'thin.pid',
246
+ :onebyone => true,
247
+ :wait => 30
248
+ )
249
+ end
250
+
251
+ it "should restart servers one by one" do
252
+ Command.should_receive(:run).with(:stop, options_for_port(3000))
253
+ Command.should_receive(:run).with(:start, options_for_port(3000))
254
+ @cluster.should_receive(:wait_until_server_started).with(3000)
255
+
256
+ Command.should_receive(:run).with(:stop, options_for_port(3001))
257
+ Command.should_receive(:run).with(:start, options_for_port(3001))
258
+ @cluster.should_receive(:wait_until_server_started).with(3001)
259
+
260
+ @cluster.restart
261
+ end
262
+
263
+ private
264
+ def options_for_port(port)
265
+ { :daemonize => true, :log => "thin.#{port}.log", :timeout => 10, :address => "0.0.0.0", :port => port, :pid => "thin.#{port}.pid", :chdir => "/rails_app" }
266
+ end
235
267
  end
@@ -2,9 +2,18 @@ require File.dirname(__FILE__) + '/../spec_helper'
2
2
 
3
3
  describe Rack::Adapter do
4
4
  before do
5
+ @config_ru_path = File.dirname(__FILE__) + '/../../example'
5
6
  @rails_path = File.dirname(__FILE__) + '/../rails_app'
6
7
  end
7
8
 
9
+ it "should load Rack app from config" do
10
+ Rack::Adapter.load(@config_ru_path + '/config.ru').class.should == Proc
11
+ end
12
+
13
+ it "should guess Rack app from dir" do
14
+ Rack::Adapter.guess(@config_ru_path).should == :rack
15
+ end
16
+
8
17
  it "should guess rails app from dir" do
9
18
  Rack::Adapter.guess(@rails_path).should == :rails
10
19
  end
@@ -13,6 +22,10 @@ describe Rack::Adapter do
13
22
  proc { Rack::Adapter.guess('.') }.should raise_error(Rack::AdapterNotFound)
14
23
  end
15
24
 
25
+ it "should load Rack adapter" do
26
+ Rack::Adapter.for(:rack, :chdir => @config_ru_path).class.should == Proc
27
+ end
28
+
16
29
  it "should load Rails adapter" do
17
30
  Rack::Adapter::Rails.should_receive(:new)
18
31
  Rack::Adapter.for(:rails, :chdir => @rails_path)
@@ -209,7 +209,35 @@ EOS
209
209
  parser = HttpParser.new
210
210
  req = {}
211
211
  nread = parser.execute(req, req_str, 0)
212
- req.should be_has_key('HTTP_HOS_T')
212
+ req.should have_key('HTTP_HOS_T')
213
213
  }
214
214
  end
215
+
216
+ it "should parse PATH_INFO with semicolon" do
217
+ qs = "QUERY_STRING"
218
+ pi = "PATH_INFO"
219
+ {
220
+ "/1;a=b?c=d&e=f" => { qs => "c=d&e=f", pi => "/1;a=b" },
221
+ "/1?c=d&e=f" => { qs => "c=d&e=f", pi => "/1" },
222
+ "/1;a=b" => { qs => "", pi => "/1;a=b" },
223
+ "/1;a=b?" => { qs => "", pi => "/1;a=b" },
224
+ "/1?a=b;c=d&e=f" => { qs => "a=b;c=d&e=f", pi => "/1" },
225
+ "*" => { qs => "", pi => "" },
226
+ }.each do |uri, expect|
227
+ parser = HttpParser.new
228
+ env = {}
229
+ nread = parser.execute(env, "GET #{uri} HTTP/1.1\r\nHost: www.example.com\r\n\r\n", 0)
230
+
231
+ env[pi].should == expect[pi]
232
+ env[qs].should == expect[qs]
233
+ env["REQUEST_URI"].should == uri
234
+
235
+ next if uri == "*"
236
+
237
+ # Validate w/ Ruby's URI.parse
238
+ uri = URI.parse("http://example.com#{uri}")
239
+ env[qs].should == uri.query.to_s
240
+ env[pi].should == uri.path
241
+ end
242
+ end
215
243
  end