unicorn 0.92.0 → 0.93.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/unicorn/const.rb CHANGED
@@ -7,10 +7,10 @@ module Unicorn
7
7
  # gave about a 3% to 10% performance improvement over using the strings directly.
8
8
  # Symbols did not really improve things much compared to constants.
9
9
  module Const
10
- UNICORN_VERSION="0.92.0"
10
+ UNICORN_VERSION="0.93.0"
11
11
 
12
12
  DEFAULT_HOST = "0.0.0.0" # default TCP listen host address
13
- DEFAULT_PORT = "8080" # default TCP listen port
13
+ DEFAULT_PORT = 8080 # default TCP listen port
14
14
  DEFAULT_LISTEN = "#{DEFAULT_HOST}:#{DEFAULT_PORT}"
15
15
 
16
16
  # The basic max request size we'll try to read.
@@ -19,7 +19,7 @@ module Unicorn
19
19
  "SERVER_SOFTWARE" => "Unicorn #{Const::UNICORN_VERSION}"
20
20
  }
21
21
 
22
- NULL_IO = StringIO.new(Z)
22
+ NULL_IO = StringIO.new("")
23
23
  LOCALHOST = '127.0.0.1'
24
24
 
25
25
  # Being explicitly single-threaded, we have certain advantages in
@@ -56,24 +56,15 @@ module Unicorn
56
56
  TCPSocket === socket ? socket.peeraddr.last : LOCALHOST
57
57
 
58
58
  # short circuit the common case with small GET requests first
59
- PARSER.headers(REQ, socket.readpartial(Const::CHUNK_SIZE, BUF)) and
60
- return handle_body(socket)
59
+ if PARSER.headers(REQ, socket.readpartial(Const::CHUNK_SIZE, BUF)).nil?
60
+ data = BUF.dup # socket.readpartial will clobber data
61
61
 
62
- data = BUF.dup # socket.readpartial will clobber data
63
-
64
- # Parser is not done, queue up more data to read and continue parsing
65
- # an Exception thrown from the PARSER will throw us out of the loop
66
- begin
67
- BUF << socket.readpartial(Const::CHUNK_SIZE, data)
68
- PARSER.headers(REQ, BUF) and return handle_body(socket)
69
- end while true
70
- end
71
-
72
- private
73
-
74
- # Handles dealing with the rest of the request
75
- # returns a # Rack environment if successful
76
- def handle_body(socket)
62
+ # Parser is not done, queue up more data to read and continue parsing
63
+ # an Exception thrown from the PARSER will throw us out of the loop
64
+ begin
65
+ BUF << socket.readpartial(Const::CHUNK_SIZE, data)
66
+ end while PARSER.headers(REQ, BUF).nil?
67
+ end
77
68
  REQ[Const::RACK_INPUT] = 0 == PARSER.content_length ?
78
69
  NULL_IO : Unicorn::TeeInput.new(socket, REQ, PARSER, BUF)
79
70
  REQ.update(DEFAULTS)
@@ -33,39 +33,37 @@ module Unicorn
33
33
  # Connection: and Date: headers no matter what (if anything) our
34
34
  # Rack application sent us.
35
35
  SKIP = { 'connection' => true, 'date' => true, 'status' => true }
36
- OUT = [] # :nodoc
37
36
 
38
- def self.write_header(socket, status, headers)
39
- status = CODES[status.to_i] || status
40
- OUT.clear
37
+ # writes the rack_response to socket as an HTTP response
38
+ def self.write(socket, rack_response, have_header = true)
39
+ status, headers, body = rack_response
41
40
 
42
- # Don't bother enforcing duplicate supression, it's a Hash most of
43
- # the time anyways so just hope our app knows what it's doing
44
- headers.each do |key, value|
45
- next if SKIP.include?(key.downcase)
46
- if value =~ /\n/
47
- value.split(/\n/).each { |v| OUT << "#{key}: #{v}\r\n" }
48
- else
49
- OUT << "#{key}: #{value}\r\n"
50
- end
51
- end
41
+ if have_header
42
+ status = CODES[status.to_i] || status
43
+ out = []
52
44
 
