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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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