unicorn 0.9.2 → 0.90.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|