thin 1.0.0 → 1.2.1

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.

@@ -102,7 +102,13 @@ module Thin
102
102
 
103
103
  def with_each_server
104
104
  if only
105
- yield only
105
+ if only < 80
106
+ # interpret +only+ as a sequence number
107
+ yield(first_port + only)
108
+ else
109
+ # interpret +only+ as an absolute port number
110
+ yield only
111
+ end
106
112
  elsif socket || swiftiply?
107
113
  size.times { |n| yield n }
108
114
  else
@@ -166,6 +166,7 @@ module Thin
166
166
  end
167
167
 
168
168
  def load_rackup_config
169
+ ENV['RACK_ENV'] = @options[:environment]
169
170
  case @options[:rackup]
170
171
  when /\.rb$/
171
172
  Kernel.load(@options[:rackup])
@@ -16,6 +16,8 @@ module Thin
16
16
 
17
17
  # Freeze some HTTP header names & values
18
18
  SERVER_SOFTWARE = 'SERVER_SOFTWARE'.freeze
19
+ SERVER_NAME = 'SERVER_NAME'.freeze
20
+ LOCALHOST = 'localhost'.freeze
19
21
  HTTP_VERSION = 'HTTP_VERSION'.freeze
20
22
  HTTP_1_0 = 'HTTP/1.0'.freeze
21
23
  REMOTE_ADDR = 'REMOTE_ADDR'.freeze
@@ -24,7 +26,7 @@ module Thin
24
26
  CONNECTION = 'HTTP_CONNECTION'.freeze
25
27
  KEEP_ALIVE_REGEXP = /\bkeep-alive\b/i.freeze
26
28
  CLOSE_REGEXP = /\bclose\b/i.freeze
27
-
29
+
28
30
  # Freeze some Rack header names
29
31
  RACK_INPUT = 'rack.input'.freeze
30
32
  RACK_VERSION = 'rack.version'.freeze
@@ -32,6 +34,8 @@ module Thin
32
34
  RACK_MULTITHREAD = 'rack.multithread'.freeze
33
35
  RACK_MULTIPROCESS = 'rack.multiprocess'.freeze
34
36
  RACK_RUN_ONCE = 'rack.run_once'.freeze
37
+ ASYNC_CALLBACK = 'async.callback'.freeze
38
+ ASYNC_CLOSE = 'async.close'.freeze
35
39
 
36
40
  # CGI-like request environment variables
37
41
  attr_reader :env
@@ -43,12 +47,13 @@ module Thin
43
47
  attr_reader :body
44
48
 
45
49
  def initialize
46
- @parser = HttpParser.new
50
+ @parser = Thin::HttpParser.new
47
51
  @data = ''
48
52
  @nparsed = 0
49
53
  @body = StringIO.new