53
- # Rack should enforce Content-Length or chunked transfer encoding,
54
- # so don't worry or care about them.
55
- # Date is required by HTTP/1.1 as long as our clock can be trusted.
56
- # Some broken clients require a "Status" header so we accomodate them
57
- socket.write("HTTP/1.1 #{status}\r\n" \
58
- "Date: #{Time.now.httpdate}\r\n" \
59
- "Status: #{status}\r\n" \
60
- "Connection: close\r\n" \
61
- "#{OUT.join(Z)}\r\n")
45
+ # Don't bother enforcing duplicate supression, it's a Hash most of
46
+ # the time anyways so just hope our app knows what it's doing
47
+ headers.each do |key, value|
48
+ next if SKIP.include?(key.downcase)
49
+ if value =~ /\n/
50
+ out.concat(value.split(/\n/).map! { |v| "#{key}: #{v}\r\n" })
51
+ else
52
+ out << "#{key}: #{value}\r\n"
53
+ end
54
+ end
62
55
 
63
- end
56
+ # Rack should enforce Content-Length or chunked transfer encoding,
57
+ # so don't worry or care about them.
58
+ # Date is required by HTTP/1.1 as long as our clock can be trusted.
59
+ # Some broken clients require a "Status" header so we accomodate them
60
+ socket.write("HTTP/1.1 #{status}\r\n" \
61
+ "Date: #{Time.now.httpdate}\r\n" \
62
+ "Status: #{status}\r\n" \
63
+ "Connection: close\r\n" \
64
+ "#{out.join('')}\r\n")
65
+ end
64
66
 
65
- # writes the rack_response to socket as an HTTP response
66
- def self.write(socket, rack_response, have_header = true)
67
- status, headers, body = rack_response
68
- write_header(socket, status, headers) if have_header
69
67
  body.each { |chunk| socket.write(chunk) }
70
68
  socket.close # flushes and uncorks the socket immediately
71
69
  ensure
@@ -76,7 +76,7 @@ module Unicorn
76
76
  def bind_listen(address = '0.0.0.0:8080', opt = {})
77
77
  return address unless String === address
78
78
 
79
- sock = if address[0..0] == "/"
79
+ sock = if address[0] == ?/
80
80
  if File.exist?(address)
81
81
  if File.socket?(address)
82
82
  if self.respond_to?(:logger)
@@ -15,7 +15,7 @@ module Unicorn
15
15
  def initialize(*args)
16
16
  super(*args)
17
17
  @size = parser.content_length
18
- @tmp = @size && @size < Const::MAX_BODY ? StringIO.new(Z.dup) : Util.tmpio
18
+ @tmp = @size && @size < Const::MAX_BODY ? StringIO.new("") : Util.tmpio
19
19
  @buf2 = buf.dup
20
20
  if buf.size > 0
21
21
  parser.filter_body(@buf2, buf) and finalize_input
@@ -46,7 +46,7 @@ module Unicorn
46
46
 
47
47
  length = args.shift
48
48
  if nil == length
49
- rv = @tmp.read || Z.dup
49
+ rv = @tmp.read || ""
50
50
  while tee(Const::CHUNK_SIZE, @buf2)
51
51
  rv << @buf2
52
52
  end
data/lib/unicorn/util.rb CHANGED
@@ -7,8 +7,6 @@ module Unicorn
7
7
  class Util
8
8
  class << self
9
9
 
10
- APPEND_FLAGS = File::WRONLY | File::APPEND
11
-
12
10
  # This reopens ALL logfiles in the process that have been rotated
13
11
  # using logrotate(8) (without copytruncate) or similar tools.
14
12
  # A +File+ object is considered for reopening if it is:
@@ -19,10 +17,12 @@ module Unicorn
19
17
  # Returns the number of files reopened
20
18
  def reopen_logs
21
19
  nr = 0
20
+ append_flags = File::WRONLY | File::APPEND
21
+
22
22
  ObjectSpace.each_object(File) do |fp|
23
23
  next if fp.closed?
24
24
  next unless (fp.sync && fp.path[0..0] == "/")
25
- next unless (fp.fcntl(Fcntl::F_GETFL) & APPEND_FLAGS) == APPEND_FLAGS
25
+ next unless (fp.fcntl(Fcntl::F_GETFL) & append_flags) == append_flags
26
26
 
