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