50
54
  @env = {
51
55
  SERVER_SOFTWARE => SERVER,
56
+ SERVER_NAME => LOCALHOST,
52
57
 
53
58
  # Rack stuff
54
59
  RACK_INPUT => @body,
@@ -125,6 +130,15 @@ module Thin
125
130
  def threaded=(value)
126
131
  @env[RACK_MULTITHREAD] = value
127
132
  end
133
+
134
+ def async_callback=(callback)
135
+ @env[ASYNC_CALLBACK] = callback
136
+ @env[ASYNC_CLOSE] = EventMachine::DefaultDeferrable.new
137
+ end
138
+
139
+ def async_close
140
+ @async_close ||= @env[ASYNC_CLOSE]
141
+ end
128
142
 
129
143
  # Close any resource used by the request
130
144
  def close
@@ -80,8 +80,10 @@ module Thin
80
80
  # define your own +each+ method on +body+.
81
81
  def each
82
82
  yield head
83
- @body.each do |chunk|
84
- yield chunk
83
+ if @body.is_a?(String)
84
+ yield @body
85
+ else
86
+ @body.each { |chunk| yield chunk }
85
87
  end
86
88
  end
87
89
 
@@ -92,7 +92,7 @@ module Thin
92
92
  opts.separator "Cluster options:"
93
93
 
94
94
  opts.on("-s", "--servers NUM", "Number of servers to start") { |num| @options[:servers] = num.to_i }
95
- opts.on("-o", "--only NUM", "Send command to only one server of the cluster") { |only| @options[:only] = only }
95
+ opts.on("-o", "--only NUM", "Send command to only one server of the cluster") { |only| @options[:only] = only.to_i }
96
96
  opts.on("-C", "--config FILE", "Load options from config file") { |file| @options[:config] = file }
97
97
  opts.on( "--all [DIR]", "Send command to each config files in DIR") { |dir| @options[:all] = dir } if Thin.linux?
98
98
  end
@@ -5,21 +5,21 @@ module Thin
5
5
 
6
6
  module VERSION #:nodoc:
7
7
  MAJOR = 1
8
- MINOR = 0
9
- TINY = 0
8
+ MINOR = 2
9
+ TINY = 1
10
10
 
11
11
  STRING = [MAJOR, MINOR, TINY].join('.')
12
12
 
13
- CODENAME = "That's What She Said".freeze
13
+ CODENAME = "Asynctilicious Ultra Supreme".freeze
14
14
 
15
- RACK = [0, 3].freeze # Latest Rack version that was tested
15
+ RACK = [1, 0].freeze # Rack protocol version
16
16
  end
17
17
 
18
18
  NAME = 'thin'.freeze
19
19
  SERVER = "#{NAME} #{VERSION::STRING} codename #{VERSION::CODENAME}".freeze
20
20
 
21
21
  def self.win?
22
- RUBY_PLATFORM =~ /mswin/
22
+ RUBY_PLATFORM =~ /mswin|mingw/
23
23
  end
24
24
 
25
25
  def self.linux?
Binary file
@@ -4,7 +4,7 @@ describe Command do
4
4
  before do
5
5
  Command.script = 'thin'
6
6
  @command = Command.new(:start, :port => 3000, :daemonize => true, :log => 'hi.log',
7
- :require => %w(rubygems thin))
7
+ :require => %w(rubygems thin), :no_epoll => true)
8
8
  end
9
9
 
10
10
  it 'should shellify command' do
@@ -17,4 +17,9 @@ describe Command do
17
17
  out = @command.shellify
18
18
  out.should include('--require="rubygems"', '--require="thin"')
19
19
  end
20
+
21
+ it 'should convert _ to - in option name' do
22
+ out = @command.shellify
23
+ out.should include('--no-epoll')
24
+ end
20
25
  end
@@ -135,6 +135,39 @@ describe Cluster, "controlling only one server" do
135
135
  end
136
136
  end
137
137
 
138
+ describe Cluster, "controlling only one server, by sequence number" do
139
+ before do
140
+ @cluster = Cluster.new(:chdir => '/rails_app',
141
+ :address => '0.0.0.0',
142
+ :port => 3000,
143
+ :servers => 3,
144
+ :timeout => 10,
145
+ :log => 'thin.log',
146
+ :pid => 'thin.pid',
147
+ :only => 1
148
+ )
149
+ end
150
+
151
+ it 'should call only specified server' do
152
+ calls = []
153
+ @cluster.send(:with_each_server) do |n|
154
+ calls << n
155
+ end
156
+ calls.should == [3001]
157
+ end
158
+
159
+ it "should start only specified server" do
160
+ Command.should_receive(:run).with(:start, options_for_port(3001))
161
+
162
+ @cluster.start
163
+ end
164
+
165
+ private
166
+ def options_for_port(port)
167
+ { :daemonize => true, :log => "thin.#{port}.log", :timeout => 10, :address => "0.0.0.0", :port => port, :pid => "thin.#{port}.pid", :chdir => "/rails_app" }
168
+ end
169
+ end
170
+
138
171
  describe Cluster, "with Swiftiply" do
139
172
  before do