27
27
  begin
28
28
  a, b = fp.stat, File.stat(fp.path)
data/local.mk.sample CHANGED
@@ -27,12 +27,19 @@ test-18:
27
27
  test-19:
28
28
  $(MAKE) test test-rails r19=1 2>&1 | sed -u -e 's!^!1.9 !'
29
29
 
30
+ latest: NEWS
31
+ @awk 'BEGIN{RS="=== ";ORS=""}NR==2{sub(/\n$$/,"");print RS""$$0 }' < $<
32
+
30
33
  # publishes docs to http://unicorn.bogomips.org
31
34
  publish_doc:
32
35
  -git set-file-times
36
+ $(RM) -r doc
33
37
  $(MAKE) doc
34
- awk 'BEGIN{RS="=== ";ORS=""}NR==2{ print RS""$$0 }' NEWS > doc/LATEST
38
+ $(MAKE) -s latest > doc/LATEST
39
+ find doc/images doc/js -type f | \
40
+ TZ=UTC xargs touch -d '1970-01-01 00:00:00' doc/rdoc.css
35
41
  $(MAKE) doc_gz
42
+ chmod 644 $$(find doc -type f)
36
43
  rsync -av --delete doc/ dcvr:/srv/unicorn/
37
44
  git ls-files | xargs touch
38
45
 
@@ -728,7 +728,7 @@ end
728
728
  }
729
729
  }
730
730
  sleep 1 # racy
731
- daemon_pid = pid_file.read.to_i
731
+ daemon_pid = File.read(pid_file.path).to_i
732
732
  assert daemon_pid > 0
733
733
  Process.kill(:HUP, daemon_pid)
734
734
  sleep 1 # racy
@@ -0,0 +1 @@
1
+ HELLO
@@ -231,6 +231,31 @@ logger Logger.new('#{COMMON_TMP.path}')
231
231
  assert_equal '404 Not Found', res['Status']
232
232
  end
233
233
 
234
+ def test_alt_url_root_config_env
235
+ # cbf to actually work on this since I never use this feature (ewong)
236
+ return unless ROR_V[0] >= 2 && ROR_V[1] >= 3
237
+ tmp = Tempfile.new(nil)
238
+ tmp.syswrite("ENV['RAILS_RELATIVE_URL_ROOT'] = '/poo'\n")
239
+ redirect_test_io do
240
+ @pid = fork { exec 'unicorn_rails', "-l#@addr:#@port", "-c", tmp.path }
241
+ end
242
+ wait_master_ready("test_stderr.#$$.log")
243
+ res = Net::HTTP.get_response(URI.parse("http://#@addr:#@port/poo/foo"))
244
+ assert_equal "200", res.code
245
+ assert_equal '200 OK', res['Status']
246
+ assert_equal "FOO\n", res.body
247
+ assert_match %r{^text/html\b}, res['Content-Type']
248
+ assert_equal "4", res['Content-Length']
249
+
250
+ res = Net::HTTP.get_response(URI.parse("http://#@addr:#@port/foo"))
251
+ assert_equal "404", res.code
252
+ assert_equal '404 Not Found', res['Status']
253
+
254
+ res = Net::HTTP.get_response(URI.parse("http://#@addr:#@port/poo/x.txt"))
255
+ assert_equal "200", res.code
256
+ assert_equal "HELLO\n", res.body
257
+ end
258
+
234
259
  def teardown
235
260
  return if @start_pid != $$
236
261
 
data/test/test_helper.rb CHANGED
@@ -104,10 +104,8 @@ def unused_port(addr = '127.0.0.1')
104
104
  begin
105
105
  begin
106
106
  port = base + rand(32768 - base)
107
- if addr == Unicorn::Const::DEFAULT_HOST
108
- while port == Unicorn::Const::DEFAULT_PORT
109
- port = base + rand(32768 - base)
110
- end
107
+ while port == Unicorn::Const::DEFAULT_PORT
108
+ port = base + rand(32768 - base)
111
109
  end
112
110
 
113
111
  sock = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
