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/.document +2 -0
- data/.gitignore +1 -0
- data/Documentation/unicorn.1.txt +9 -0
- data/Documentation/unicorn_rails.1.txt +19 -0
- data/GIT-VERSION-GEN +1 -1
- data/GNUmakefile +37 -18
- data/HACKING +113 -0
- data/KNOWN_ISSUES +27 -0
- data/README +6 -22
- data/Rakefile +1 -3
- data/SIGNALS +4 -1
- data/TUNING +8 -1
- data/bin/unicorn_rails +12 -8
- data/ext/unicorn_http/extconf.rb +5 -3
- data/lib/unicorn.rb +53 -17
- data/lib/unicorn/app/exec_cgi.rb +4 -4
- data/lib/unicorn/app/inetd.rb +3 -4
- data/lib/unicorn/cgi_wrapper.rb +4 -10
- data/lib/unicorn/configurator.rb +102 -52
- data/lib/unicorn/const.rb +2 -2
- data/lib/unicorn/http_request.rb +9 -18
- data/lib/unicorn/http_response.rb +26 -28
- data/lib/unicorn/socket_helper.rb +1 -1
- data/lib/unicorn/tee_input.rb +2 -2
- data/lib/unicorn/util.rb +3 -3
- data/local.mk.sample +8 -1
- data/test/exec/test_exec.rb +1 -1
- data/test/rails/app-2.3.3.1/public/x.txt +1 -0
- data/test/rails/test_rails.rb +25 -0
- data/test/test_helper.rb +2 -4
- data/test/unit/test_configurator.rb +30 -0
- data/test/unit/test_signals.rb +2 -0
- data/unicorn.gemspec +12 -9
- metadata +24 -10
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.
|
10
|
+
UNICORN_VERSION="0.93.0"
|
11
11
|
|
12
12
|
DEFAULT_HOST = "0.0.0.0" # default TCP listen host address
|
13
|
-
DEFAULT_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.
|
data/lib/unicorn/http_request.rb
CHANGED
@@ -19,7 +19,7 @@ module Unicorn
|
|
19
19
|
"SERVER_SOFTWARE" => "Unicorn #{Const::UNICORN_VERSION}"
|
20
20
|
}
|
21
21
|
|
22
|
-
NULL_IO = StringIO.new(
|
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))
|
60
|
-
|
59
|
+
if PARSER.headers(REQ, socket.readpartial(Const::CHUNK_SIZE, BUF)).nil?
|
60
|
+
data = BUF.dup # socket.readpartial will clobber data
|
61
61
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
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
|
-
|
39
|
-
|
40
|
-
|
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
|
-
|
43
|
-
|
44
|
-
|
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
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
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
|
-
|
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
|
79
|
+
sock = if address[0] == ?/
|
80
80
|
if File.exist?(address)
|
81
81
|
if File.socket?(address)
|
82
82
|
if self.respond_to?(:logger)
|
data/lib/unicorn/tee_input.rb
CHANGED
@@ -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(
|
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 ||
|
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) &
|
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
|
-
|
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
|
|
data/test/exec/test_exec.rb
CHANGED
@@ -0,0 +1 @@
|
|
1
|
+
HELLO
|
data/test/rails/test_rails.rb
CHANGED
@@ -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
|
-
|
108
|
-
|
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|
|
data/test/unit/test_signals.rb
CHANGED
@@ -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
|
-
|
43
|
+
|
44
|
+
s.test_files = test_files
|
43
45
|
|
44
46
|
s.add_dependency(%q<rack>)
|
45
|
-
|
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.
|
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-
|
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
|
27
|
-
in Unix/Unix-like
|
28
|
-
clients
|
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
|
-
|
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
|