unicorn 0.9.2 → 0.90.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/.gitignore +1 -0
- data/CHANGELOG +3 -0
- data/GNUmakefile +7 -7
- data/Manifest +20 -23
- data/README +16 -13
- data/TODO +5 -3
- data/bin/unicorn +1 -1
- data/bin/unicorn_rails +1 -1
- data/ext/unicorn_http/c_util.h +107 -0
- data/ext/unicorn_http/common_field_optimization.h +110 -0
- data/ext/unicorn_http/ext_help.h +41 -5
- data/ext/unicorn_http/extconf.rb +3 -1
- data/ext/unicorn_http/global_variables.h +93 -0
- data/ext/unicorn_http/unicorn_http.c +2123 -326
- data/ext/unicorn_http/unicorn_http.rl +488 -87
- data/ext/unicorn_http/unicorn_http_common.rl +12 -1
- data/lib/unicorn.rb +0 -2
- data/lib/unicorn/app/exec_cgi.rb +0 -1
- data/lib/unicorn/app/inetd.rb +2 -0
- data/lib/unicorn/app/old_rails/static.rb +0 -2
- data/lib/unicorn/const.rb +1 -5
- data/lib/unicorn/http_request.rb +13 -29
- data/lib/unicorn/http_response.rb +1 -1
- data/lib/unicorn/tee_input.rb +48 -46
- data/lib/unicorn/util.rb +3 -3
- data/test/benchmark/README +0 -5
- data/test/exec/test_exec.rb +4 -0
- data/test/rails/{app-2.3.2.1 → app-2.3.3.1}/.gitignore +0 -0
- data/test/rails/{app-2.3.2.1 → app-2.3.3.1}/Rakefile +0 -0
- data/test/rails/{app-2.3.2.1 → app-2.3.3.1}/app/controllers/application_controller.rb +0 -0
- data/test/rails/{app-2.3.2.1 → app-2.3.3.1}/app/controllers/foo_controller.rb +0 -0
- data/test/rails/{app-2.3.2.1 → app-2.3.3.1}/app/helpers/application_helper.rb +0 -0
- data/test/rails/{app-2.3.2.1 → app-2.3.3.1}/config/boot.rb +0 -0
- data/test/rails/{app-2.3.2.1 → app-2.3.3.1}/config/database.yml +0 -0
- data/test/rails/{app-2.3.2.1 → app-2.3.3.1}/config/environment.rb +0 -0
- data/test/rails/{app-2.3.2.1 → app-2.3.3.1}/config/environments/development.rb +0 -0
- data/test/rails/{app-2.3.2.1 → app-2.3.3.1}/config/environments/production.rb +0 -0
- data/test/rails/{app-2.3.2.1 → app-2.3.3.1}/config/routes.rb +0 -0
- data/test/rails/{app-2.3.2.1 → app-2.3.3.1}/db/.gitignore +0 -0
- data/test/rails/{app-2.3.2.1 → app-2.3.3.1}/public/404.html +0 -0
- data/test/rails/{app-2.3.2.1 → app-2.3.3.1}/public/500.html +0 -0
- data/test/unit/test_http_parser.rb +112 -47
- data/test/unit/test_http_parser_ng.rb +284 -0
- data/test/unit/test_request.rb +25 -7
- data/test/unit/test_response.rb +11 -0
- data/test/unit/test_server.rb +7 -2
- data/test/unit/test_signals.rb +2 -0
- data/test/unit/test_tee_input.rb +118 -2
- data/test/unit/test_upload.rb +1 -1
- data/test/unit/test_util.rb +5 -0
- data/unicorn.gemspec +6 -6
- metadata +33 -37
- data/ext/unicorn_http/unicorn_http.h +0 -1289
- data/lib/unicorn/chunked_reader.rb +0 -77
- data/lib/unicorn/trailer_parser.rb +0 -52
- data/test/benchmark/big_request.rb +0 -44
- data/test/benchmark/request.rb +0 -56
- data/test/benchmark/response.rb +0 -30
- data/test/unit/test_chunked_reader.rb +0 -123
- data/test/unit/test_trailer_parser.rb +0 -52
@@ -1,77 +0,0 @@
|
|
1
|
-
# Copyright (c) 2009 Eric Wong
|
2
|
-
# You can redistribute it and/or modify it under the same terms as Ruby.
|
3
|
-
|
4
|
-
require 'unicorn'
|
5
|
-
require 'unicorn_http'
|
6
|
-
|
7
|
-
module Unicorn
|
8
|
-
class ChunkedReader
|
9
|
-
|
10
|
-
def initialize(env, input, buf)
|
11
|
-
@env, @input, @buf = env, input, buf
|
12
|
-
@chunk_left = 0
|
13
|
-
parse_chunk_header
|
14
|
-
end
|
15
|
-
|
16
|
-
def readpartial(max, buf = Z.dup)
|
17
|
-
while @input && @chunk_left <= 0 && ! parse_chunk_header
|
18
|
-
@buf << @input.readpartial(Const::CHUNK_SIZE, buf)
|
19
|
-
end
|
20
|
-
|
21
|
-
if @input
|
22
|
-
begin
|
23
|
-
@buf << @input.read_nonblock(Const::CHUNK_SIZE, buf)
|
24
|
-
rescue Errno::EAGAIN, Errno::EINTR
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
max = @chunk_left if max > @chunk_left
|
29
|
-
buf.replace(last_block(max) || Z)
|
30
|
-
@chunk_left -= buf.size
|
31
|
-
(0 == buf.size && @input.nil?) and raise EOFError
|
32
|
-
buf
|
33
|
-
end
|
34
|
-
|
35
|
-
private
|
36
|
-
|
37
|
-
def last_block(max = nil)
|
38
|
-
rv = @buf
|
39
|
-
if max && rv && max < rv.size
|
40
|
-
@buf = rv[max - rv.size, rv.size - max]
|
41
|
-
return rv[0, max]
|
42
|
-
end
|
43
|
-
@buf = Z.dup
|
44
|
-
rv
|
45
|
-
end
|
46
|
-
|
47
|
-
def parse_chunk_header
|
48
|
-
buf = @buf
|
49
|
-
# ignoring chunk-extension info for now, I haven't seen any use for it
|
50
|
-
# (or any users, and TE:chunked sent by clients is rare already)
|
51
|
-
# if there was not enough data in buffer to parse length of the chunk
|
52
|
-
# then just return
|
53
|
-
if buf.sub!(/\A(?:\r\n)?([a-fA-F0-9]{1,8})[^\r]*?\r\n/, Z)
|
54
|
-
@chunk_left = $1.to_i(16)
|
55
|
-
if 0 == @chunk_left # EOF
|
56
|
-
buf.sub!(/\A\r\n(?:\r\n)?/, Z) # cleanup for future requests
|
57
|
-
if trailer = @env[Const::HTTP_TRAILER]
|
58
|
-
tp = TrailerParser.new(trailer)
|
59
|
-
while ! tp.execute!(@env, buf)
|
60
|
-
buf << @input.readpartial(Const::CHUNK_SIZE)
|
61
|
-
end
|
62
|
-
end
|
63
|
-
@input = nil
|
64
|
-
end
|
65
|
-
return @chunk_left
|
66
|
-
end
|
67
|
-
|
68
|
-
buf.size > 256 and
|
69
|
-
raise HttpParserError,
|
70
|
-
"malformed chunk, chunk-length not found in buffer: " \
|
71
|
-
"#{buf.inspect}"
|
72
|
-
nil
|
73
|
-
end
|
74
|
-
|
75
|
-
end
|
76
|
-
|
77
|
-
end
|
@@ -1,52 +0,0 @@
|
|
1
|
-
# Copyright (c) 2009 Eric Wong
|
2
|
-
# You can redistribute it and/or modify it under the same terms as Ruby.
|
3
|
-
require 'unicorn'
|
4
|
-
require 'unicorn_http'
|
5
|
-
|
6
|
-
# Eventually I should integrate this into HttpParser...
|
7
|
-
module Unicorn
|
8
|
-
class TrailerParser
|
9
|
-
|
10
|
-
TR_FR = 'a-z-'.freeze
|
11
|
-
TR_TO = 'A-Z_'.freeze
|
12
|
-
|
13
|
-
# initializes HTTP trailer parser with acceptable +trailer+
|
14
|
-
def initialize(http_trailer)
|
15
|
-
@trailers = http_trailer.split(/\s*,\s*/).inject({}) { |hash, key|
|
16
|
-
hash[key.tr(TR_FR, TR_TO)] = true
|
17
|
-
hash
|
18
|
-
}
|
19
|
-
end
|
20
|
-
|
21
|
-
# Executes our TrailerParser on +data+ and modifies +env+ This will
|
22
|
-
# shrink +data+ as it is being consumed. Returns true if it has
|
23
|
-
# parsed all trailers, false if not. It raises HttpParserError on
|
24
|
-
# parse failure or unknown headers. It has slightly smaller limits
|
25
|
-
# than the C-based HTTP parser but should not be an issue in practice
|
26
|
-
# since Content-MD5 is probably the only legitimate use for it.
|
27
|
-
def execute!(env, data)
|
28
|
-
data.size > 0xffff and
|
29
|
-
raise HttpParserError, "trailer buffer too large: #{data.size} bytes"
|
30
|
-
|
31
|
-
begin
|
32
|
-
data.sub!(/\A([^\r]+)\r\n/, Z) or return false # need more data
|
33
|
-
|
34
|
-
key, val = $1.split(/:\s*/, 2)
|
35
|
-
|
36
|
-
key.size > 256 and
|
37
|
-
raise HttpParserError, "trailer key #{key.inspect} is too long"
|
38
|
-
val.size > 8192 and
|
39
|
-
raise HttpParserError, "trailer value #{val.inspect} is too long"
|
40
|
-
|
41
|
-
key.tr!(TR_FR, TR_TO)
|
42
|
-
|
43
|
-
@trailers.delete(key) or
|
44
|
-
raise HttpParserError, "unknown trailer: #{key.inspect}"
|
45
|
-
env["HTTP_#{key}"] = val
|
46
|
-
|
47
|
-
@trailers.empty? and return true
|
48
|
-
end while true
|
49
|
-
end
|
50
|
-
|
51
|
-
end
|
52
|
-
end
|
@@ -1,44 +0,0 @@
|
|
1
|
-
require 'benchmark'
|
2
|
-
require 'tempfile'
|
3
|
-
require 'unicorn'
|
4
|
-
nr = ENV['nr'] ? ENV['nr'].to_i : 100
|
5
|
-
bs = ENV['bs'] ? ENV['bs'].to_i : (1024 * 1024)
|
6
|
-
count = ENV['count'] ? ENV['count'].to_i : 4
|
7
|
-
length = bs * count
|
8
|
-
slice = (' ' * bs).freeze
|
9
|
-
|
10
|
-
big = Tempfile.new('')
|
11
|
-
|
12
|
-
def big.unicorn_peeraddr # old versions of Unicorn used this
|
13
|
-
'127.0.0.1'
|
14
|
-
end
|
15
|
-
|
16
|
-
big.syswrite(
|
17
|
-
"PUT /hello/world/puturl?abcd=efg&hi#anchor HTTP/1.0\r\n" \
|
18
|
-
"Host: localhost\r\n" \
|
19
|
-
"Accept: */*\r\n" \
|
20
|
-
"Content-Length: #{length}\r\n" \
|
21
|
-
"User-Agent: test-user-agent 0.1.0 (Mozilla compatible) 5.0 asdfadfasda\r\n" \
|
22
|
-
"\r\n")
|
23
|
-
count.times { big.syswrite(slice) }
|
24
|
-
big.sysseek(0)
|
25
|
-
big.fsync
|
26
|
-
|
27
|
-
include Unicorn
|
28
|
-
request = HttpRequest.new(Logger.new($stderr))
|
29
|
-
unless request.respond_to?(:reset)
|
30
|
-
def request.reset
|
31
|
-
# no-op
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
Benchmark.bmbm do |x|
|
36
|
-
x.report("big") do
|
37
|
-
for i in 1..nr
|
38
|
-
request.read(big)
|
39
|
-
request.reset
|
40
|
-
big.sysseek(0)
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
data/test/benchmark/request.rb
DELETED
@@ -1,56 +0,0 @@
|
|
1
|
-
require 'benchmark'
|
2
|
-
require 'unicorn'
|
3
|
-
nr = ENV['nr'] ? ENV['nr'].to_i : 100000
|
4
|
-
|
5
|
-
class TestClient
|
6
|
-
def initialize(response)
|
7
|
-
@response = (response.join("\r\n") << "\r\n\r\n").freeze
|
8
|
-
end
|
9
|
-
def sysread(len, buf)
|
10
|
-
buf.replace(@response)
|
11
|
-
end
|
12
|
-
|
13
|
-
alias readpartial sysread
|
14
|
-
|
15
|
-
# old versions of Unicorn used this
|
16
|
-
def unicorn_peeraddr
|
17
|
-
'127.0.0.1'
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
small = TestClient.new([
|
22
|
-
'GET / HTTP/1.0',
|
23
|
-
'Host: localhost',
|
24
|
-
'Accept: */*',
|
25
|
-
'User-Agent: test-user-agent 0.1.0'
|
26
|
-
])
|
27
|
-
|
28
|
-
medium = TestClient.new([
|
29
|
-
'GET /hello/world/geturl?abcd=efg&hi#anchor HTTP/1.0',
|
30
|
-
'Host: localhost',
|
31
|
-
'Accept: */*',
|
32
|
-
'User-Agent: test-user-agent 0.1.0 (Mozilla compatible) 5.0 asdfadfasda'
|
33
|
-
])
|
34
|
-
|
35
|
-
include Unicorn
|
36
|
-
request = HttpRequest.new(Logger.new($stderr))
|
37
|
-
unless request.respond_to?(:reset)
|
38
|
-
def request.reset
|
39
|
-
# no-op
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
Benchmark.bmbm do |x|
|
44
|
-
x.report("small") do
|
45
|
-
for i in 1..nr
|
46
|
-
request.read(small)
|
47
|
-
request.reset
|
48
|
-
end
|
49
|
-
end
|
50
|
-
x.report("medium") do
|
51
|
-
for i in 1..nr
|
52
|
-
request.read(medium)
|
53
|
-
request.reset
|
54
|
-
end
|
55
|
-
end
|
56
|
-
end
|
data/test/benchmark/response.rb
DELETED
@@ -1,30 +0,0 @@
|
|
1
|
-
require 'benchmark'
|
2
|
-
require 'unicorn'
|
3
|
-
|
4
|
-
class NullWriter
|
5
|
-
def syswrite(buf); buf.size; end
|
6
|
-
alias write syswrite
|
7
|
-
def close; end
|
8
|
-
end
|
9
|
-
|
10
|
-
include Unicorn
|
11
|
-
|
12
|
-
socket = NullWriter.new
|
13
|
-
bs = ENV['bs'] ? ENV['bs'].to_i : 4096
|
14
|
-
count = ENV['count'] ? ENV['count'].to_i : 1
|
15
|
-
slice = (' ' * bs).freeze
|
16
|
-
body = (1..count).map { slice }.freeze
|
17
|
-
hdr = {
|
18
|
-
'Content-Length' => (bs * count).to_s.freeze,
|
19
|
-
'Content-Type' => 'text/plain'.freeze
|
20
|
-
}.freeze
|
21
|
-
response = [ 200, hdr, body ].freeze
|
22
|
-
|
23
|
-
nr = ENV['nr'] ? ENV['nr'].to_i : 100000
|
24
|
-
Benchmark.bmbm do |x|
|
25
|
-
x.report do
|
26
|
-
for i in 1..nr
|
27
|
-
HttpResponse.write(socket.dup, response)
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
@@ -1,123 +0,0 @@
|
|
1
|
-
require 'test/unit'
|
2
|
-
require 'unicorn'
|
3
|
-
require 'unicorn_http'
|
4
|
-
require 'tempfile'
|
5
|
-
require 'io/nonblock'
|
6
|
-
require 'digest/sha1'
|
7
|
-
|
8
|
-
class TestChunkedReader < Test::Unit::TestCase
|
9
|
-
|
10
|
-
def setup
|
11
|
-
@env = {}
|
12
|
-
@rd, @wr = IO.pipe
|
13
|
-
@rd.binmode
|
14
|
-
@wr.binmode
|
15
|
-
@rd.sync = @wr.sync = true
|
16
|
-
@start_pid = $$
|
17
|
-
end
|
18
|
-
|
19
|
-
def teardown
|
20
|
-
return if $$ != @start_pid
|
21
|
-
@rd.close rescue nil
|
22
|
-
@wr.close rescue nil
|
23
|
-
begin
|
24
|
-
Process.wait
|
25
|
-
rescue Errno::ECHILD
|
26
|
-
break
|
27
|
-
end while true
|
28
|
-
end
|
29
|
-
|
30
|
-
def test_error
|
31
|
-
cr = bin_reader("8\r\nasdfasdf\r\n8\r\nasdfasdfa#{'a' * 1024}")
|
32
|
-
a = nil
|
33
|
-
assert_nothing_raised { a = cr.readpartial(8192) }
|
34
|
-
assert_equal 'asdfasdf', a
|
35
|
-
assert_nothing_raised { a = cr.readpartial(8192) }
|
36
|
-
assert_equal 'asdfasdf', a
|
37
|
-
assert_raises(Unicorn::HttpParserError) { cr.readpartial(8192) }
|
38
|
-
end
|
39
|
-
|
40
|
-
def test_eof1
|
41
|
-
cr = bin_reader("0\r\n")
|
42
|
-
assert_raises(EOFError) { cr.readpartial(8192) }
|
43
|
-
end
|
44
|
-
|
45
|
-
def test_eof2
|
46
|
-
cr = bin_reader("0\r\n\r\n")
|
47
|
-
assert_raises(EOFError) { cr.readpartial(8192) }
|
48
|
-
end
|
49
|
-
|
50
|
-
def test_readpartial1
|
51
|
-
cr = bin_reader("4\r\nasdf\r\n0\r\n")
|
52
|
-
assert_equal 'asdf', cr.readpartial(8192)
|
53
|
-
assert_raises(EOFError) { cr.readpartial(8192) }
|
54
|
-
end
|
55
|
-
|
56
|
-
def test_dd
|
57
|
-
cr = bin_reader("6\r\nhello\n\r\n")
|
58
|
-
tmp = Tempfile.new('test_dd')
|
59
|
-
tmp.sync = true
|
60
|
-
|
61
|
-
pid = fork {
|
62
|
-
crd, cwr = IO.pipe
|
63
|
-
crd.binmode
|
64
|
-
cwr.binmode
|
65
|
-
crd.sync = cwr.sync = true
|
66
|
-
|
67
|
-
pid = fork {
|
68
|
-
STDOUT.reopen(cwr)
|
69
|
-
crd.close
|
70
|
-
cwr.close
|
71
|
-
exec('dd', 'if=/dev/urandom', 'bs=93390', 'count=16')
|
72
|
-
}
|
73
|
-
cwr.close
|
74
|
-
begin
|
75
|
-
buf = crd.readpartial(16384)
|
76
|
-
tmp.write(buf)
|
77
|
-
@wr.write("#{'%x' % buf.size}\r\n#{buf}\r\n")
|
78
|
-
rescue EOFError
|
79
|
-
@wr.write("0\r\n\r\n")
|
80
|
-
Process.waitpid(pid)
|
81
|
-
exit 0
|
82
|
-
end while true
|
83
|
-
}
|
84
|
-
assert_equal "hello\n", cr.readpartial(6)
|
85
|
-
sha1 = Digest::SHA1.new
|
86
|
-
buf = Unicorn::Z.dup
|
87
|
-
begin
|
88
|
-
cr.readpartial(16384, buf)
|
89
|
-
sha1.update(buf)
|
90
|
-
rescue EOFError
|
91
|
-
break
|
92
|
-
end while true
|
93
|
-
|
94
|
-
assert_nothing_raised { Process.waitpid(pid) }
|
95
|
-
sha1_file = Digest::SHA1.new
|
96
|
-
File.open(tmp.path, 'rb') { |fp|
|
97
|
-
while fp.read(16384, buf)
|
98
|
-
sha1_file.update(buf)
|
99
|
-
end
|
100
|
-
}
|
101
|
-
assert_equal sha1_file.hexdigest, sha1.hexdigest
|
102
|
-
end
|
103
|
-
|
104
|
-
def test_trailer
|
105
|
-
@env['HTTP_TRAILER'] = 'Content-MD5'
|
106
|
-
pid = fork { @wr.syswrite("Content-MD5: asdf\r\n") }
|
107
|
-
cr = bin_reader("8\r\nasdfasdf\r\n8\r\nasdfasdf\r\n0\r\n")
|
108
|
-
assert_equal 'asdfasdf', cr.readpartial(4096)
|
109
|
-
assert_equal 'asdfasdf', cr.readpartial(4096)
|
110
|
-
assert_raises(EOFError) { cr.readpartial(4096) }
|
111
|
-
pid, status = Process.waitpid2(pid)
|
112
|
-
assert status.success?
|
113
|
-
assert_equal 'asdf', @env['HTTP_CONTENT_MD5']
|
114
|
-
end
|
115
|
-
|
116
|
-
private
|
117
|
-
|
118
|
-
def bin_reader(buf)
|
119
|
-
buf.force_encoding(Encoding::BINARY) if buf.respond_to?(:force_encoding)
|
120
|
-
Unicorn::ChunkedReader.new(@env, @rd, buf)
|
121
|
-
end
|
122
|
-
|
123
|
-
end
|
@@ -1,52 +0,0 @@
|
|
1
|
-
require 'test/unit'
|
2
|
-
require 'unicorn'
|
3
|
-
require 'unicorn_http'
|
4
|
-
require 'unicorn/trailer_parser'
|
5
|
-
|
6
|
-
class TestTrailerParser < Test::Unit::TestCase
|
7
|
-
|
8
|
-
def test_basic
|
9
|
-
tp = Unicorn::TrailerParser.new('Content-MD5')
|
10
|
-
env = {}
|
11
|
-
assert ! tp.execute!(env, "Content-MD5: asdf")
|
12
|
-
assert env.empty?
|
13
|
-
assert tp.execute!(env, "Content-MD5: asdf\r\n")
|
14
|
-
assert_equal 'asdf', env['HTTP_CONTENT_MD5']
|
15
|
-
assert_equal 1, env.size
|
16
|
-
end
|
17
|
-
|
18
|
-
def test_invalid_trailer
|
19
|
-
tp = Unicorn::TrailerParser.new('Content-MD5')
|
20
|
-
env = {}
|
21
|
-
assert_raises(Unicorn::HttpParserError) {
|
22
|
-
tp.execute!(env, "Content-MD: asdf\r\n")
|
23
|
-
}
|
24
|
-
assert env.empty?
|
25
|
-
end
|
26
|
-
|
27
|
-
def test_multiple_trailer
|
28
|
-
tp = Unicorn::TrailerParser.new('Foo,Bar')
|
29
|
-
env = {}
|
30
|
-
buf = "Bar: a\r\nFoo: b\r\n"
|
31
|
-
assert tp.execute!(env, buf)
|
32
|
-
assert_equal 'a', env['HTTP_BAR']
|
33
|
-
assert_equal 'b', env['HTTP_FOO']
|
34
|
-
end
|
35
|
-
|
36
|
-
def test_too_big_key
|
37
|
-
tp = Unicorn::TrailerParser.new('Foo,Bar')
|
38
|
-
env = {}
|
39
|
-
buf = "Bar#{'a' * 1024}: a\r\nFoo: b\r\n"
|
40
|
-
assert_raises(Unicorn::HttpParserError) { tp.execute!(env, buf) }
|
41
|
-
assert env.empty?
|
42
|
-
end
|
43
|
-
|
44
|
-
def test_too_big_value
|
45
|
-
tp = Unicorn::TrailerParser.new('Foo,Bar')
|
46
|
-
env = {}
|
47
|
-
buf = "Bar: #{'a' * (1024 * 1024)}: a\r\nFoo: b\r\n"
|
48
|
-
assert_raises(Unicorn::HttpParserError) { tp.execute!(env, buf) }
|
49
|
-
assert env.empty?
|
50
|
-
end
|
51
|
-
|
52
|
-
end
|