@@ -99,6 +99,36 @@ class TestConfigurator < Test::Unit::TestCase
99
99
  end
100
100
  end
101
101
 
102
+ def test_listen_option_bad_delay
103
+ tmp = Tempfile.new('unicorn_config')
104
+ expect = { :delay => "five" }
105
+ listener = "127.0.0.1:12345"
106
+ tmp.syswrite("listen '#{listener}', #{expect.inspect}\n")
107
+ assert_raises(ArgumentError) do
108
+ Unicorn::Configurator.new(:config_file => tmp.path)
109
+ end
110
+ end
111
+
112
+ def test_listen_option_float_delay
113
+ tmp = Tempfile.new('unicorn_config')
114
+ expect = { :delay => 0.5 }
115
+ listener = "127.0.0.1:12345"
116
+ tmp.syswrite("listen '#{listener}', #{expect.inspect}\n")
117
+ assert_nothing_raised do
118
+ Unicorn::Configurator.new(:config_file => tmp.path)
119
+ end
120
+ end
121
+
122
+ def test_listen_option_int_delay
123
+ tmp = Tempfile.new('unicorn_config')
124
+ expect = { :delay => 5 }
125
+ listener = "127.0.0.1:12345"
126
+ tmp.syswrite("listen '#{listener}', #{expect.inspect}\n")
127
+ assert_nothing_raised do
128
+ Unicorn::Configurator.new(:config_file => tmp.path)
129
+ end
130
+ end
131
+
102
132
  def test_after_fork_proc
103
133
  test_struct = TestStruct.new
104
134
  [ proc { |a,b| }, Proc.new { |a,b| }, lambda { |a,b| } ].each do |my_proc|
@@ -141,6 +141,7 @@ class SignalsTest < Test::Unit::TestCase
141
141
  pid = buf[/\r\nX-Pid: (\d+)\r\n/, 1].to_i
142
142
  header_len = buf[/\A(.+?\r\n\r\n)/m, 1].size
143
143
  end
144
+ assert pid > 0, "pid not positive: #{pid.inspect}"
144
145
  read = buf.size
145
146
  mode_before = @tmp.stat.mode
