unicorn 0.92.0 → 0.93.0

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.
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