140
173
  @cluster = Cluster.new(:chdir => '/rails_app',
@@ -63,14 +63,14 @@ describe Controller, 'start' do
63
63
  end
64
64
 
65
65
  it "should load app from Rack config" do
66
- @controller.options[:rackup] = File.dirname(__FILE__) + '/../../example/config.ru'
66
+ @controller.options[:rackup] = File.dirname(__FILE__) + '/../../example/config.ru'
67
67
  @controller.start
68
68
 
69
69
  @server.app.class.should == Proc
70
70
  end
71
71
 
72
72
  it "should load app from ruby file" do
73
- @controller.options[:rackup] = filename = File.dirname(__FILE__) + '/../../example/myapp.rb'
73
+ @controller.options[:rackup] = File.dirname(__FILE__) + '/../../example/myapp.rb'
74
74
  @controller.start
75
75
 
76
76
  @server.app.should == Myapp
@@ -78,7 +78,7 @@ describe Controller, 'start' do
78
78
 
79
79
  it "should throwup if rackup is not a .ru or .rb file" do
80
80
  proc do
81
- @controller.options[:rackup] = filename = File.dirname(__FILE__) + '/../../example/myapp.foo'
81
+ @controller.options[:rackup] = File.dirname(__FILE__) + '/../../example/myapp.foo'
82
82
  @controller.start
83
83
  end.should raise_error(RuntimeError, /please/)
84
84
  end
@@ -89,6 +89,14 @@ describe Controller, 'start' do
89
89
 
90
90
  @server.threaded.should be_true
91
91
  end
92
+
93
+ it "should set RACK_ENV" do
94
+ @controller.options[:rackup] = File.dirname(__FILE__) + '/../../example/config.ru'
95
+ @controller.options[:environment] = "lolcat"
96
+ @controller.start
97
+
98
+ ENV['RACK_ENV'].should == "lolcat"
99
+ end
92
100
 
93
101
  end
94
102
 
@@ -1,5 +1,12 @@
1
1
  require File.dirname(__FILE__) + '/../spec_helper'
2
2
 
3
+ # Require mongrel so we can test that Thin parser don't clash w/ Mongrel parser.
4
+ begin
5
+ require 'mongrel'
6
+ rescue LoadError
7
+ warn "Install mongrel to test compatibility w/ it"
8
+ end
9
+
3
10
  describe Request, 'parser' do
4
11
  it 'should include basic headers' do
5
12
  request = R("GET / HTTP/1.1\r\nHost: localhost\r\n\r\n")
@@ -52,7 +59,7 @@ describe Request, 'parser' do
52
59
  it 'should parse headers from GET request' do
53
60
  request = R(<<-EOS, true)
54
61
  GET / HTTP/1.1
55
- Host: localhost:3000
62
+ Host: myhost.com:3000
56
63
  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
57
64
  Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
58
65
  Accept-Language: en-us,en;q=0.5
@@ -63,8 +70,8 @@ Keep-Alive: 300
63
70
  Connection: keep-alive
64
71
 
65
72
  EOS
66
- request.env['HTTP_HOST'].should == 'localhost:3000'
67
- request.env['SERVER_NAME'].should == 'localhost'
73
+ request.env['HTTP_HOST'].should == 'myhost.com:3000'
74
+ request.env['SERVER_NAME'].should == 'myhost.com'
68
75
  request.env['SERVER_PORT'].should == '3000'
69
76
  request.env['HTTP_COOKIE'].should == 'mium=7'
70
77
 
@@ -167,7 +174,7 @@ EOS
167
174
  sorta_safe = %(GET #{path} HTTP/1.1\r\n\r\n)
168
175
  nread = parser.execute(req, sorta_safe, 0)
169
176
 
170
- sorta_safe.size.should == nread
177
+ sorta_safe.size.should == nread - 1 # Ragel 6 skips last linebreak
171
178
  parser.should be_finished
172
179
  parser.should_not be_error
173
180
  end
@@ -188,4 +195,21 @@ EOS
188
195
  it "should fails on heders larger then MAX_HEADER" do
189
196
  proc { R("GET / HTTP/1.1\r\nFoo: #{'X' * Request::MAX_HEADER}\r\n\r\n") }.should raise_error(InvalidRequest)
190
197
  end
198
+
199
+ it "should default SERVER_NAME to localhost" do
200
+ request = R("GET / HTTP/1.1\r\n\r\n")
201
+ request.env['SERVER_NAME'].should == "localhost"
202
+ end
203
+
204
+ it 'should normalize http_fields' do
205
+ [ "GET /index.html HTTP/1.1\r\nhos-t: localhost\r\n\r\n",
206
+ "GET /index.html HTTP/1.1\r\nhOs_t: localhost\r\n\r\n",
207
+ "GET /index.html HTTP/1.1\r\nhoS-T: localhost\r\n\r\n"
208
+ ].each { |req_str|
209
+ parser = HttpParser.new
210
+ req = {}
211
+ nread = parser.execute(req, req_str, 0)
212
+ req.should be_has_key('HTTP_HOS_T')
213
+ }
214
+ end
191
215
  end
@@ -52,6 +52,14 @@ describe Response do
52
52
  end
53
53
 
54
54
  it 'should output body' do
55
+ @response.body = ['<html>', '</html>']
56
+
57
+ out = ''
58
+ @response.each { |l| out << l }
59
+ out.should include("\r\n\r\n<html></html>")
60
+ end
61
+
62
+ it 'should output String body' do
55
63
  @response.body = '<html></html>'
56
64
 
57
65
  out = ''
@@ -2,10 +2,11 @@ require File.dirname(__FILE__) + '/spec_helper'
2
2
 
3
3
  describe Runner do
4
4
  it "should parse options" do
5
- runner = Runner.new(%w(start --pid test.pid --port 5000))
5
+ runner = Runner.new(%w(start --pid test.pid --port 5000 -o 3000))
6
6
 
7
7
  runner.options[:pid].should == 'test.pid'
8
8
  runner.options[:port].should == 5000
9
+ runner.options[:only].should == 3000
9
10
  end
10
11
 
11
12
  it "should parse specified command" do
@@ -164,4 +165,4 @@ describe Runner, "service" do
164
165
 
165
166
  runner.run!
166
167
  end
167
- end
168
+ end
@@ -16,6 +16,7 @@ describe Server, "HTTP pipelining" do
16
16
  socket.write "GET /first HTTP/1.1\r\nConnection: keep-alive\r\n\r\n"
17
17
  socket.flush
18
18
  socket.write "GET /second HTTP/1.1\r\nConnection: close\r\n\r\n"
19
+ socket.flush
19
20
  response = socket.read
20
21
  socket.close
21
22
 
@@ -5,13 +5,21 @@ describe Server, "stopping" do
5
5
  start_server do |env|
6
6
  [200, { 'Content-Type' => 'text/html' }, ['ok']]
7
7
  end
8
+ @done = false
8
9
  end
9
10
 
10
11
  it "should wait for current requests before soft stopping" do
11
12
  socket = TCPSocket.new('0.0.0.0', 3333)
12
13
  socket.write("GET / HTTP/1.1")
13
- @server.stop # Stop the server in the middle of a request
14
- socket.write("\r\n\r\n")
14
+ EventMachine.next_tick do
15
+ @server.stop # Stop the server in the middle of a request
16
+ socket.write("\r\n\r\n")
17
+ @done = true
18
+ end
19
+
20
+ timeout(2) do
21
+ Thread.pass until @done
22
+ end
15
23
 
16
24
  out = socket.read
17
25
  socket.close
@@ -36,7 +44,9 @@ describe Server, "stopping" do
36
44
  socket.write("GET / HTTP/1.1")
37
45
  @server.stop! # Force stop the server in the middle of a request
38
46
 
39
- EventMachine.next_tick { socket.should be_closed }
47
+ EventMachine.next_tick do
48
+ socket.should be_closed
49
+ end
40
50
  end
41
51
 
42
52
  after do
@@ -63,7 +63,7 @@ module Matchers
63
63
  class ValidateWithLint
64
64
  def matches?(request)
65
65
  @request = request
66
- Rack::Lint.new(proc{[200, {'Content-Type' => 'text/html'}, []]}).call(@request.env)
66
+ Rack::Lint.new(proc{[200, {'Content-Type' => 'text/html', 'Content-Length' => '0'}, []]}).call(@request.env)
67
67
  true
68
68
  rescue Rack::Lint::LintError => e
69
69
  @message = e.message
@@ -71,7 +71,7 @@ module Matchers
71
71
  end
72
72
 
73
73
  def failure_message(negation=nil)
74
- "should#{negation} validate with Rack Lint"
74
+ "should#{negation} validate with Rack Lint: #{@message}"
75
75
  end
76
76
 
77
77
  def negative_failure_message
@@ -1,4 +1,5 @@
1
1
  require 'rake/gempackagetask'
2
+ require 'yaml'
2
3
 
3
4
  WIN_SUFFIX = ENV['WIN_SUFFIX'] || 'i386-mswin32'
4
5
 
@@ -19,13 +20,13 @@ spec = Gem::Specification.new do |s|
19
20
 
20
21
  s.required_ruby_version = '>= 1.8.5'
21
22
 
22
- s.add_dependency 'rack', '>= 0.3.0'
23
- s.add_dependency 'eventmachine', '>= 0.12.0'
23
+ s.add_dependency 'rack', '>= 1.0.0'
24
+ s.add_dependency 'eventmachine', '>= 0.12.6'
24
25
  unless WIN
25
26
  s.add_dependency 'daemons', '>= 1.0.9'
26
27
  end
27
28
 
28
- s.files = %w(COPYING CHANGELOG COMMITTERS README Rakefile) +
29
+ s.files = %w(COPYING CHANGELOG README Rakefile) +
29
30
  Dir.glob("{benchmark,bin,doc,example,lib,spec,tasks}/**/*") +
30
31
  Dir.glob("ext/**/*.{h,c,rb,rl}")
31
32
 
@@ -58,6 +59,11 @@ end
58
59
  task :gem => :tag_warn
59
60
 
60
61
  namespace :gem do
62
+ desc "Update the gemspec for GitHub's gem server"
63
+ task :github do
64
+ File.open("thin.gemspec", 'w') { |f| f << YAML.dump(spec) }
65
+ end
66
+
61
67
  desc 'Upload gem to code.macournoyer.com'
62
68
  task :upload => :gem do
63
69
  upload "pkg/#{spec.full_name}.gem", 'gems'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: thin
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Marc-Andre Cournoyer
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2008-09-28 00:00:00 -04:00
12
+ date: 2009-05-01 00:00:00 -04:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -20,7 +20,7 @@ dependencies:
20
20
  requirements:
21
21
  - - ">="
22
22
  - !ruby/object:Gem::Version
23
- version: 0.3.0
23
+ version: 1.0.0
24
24
  version:
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: eventmachine
@@ -30,7 +30,7 @@ dependencies:
30
30
  requirements:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: 0.12.0
33
+ version: 0.12.6
34
34
  version:
35
35
  - !ruby/object:Gem::Dependency
36
36
  name: daemons
@@ -53,7 +53,6 @@ extra_rdoc_files: []
53
53
  files:
54
54
  - COPYING
55
55
  - CHANGELOG
56
- - COMMITTERS
57
56
  - README
58
57
  - Rakefile
59
58
  - benchmark/abc
@@ -61,6 +60,9 @@ files:
61
60
  - benchmark/runner
62
61
  - bin/thin
63
62
  - example/adapter.rb
63
+ - example/async_app.ru
64
+ - example/async_chat.ru
65
+ - example/async_tailer.ru
64
66
  - example/config.ru
65
67
  - example/monit_sockets
66
68
  - example/monit_unixsock
@@ -74,8 +76,6 @@ files:
74
76
  - lib/rack/adapter
75
77
  - lib/rack/adapter/loader.rb
76
78
  - lib/rack/adapter/rails.rb
77
- - lib/rack/handler
78
- - lib/rack/handler/thin.rb
79
79
  - lib/thin
80
80
  - lib/thin/backends
81
81
  - lib/thin/backends/base.rb
@@ -101,6 +101,7 @@ files:
101
101
  - lib/thin/statuses.rb
102
102
  - lib/thin/version.rb
103
103
  - lib/thin.rb
104
+ - lib/thin_parser.bundle
104
105
  - spec/backends
105
106
  - spec/backends/swiftiply_client_spec.rb
106
107
  - spec/backends/tcp_server_spec.rb
@@ -240,7 +241,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
240
241
  requirements: []
241
242
 
242
243
  rubyforge_project: thin
243
- rubygems_version: 1.2.0
244
+ rubygems_version: 1.3.1
244
245
  signing_key:
245
246
  specification_version: 2
246
247
  summary: A thin and fast web server