146
147
  assert_raises(EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,
@@ -177,6 +178,7 @@ class SignalsTest < Test::Unit::TestCase
177
178
  sock.close
178
179
  end
179
180
 
181
+ assert pid > 0, "pid not positive: #{pid.inspect}"
180
182
  sock = TCPSocket.new('127.0.0.1', @port)
181
183
  sock.syswrite("PUT / HTTP/1.0\r\n")
182
184
  sock.syswrite("Content-Length: #{@bs * @count}\r\n\r\n")
data/unicorn.gemspec CHANGED
@@ -3,19 +3,20 @@
3
3
  ENV["VERSION"] or abort "VERSION= must be specified"
4
4
  manifest = File.readlines('.manifest').map! { |x| x.chomp! }
5
5
 
6
+ # don't bother with tests that fork, not worth our time to get working
7
+ # with `gem check -t` ... (of course we care for them when testing with
8
+ # GNU make when they can run in parallel)
9
+ test_files = manifest.grep(%r{\Atest/unit/test_.*\.rb\z}).map do |f|
10
+ File.readlines(f).grep(/\bfork\b/).empty? ? f : nil
11
+ end.compact
12
+
6
13
  Gem::Specification.new do |s|
7
14
  s.name = %q{unicorn}
8
15
  s.version = ENV["VERSION"]
9
16
 
10
17
  s.authors = ["Eric Wong"]
11
18
  s.date = Time.now.utc.strftime('%Y-%m-%d')
12
-
13
- s.description = %q{
14
- HTTP server for Rack applications designed to take advantage of features
15
- in Unix/Unix-like operating systems and serve low-latency, high-bandwidth
16
- clients (such as a buffering reverse proxy server).
17
- }.strip
18
-
19
+ s.description = File.read("README").split(/\n\n/)[1]
19
20
  s.email = %q{mongrel-unicorn@rubyforge.org}
20
21
  s.executables = %w(unicorn unicorn_rails)
21
22
  s.extensions = %w(ext/unicorn_http/extconf.rb)
@@ -39,8 +40,10 @@ clients (such as a buffering reverse proxy server).
39
40
  s.require_paths = %w(lib ext)
40
41
  s.rubyforge_project = %q{mongrel}
41
42
  s.summary = %q{Rack HTTP server for Unix and fast clients}
42
- s.test_files = manifest.grep(%r{\Atest/unit/test_*\.rb\z})
43
+
44
+ s.test_files = test_files
43
45
 
44
46
  s.add_dependency(%q<rack>)
45
- s.licenses = %w(GPLv2 Ruby)
47
+
48
+ # s.licenses = %w(GPLv2 Ruby) # licenses= method is not in older Rubygems
46
49
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: unicorn
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.92.0
4
+ version: 0.93.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eric Wong
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-09-18 00:00:00 -07:00
12
+ date: 2009-10-02 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -23,9 +23,11 @@ dependencies:
23
23
  version: "0"
24
24
  version:
25
25
  description: |-
26
- HTTP server for Rack applications designed to take advantage of features
27
- in Unix/Unix-like operating systems and serve low-latency, high-bandwidth
28
- clients (such as a buffering reverse proxy server).
26
+ Unicorn is a HTTP server for Rack applications designed to take
27
+ advantage of features in Unix/Unix-like kernels and only serve fast
28
+ clients on low-latency, high-bandwidth connections. Slow clients should
29
+ only be served by placing a reverse proxy capable of fully-buffering
30
+ both the the request and response in between Unicorn and slow clients.
29
31
  email: mongrel-unicorn@rubyforge.org
30
32
  executables:
31
33
  - unicorn
@@ -36,10 +38,12 @@ extra_rdoc_files:
36
38
  - README
37
39
  - TUNING
38
40
  - PHILOSOPHY
41
+ - HACKING
39
42
  - DESIGN
40
43
  - CONTRIBUTORS
41
44
  - LICENSE
42
45
  - SIGNALS
46
+ - KNOWN_ISSUES
43
47
  - TODO
44
48
  - NEWS
45
49
  - ChangeLog
@@ -75,6 +79,8 @@ files:
75
79
  - GIT-VERSION-FILE
76
80
  - GIT-VERSION-GEN
77
81
  - GNUmakefile
82
+ - HACKING
83
+ - KNOWN_ISSUES
78
84
  - LICENSE
79
85
  - NEWS
80
86
  - PHILOSOPHY
@@ -111,6 +117,8 @@ files:
111
117
  - lib/unicorn/tee_input.rb
112
118
  - lib/unicorn/util.rb
113
119
  - local.mk.sample
120
+ - man/man1/unicorn.1
121
+ - man/man1/unicorn_rails.1
114
122
  - setup.rb
115
123
  - test/aggregate.rb
116
124
  - test/benchmark/README
@@ -192,6 +200,7 @@ files:
192
200
  - test/rails/app-2.3.3.1/log/.gitignore
193
201
  - test/rails/app-2.3.3.1/public/404.html
194
202
  - test/rails/app-2.3.3.1/public/500.html
203
+ - test/rails/app-2.3.3.1/public/x.txt
195
204
  - test/rails/test_rails.rb
196
205
  - test/test_helper.rb
197
206
  - test/unit/test_configurator.rb
@@ -208,9 +217,8 @@ files:
208
217
  - unicorn.gemspec
209
218
  has_rdoc: true
210
219
  homepage: http://unicorn.bogomips.org/
211
- licenses:
212
- - GPLv2
213
- - Ruby
220
+ licenses: []
221
+
214
222
  post_install_message:
215
223
  rdoc_options:
216
224
  - -Na
@@ -238,5 +246,11 @@ rubygems_version: 1.3.5
238
246
  signing_key:
239
247
  specification_version: 3
240
248
  summary: Rack HTTP server for Unix and fast clients
241
- test_files: []
242
-
249
+ test_files:
250
+ - test/unit/test_configurator.rb
251
+ - test/unit/test_http_parser.rb
252
+ - test/unit/test_http_parser_ng.rb
253
+ - test/unit/test_request.rb
254
+ - test/unit/test_response.rb
255
+ - test/unit/test_server.rb
256
+ - test/unit/test_util.rb