unicorn-maintained 6.2.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.
- checksums.yaml +7 -0
- data/.CHANGELOG.old +25 -0
- data/.document +28 -0
- data/.gitattributes +5 -0
- data/.gitignore +25 -0
- data/.mailmap +26 -0
- data/.manifest +149 -0
- data/.olddoc.yml +25 -0
- data/Application_Timeouts +77 -0
- data/CONTRIBUTORS +39 -0
- data/COPYING +674 -0
- data/DESIGN +99 -0
- data/Documentation/.gitignore +3 -0
- data/Documentation/unicorn.1 +222 -0
- data/Documentation/unicorn_rails.1 +207 -0
- data/FAQ +70 -0
- data/GIT-VERSION-FILE +1 -0
- data/GIT-VERSION-GEN +39 -0
- data/GNUmakefile +317 -0
- data/HACKING +112 -0
- data/ISSUES +102 -0
- data/KNOWN_ISSUES +79 -0
- data/LATEST +1 -0
- data/LICENSE +67 -0
- data/Links +58 -0
- data/NEWS +1 -0
- data/PHILOSOPHY +139 -0
- data/README +156 -0
- data/Rakefile +16 -0
- data/SIGNALS +123 -0
- data/Sandbox +104 -0
- data/TODO +3 -0
- data/TUNING +119 -0
- data/archive/.gitignore +3 -0
- data/archive/slrnpull.conf +4 -0
- data/bin/unicorn +128 -0
- data/bin/unicorn_rails +209 -0
- data/examples/big_app_gc.rb +2 -0
- data/examples/echo.ru +26 -0
- data/examples/init.sh +102 -0
- data/examples/logger_mp_safe.rb +25 -0
- data/examples/logrotate.conf +44 -0
- data/examples/nginx.conf +156 -0
- data/examples/unicorn.conf.minimal.rb +13 -0
- data/examples/unicorn.conf.rb +110 -0
- data/examples/unicorn.socket +11 -0
- data/examples/unicorn@.service +40 -0
- data/ext/unicorn_http/CFLAGS +13 -0
- data/ext/unicorn_http/c_util.h +116 -0
- data/ext/unicorn_http/common_field_optimization.h +128 -0
- data/ext/unicorn_http/epollexclusive.h +128 -0
- data/ext/unicorn_http/ext_help.h +38 -0
- data/ext/unicorn_http/extconf.rb +39 -0
- data/ext/unicorn_http/global_variables.h +97 -0
- data/ext/unicorn_http/httpdate.c +91 -0
- data/ext/unicorn_http/unicorn_http.c +4334 -0
- data/ext/unicorn_http/unicorn_http.rl +1040 -0
- data/ext/unicorn_http/unicorn_http_common.rl +76 -0
- data/lib/unicorn/app/old_rails/static.rb +59 -0
- data/lib/unicorn/app/old_rails.rb +35 -0
- data/lib/unicorn/cgi_wrapper.rb +147 -0
- data/lib/unicorn/configurator.rb +748 -0
- data/lib/unicorn/const.rb +21 -0
- data/lib/unicorn/http_request.rb +201 -0
- data/lib/unicorn/http_response.rb +93 -0
- data/lib/unicorn/http_server.rb +859 -0
- data/lib/unicorn/launcher.rb +62 -0
- data/lib/unicorn/oob_gc.rb +81 -0
- data/lib/unicorn/preread_input.rb +33 -0
- data/lib/unicorn/select_waiter.rb +6 -0
- data/lib/unicorn/socket_helper.rb +185 -0
- data/lib/unicorn/stream_input.rb +151 -0
- data/lib/unicorn/tee_input.rb +131 -0
- data/lib/unicorn/tmpio.rb +33 -0
- data/lib/unicorn/util.rb +90 -0
- data/lib/unicorn/version.rb +1 -0
- data/lib/unicorn/worker.rb +165 -0
- data/lib/unicorn.rb +136 -0
- data/man/man1/unicorn.1 +222 -0
- data/man/man1/unicorn_rails.1 +207 -0
- data/setup.rb +1586 -0
- data/t/.gitignore +4 -0
- data/t/GNUmakefile +5 -0
- data/t/README +49 -0
- data/t/active-unix-socket.t +117 -0
- data/t/bin/unused_listen +40 -0
- data/t/broken-app.ru +12 -0
- data/t/client_body_buffer_size.ru +14 -0
- data/t/client_body_buffer_size.t +80 -0
- data/t/detach.ru +11 -0
- data/t/env.ru +3 -0
- data/t/fails-rack-lint.ru +5 -0
- data/t/heartbeat-timeout.ru +12 -0
- data/t/heartbeat-timeout.t +62 -0
- data/t/integration.ru +115 -0
- data/t/integration.t +356 -0
- data/t/lib.perl +258 -0
- data/t/listener_names.ru +4 -0
- data/t/my-tap-lib.sh +201 -0
- data/t/oob_gc.ru +17 -0
- data/t/oob_gc_path.ru +17 -0
- data/t/pid.ru +3 -0
- data/t/preread_input.ru +22 -0
- data/t/reload-bad-config.t +54 -0
- data/t/reopen-logs.ru +13 -0
- data/t/reopen-logs.t +39 -0
- data/t/t0008-back_out_of_upgrade.sh +110 -0
- data/t/t0009-broken-app.sh +56 -0
- data/t/t0010-reap-logging.sh +55 -0
- data/t/t0012-reload-empty-config.sh +86 -0
- data/t/t0013-rewindable-input-false.sh +24 -0
- data/t/t0013.ru +12 -0
- data/t/t0014-rewindable-input-true.sh +24 -0
- data/t/t0014.ru +12 -0
- data/t/t0015-configurator-internals.sh +25 -0
- data/t/t0020-at_exit-handler.sh +49 -0
- data/t/t0021-process_detach.sh +29 -0
- data/t/t0022-listener_names-preload_app.sh +32 -0
- data/t/t0300-no-default-middleware.sh +20 -0
- data/t/t0301-no-default-middleware-ignored-in-config.sh +25 -0
- data/t/t0301.ru +13 -0
- data/t/t9001-oob_gc.sh +47 -0
- data/t/t9002-oob_gc-path.sh +75 -0
- data/t/test-lib.sh +125 -0
- data/t/winch_ttin.t +67 -0
- data/t/working_directory.t +94 -0
- data/test/aggregate.rb +15 -0
- data/test/benchmark/README +60 -0
- data/test/benchmark/dd.ru +18 -0
- data/test/benchmark/ddstream.ru +50 -0
- data/test/benchmark/readinput.ru +40 -0
- data/test/benchmark/stack.ru +8 -0
- data/test/benchmark/uconnect.perl +66 -0
- data/test/exec/README +5 -0
- data/test/exec/test_exec.rb +1029 -0
- data/test/test_helper.rb +306 -0
- data/test/unit/test_ccc.rb +91 -0
- data/test/unit/test_configurator.rb +175 -0
- data/test/unit/test_droplet.rb +28 -0
- data/test/unit/test_http_parser.rb +884 -0
- data/test/unit/test_http_parser_ng.rb +714 -0
- data/test/unit/test_request.rb +169 -0
- data/test/unit/test_server.rb +244 -0
- data/test/unit/test_signals.rb +188 -0
- data/test/unit/test_socket_helper.rb +159 -0
- data/test/unit/test_stream_input.rb +210 -0
- data/test/unit/test_tee_input.rb +303 -0
- data/test/unit/test_util.rb +131 -0
- data/test/unit/test_waiter.rb +34 -0
- data/unicorn.gemspec +48 -0
- metadata +275 -0
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# -*- encoding: binary -*-
|
|
2
|
+
|
|
3
|
+
require './test/test_helper'
|
|
4
|
+
require 'tempfile'
|
|
5
|
+
|
|
6
|
+
class TestSocketHelper < Test::Unit::TestCase
|
|
7
|
+
include Unicorn::SocketHelper
|
|
8
|
+
attr_reader :logger
|
|
9
|
+
GET_SLASH = "GET / HTTP/1.0\r\n\r\n".freeze
|
|
10
|
+
|
|
11
|
+
def setup
|
|
12
|
+
@log_tmp = Tempfile.new 'logger'
|
|
13
|
+
@logger = Logger.new(@log_tmp.path)
|
|
14
|
+
@test_addr = ENV['UNICORN_TEST_ADDR'] || '127.0.0.1'
|
|
15
|
+
@test6_addr = ENV['UNICORN_TEST6_ADDR'] || '::1'
|
|
16
|
+
GC.disable
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def teardown
|
|
20
|
+
GC.enable
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def test_bind_listen_tcp
|
|
24
|
+
port = unused_port @test_addr
|
|
25
|
+
@tcp_listener_name = "#@test_addr:#{port}"
|
|
26
|
+
@tcp_listener = bind_listen(@tcp_listener_name)
|
|
27
|
+
assert Socket === @tcp_listener
|
|
28
|
+
assert @tcp_listener.local_address.ip?
|
|
29
|
+
assert_equal @tcp_listener_name, sock_name(@tcp_listener)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def test_bind_listen_options
|
|
33
|
+
port = unused_port @test_addr
|
|
34
|
+
tcp_listener_name = "#@test_addr:#{port}"
|
|
35
|
+
tmp = Tempfile.new 'unix.sock'
|
|
36
|
+
unix_listener_name = tmp.path
|
|
37
|
+
File.unlink(tmp.path)
|
|
38
|
+
[ { :backlog => 5 }, { :sndbuf => 4096 }, { :rcvbuf => 4096 },
|
|
39
|
+
{ :backlog => 16, :rcvbuf => 4096, :sndbuf => 4096 }
|
|
40
|
+
].each do |opts|
|
|
41
|
+
tcp_listener = bind_listen(tcp_listener_name, opts)
|
|
42
|
+
assert tcp_listener.local_address.ip?
|
|
43
|
+
tcp_listener.close
|
|
44
|
+
unix_listener = bind_listen(unix_listener_name, opts)
|
|
45
|
+
assert unix_listener.local_address.unix?
|
|
46
|
+
unix_listener.close
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def test_bind_listen_unix
|
|
51
|
+
old_umask = File.umask(0777)
|
|
52
|
+
tmp = Tempfile.new 'unix.sock'
|
|
53
|
+
@unix_listener_path = tmp.path
|
|
54
|
+
File.unlink(@unix_listener_path)
|
|
55
|
+
@unix_listener = bind_listen(@unix_listener_path)
|
|
56
|
+
assert Socket === @unix_listener
|
|
57
|
+
assert @unix_listener.local_address.unix?
|
|
58
|
+
assert_equal @unix_listener_path, sock_name(@unix_listener)
|
|
59
|
+
assert File.readable?(@unix_listener_path), "not readable"
|
|
60
|
+
assert File.writable?(@unix_listener_path), "not writable"
|
|
61
|
+
assert_equal 0777, File.umask
|
|
62
|
+
assert_equal @unix_listener, bind_listen(@unix_listener)
|
|
63
|
+
ensure
|
|
64
|
+
File.umask(old_umask)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def test_bind_listen_unix_umask
|
|
68
|
+
old_umask = File.umask(0777)
|
|
69
|
+
tmp = Tempfile.new 'unix.sock'
|
|
70
|
+
@unix_listener_path = tmp.path
|
|
71
|
+
File.unlink(@unix_listener_path)
|
|
72
|
+
@unix_listener = bind_listen(@unix_listener_path, :umask => 077)
|
|
73
|
+
assert_equal @unix_listener_path, sock_name(@unix_listener)
|
|
74
|
+
assert_equal 0140700, File.stat(@unix_listener_path).mode
|
|
75
|
+
assert_equal 0777, File.umask
|
|
76
|
+
ensure
|
|
77
|
+
File.umask(old_umask)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def test_bind_listen_unix_rebind
|
|
81
|
+
test_bind_listen_unix
|
|
82
|
+
new_listener = nil
|
|
83
|
+
assert_raises(Errno::EADDRINUSE) do
|
|
84
|
+
new_listener = bind_listen(@unix_listener_path)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
File.unlink(@unix_listener_path)
|
|
88
|
+
new_listener = bind_listen(@unix_listener_path)
|
|
89
|
+
|
|
90
|
+
assert new_listener.fileno != @unix_listener.fileno
|
|
91
|
+
assert_equal sock_name(new_listener), sock_name(@unix_listener)
|
|
92
|
+
assert_equal @unix_listener_path, sock_name(new_listener)
|
|
93
|
+
pid = fork do
|
|
94
|
+
begin
|
|
95
|
+
client, _ = new_listener.accept
|
|
96
|
+
client.syswrite('abcde')
|
|
97
|
+
exit 0
|
|
98
|
+
rescue => e
|
|
99
|
+
warn "#{e.message} (#{e.class})"
|
|
100
|
+
exit 1
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
s = unix_socket(@unix_listener_path)
|
|
104
|
+
IO.select([s])
|
|
105
|
+
assert_equal 'abcde', s.sysread(5)
|
|
106
|
+
pid, status = Process.waitpid2(pid)
|
|
107
|
+
assert status.success?
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def test_tcp_defer_accept_default
|
|
111
|
+
return unless defined?(TCP_DEFER_ACCEPT)
|
|
112
|
+
port = unused_port @test_addr
|
|
113
|
+
name = "#@test_addr:#{port}"
|
|
114
|
+
sock = bind_listen(name)
|
|
115
|
+
cur = sock.getsockopt(Socket::SOL_TCP, TCP_DEFER_ACCEPT).unpack('i')[0]
|
|
116
|
+
assert cur >= 1
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def test_tcp_defer_accept_disable
|
|
120
|
+
return unless defined?(TCP_DEFER_ACCEPT)
|
|
121
|
+
port = unused_port @test_addr
|
|
122
|
+
name = "#@test_addr:#{port}"
|
|
123
|
+
sock = bind_listen(name, :tcp_defer_accept => false)
|
|
124
|
+
cur = sock.getsockopt(Socket::SOL_TCP, TCP_DEFER_ACCEPT).unpack('i')[0]
|
|
125
|
+
assert_equal 0, cur
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def test_tcp_defer_accept_nr
|
|
129
|
+
return unless defined?(TCP_DEFER_ACCEPT)
|
|
130
|
+
port = unused_port @test_addr
|
|
131
|
+
name = "#@test_addr:#{port}"
|
|
132
|
+
sock = bind_listen(name, :tcp_defer_accept => 60)
|
|
133
|
+
cur = sock.getsockopt(Socket::SOL_TCP, TCP_DEFER_ACCEPT).unpack('i')[0]
|
|
134
|
+
assert cur > 1
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def test_ipv6only
|
|
138
|
+
port = begin
|
|
139
|
+
unused_port "#@test6_addr"
|
|
140
|
+
rescue Errno::EINVAL
|
|
141
|
+
return
|
|
142
|
+
end
|
|
143
|
+
sock = bind_listen "[#@test6_addr]:#{port}", :ipv6only => true
|
|
144
|
+
cur = sock.getsockopt(:IPPROTO_IPV6, :IPV6_V6ONLY).unpack('i')[0]
|
|
145
|
+
assert_equal 1, cur
|
|
146
|
+
rescue Errno::EAFNOSUPPORT
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def test_reuseport
|
|
150
|
+
return unless defined?(Socket::SO_REUSEPORT)
|
|
151
|
+
port = unused_port @test_addr
|
|
152
|
+
name = "#@test_addr:#{port}"
|
|
153
|
+
sock = bind_listen(name, :reuseport => true)
|
|
154
|
+
cur = sock.getsockopt(:SOL_SOCKET, :SO_REUSEPORT).int
|
|
155
|
+
assert_operator cur, :>, 0
|
|
156
|
+
rescue Errno::ENOPROTOOPT
|
|
157
|
+
# kernel does not support SO_REUSEPORT (older Linux)
|
|
158
|
+
end
|
|
159
|
+
end
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
# -*- encoding: binary -*-
|
|
2
|
+
|
|
3
|
+
require 'test/unit'
|
|
4
|
+
require 'digest/sha1'
|
|
5
|
+
require 'unicorn'
|
|
6
|
+
|
|
7
|
+
class TestStreamInput < Test::Unit::TestCase
|
|
8
|
+
def setup
|
|
9
|
+
@rs = "\n"
|
|
10
|
+
$/ == "\n" or abort %q{test broken if \$/ != "\\n"}
|
|
11
|
+
@env = {}
|
|
12
|
+
@rd, @wr = UNIXSocket.pair
|
|
13
|
+
@rd.sync = @wr.sync = true
|
|
14
|
+
@start_pid = $$
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def teardown
|
|
18
|
+
return if $$ != @start_pid
|
|
19
|
+
@rd.close rescue nil
|
|
20
|
+
@wr.close rescue nil
|
|
21
|
+
Process.waitall
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def test_read_negative
|
|
25
|
+
r = init_request('hello')
|
|
26
|
+
si = Unicorn::StreamInput.new(@rd, r)
|
|
27
|
+
assert_raises(ArgumentError) { si.read(-1) }
|
|
28
|
+
assert_equal 'hello', si.read
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def test_read_small
|
|
32
|
+
r = init_request('hello')
|
|
33
|
+
si = Unicorn::StreamInput.new(@rd, r)
|
|
34
|
+
assert_equal 'hello', si.read
|
|
35
|
+
assert_equal '', si.read
|
|
36
|
+
assert_nil si.read(5)
|
|
37
|
+
assert_nil si.gets
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def test_gets_oneliner
|
|
41
|
+
r = init_request('hello')
|
|
42
|
+
si = Unicorn::StreamInput.new(@rd, r)
|
|
43
|
+
assert_equal 'hello', si.gets
|
|
44
|
+
assert_nil si.gets
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def test_gets_multiline
|
|
48
|
+
r = init_request("a\nb\n\n")
|
|
49
|
+
si = Unicorn::StreamInput.new(@rd, r)
|
|
50
|
+
assert_equal "a\n", si.gets
|
|
51
|
+
assert_equal "b\n", si.gets
|
|
52
|
+
assert_equal "\n", si.gets
|
|
53
|
+
assert_nil si.gets
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def test_gets_empty_rs
|
|
57
|
+
r = init_request("a\nb\n\n")
|
|
58
|
+
si = Unicorn::StreamInput.new(@rd, r)
|
|
59
|
+
pid = fork do # to avoid $/ warning (hopefully)
|
|
60
|
+
$/ = nil
|
|
61
|
+
@rd.close
|
|
62
|
+
@wr.write(si.gets)
|
|
63
|
+
@wr.close
|
|
64
|
+
end
|
|
65
|
+
@wr.close
|
|
66
|
+
assert_equal "a\nb\n\n", @rd.read
|
|
67
|
+
pid, status = Process.waitpid2(pid)
|
|
68
|
+
assert_predicate status, :success?
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def test_read_with_equal_len
|
|
72
|
+
r = init_request("abcde")
|
|
73
|
+
si = Unicorn::StreamInput.new(@rd, r)
|
|
74
|
+
assert_equal "abcde", si.read(5)
|
|
75
|
+
assert_nil si.read(5)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def test_big_body_multi
|
|
79
|
+
r = init_request('.', Unicorn::Const::MAX_BODY + 1)
|
|
80
|
+
si = Unicorn::StreamInput.new(@rd, r)
|
|
81
|
+
assert_equal Unicorn::Const::MAX_BODY, @parser.content_length
|
|
82
|
+
assert ! @parser.body_eof?
|
|
83
|
+
nr = Unicorn::Const::MAX_BODY / 4
|
|
84
|
+
pid = fork {
|
|
85
|
+
@rd.close
|
|
86
|
+
nr.times { @wr.write('....') }
|
|
87
|
+
@wr.close
|
|
88
|
+
}
|
|
89
|
+
@wr.close
|
|
90
|
+
assert_equal '.', si.read(1)
|
|
91
|
+
nr.times { |x|
|
|
92
|
+
assert_equal '....', si.read(4), "nr=#{x}"
|
|
93
|
+
}
|
|
94
|
+
assert_nil si.read(1)
|
|
95
|
+
pid, status = Process.waitpid2(pid)
|
|
96
|
+
assert status.success?
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def test_gets_long
|
|
100
|
+
r = init_request("hello", 5 + (4096 * 4 * 3) + "#{@rs}foo#{@rs}".size)
|
|
101
|
+
si = Unicorn::StreamInput.new(@rd, r)
|
|
102
|
+
status = line = nil
|
|
103
|
+
pid = fork {
|
|
104
|
+
@rd.close
|
|
105
|
+
3.times { @wr.write("ffff" * 4096) }
|
|
106
|
+
@wr.write "#{@rs}foo#{@rs}"
|
|
107
|
+
@wr.close
|
|
108
|
+
}
|
|
109
|
+
@wr.close
|
|
110
|
+
line = si.gets
|
|
111
|
+
assert_equal(4096 * 4 * 3 + 5 + $/.size, line.size)
|
|
112
|
+
assert_equal("hello" << ("ffff" * 4096 * 3) << "#{@rs}", line)
|
|
113
|
+
line = si.gets
|
|
114
|
+
assert_equal "foo#{@rs}", line
|
|
115
|
+
assert_nil si.gets
|
|
116
|
+
pid, status = Process.waitpid2(pid)
|
|
117
|
+
assert status.success?
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def test_read_with_buffer
|
|
121
|
+
r = init_request('hello')
|
|
122
|
+
si = Unicorn::StreamInput.new(@rd, r)
|
|
123
|
+
buf = ''
|
|
124
|
+
rv = si.read(4, buf)
|
|
125
|
+
assert_equal 'hell', rv
|
|
126
|
+
assert_equal 'hell', buf
|
|
127
|
+
assert_equal rv.object_id, buf.object_id
|
|
128
|
+
assert_equal 'o', si.read
|
|
129
|
+
assert_equal nil, si.read(5, buf)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def test_read_with_buffer_clobbers
|
|
133
|
+
r = init_request('hello')
|
|
134
|
+
si = Unicorn::StreamInput.new(@rd, r)
|
|
135
|
+
buf = 'foo'
|
|
136
|
+
assert_equal 'hello', si.read(nil, buf)
|
|
137
|
+
assert_equal 'hello', buf
|
|
138
|
+
assert_equal '', si.read(nil, buf)
|
|
139
|
+
assert_equal '', buf
|
|
140
|
+
buf = 'asdf'
|
|
141
|
+
assert_nil si.read(5, buf)
|
|
142
|
+
assert_equal '', buf
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def test_read_zero
|
|
146
|
+
r = init_request('hello')
|
|
147
|
+
si = Unicorn::StreamInput.new(@rd, r)
|
|
148
|
+
assert_equal '', si.read(0)
|
|
149
|
+
buf = 'asdf'
|
|
150
|
+
rv = si.read(0, buf)
|
|
151
|
+
assert_equal rv.object_id, buf.object_id
|
|
152
|
+
assert_equal '', buf
|
|
153
|
+
assert_equal 'hello', si.read
|
|
154
|
+
assert_nil si.read(5)
|
|
155
|
+
assert_equal '', si.read(0)
|
|
156
|
+
buf = 'hello'
|
|
157
|
+
rv = si.read(0, buf)
|
|
158
|
+
assert_equal rv.object_id, buf.object_id
|
|
159
|
+
assert_equal '', rv
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def test_gets_read_mix
|
|
163
|
+
r = init_request("hello\nasdfasdf")
|
|
164
|
+
si = Unicorn::StreamInput.new(@rd, r)
|
|
165
|
+
assert_equal "hello\n", si.gets
|
|
166
|
+
assert_equal "asdfasdf", si.read(9)
|
|
167
|
+
assert_nil si.read(9)
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def test_gets_read_mix_chunked
|
|
171
|
+
r = @parser = Unicorn::HttpParser.new
|
|
172
|
+
body = "6\r\nhello"
|
|
173
|
+
@buf = "POST / HTTP/1.1\r\n" \
|
|
174
|
+
"Host: localhost\r\n" \
|
|
175
|
+
"Transfer-Encoding: chunked\r\n" \
|
|
176
|
+
"\r\n#{body}"
|
|
177
|
+
assert_equal @env, @parser.headers(@env, @buf)
|
|
178
|
+
assert_equal body, @buf
|
|
179
|
+
si = Unicorn::StreamInput.new(@rd, r)
|
|
180
|
+
@wr.syswrite "\n\r\n"
|
|
181
|
+
assert_equal "hello\n", si.gets
|
|
182
|
+
@wr.syswrite "8\r\nasdfasdf\r\n"
|
|
183
|
+
assert_equal"asdfasdf", si.read(9) + si.read(9)
|
|
184
|
+
@wr.syswrite "0\r\n\r\n"
|
|
185
|
+
assert_nil si.read(9)
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def test_gets_read_mix_big
|
|
189
|
+
r = init_request("hello\n#{'.' * 65536}")
|
|
190
|
+
si = Unicorn::StreamInput.new(@rd, r)
|
|
191
|
+
assert_equal "hello\n", si.gets
|
|
192
|
+
assert_equal '.' * 16384, si.read(16384)
|
|
193
|
+
assert_equal '.' * 16383, si.read(16383)
|
|
194
|
+
assert_equal '.' * 16384, si.read(16384)
|
|
195
|
+
assert_equal '.' * 16385, si.read(16385)
|
|
196
|
+
assert_nil si.gets
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def init_request(body, size = nil)
|
|
200
|
+
@parser = Unicorn::HttpParser.new
|
|
201
|
+
body = body.to_s.freeze
|
|
202
|
+
@buf = "POST / HTTP/1.1\r\n" \
|
|
203
|
+
"Host: localhost\r\n" \
|
|
204
|
+
"Content-Length: #{size || body.size}\r\n" \
|
|
205
|
+
"\r\n#{body}"
|
|
206
|
+
assert_equal @env, @parser.headers(@env, @buf)
|
|
207
|
+
assert_equal body, @buf
|
|
208
|
+
@parser
|
|
209
|
+
end
|
|
210
|
+
end
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
# -*- encoding: binary -*-
|
|
2
|
+
|
|
3
|
+
require 'test/unit'
|
|
4
|
+
require 'digest/sha1'
|
|
5
|
+
require 'unicorn'
|
|
6
|
+
|
|
7
|
+
class TeeInput < Unicorn::TeeInput
|
|
8
|
+
attr_accessor :tmp, :len
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
class TestTeeInput < Test::Unit::TestCase
|
|
12
|
+
def setup
|
|
13
|
+
@rd, @wr = UNIXSocket.pair
|
|
14
|
+
@rd.sync = @wr.sync = true
|
|
15
|
+
@start_pid = $$
|
|
16
|
+
@rs = "\n"
|
|
17
|
+
$/ == "\n" or abort %q{test broken if \$/ != "\\n"}
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def teardown
|
|
21
|
+
return if $$ != @start_pid
|
|
22
|
+
@rd.close rescue nil
|
|
23
|
+
@wr.close rescue nil
|
|
24
|
+
begin
|
|
25
|
+
Process.wait
|
|
26
|
+
rescue Errno::ECHILD
|
|
27
|
+
break
|
|
28
|
+
end while true
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def check_tempfiles
|
|
32
|
+
tmp = @parser.env["rack.tempfiles"]
|
|
33
|
+
assert_instance_of Array, tmp
|
|
34
|
+
assert_operator tmp.size, :>=, 1
|
|
35
|
+
assert_instance_of Unicorn::TmpIO, tmp[0]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def test_gets_long
|
|
39
|
+
r = init_request("hello", 5 + (4096 * 4 * 3) + "#{@rs}foo#{@rs}".size)
|
|
40
|
+
ti = TeeInput.new(@rd, r)
|
|
41
|
+
status = line = nil
|
|
42
|
+
pid = fork {
|
|
43
|
+
@rd.close
|
|
44
|
+
3.times { @wr.write("ffff" * 4096) }
|
|
45
|
+
@wr.write "#{@rs}foo#{@rs}"
|
|
46
|
+
@wr.close
|
|
47
|
+
}
|
|
48
|
+
@wr.close
|
|
49
|
+
line = ti.gets
|
|
50
|
+
assert_equal(4096 * 4 * 3 + 5 + $/.size, line.size)
|
|
51
|
+
assert_equal("hello" << ("ffff" * 4096 * 3) << "#{@rs}", line)
|
|
52
|
+
line = ti.gets
|
|
53
|
+
assert_equal "foo#{@rs}", line
|
|
54
|
+
assert_nil ti.gets
|
|
55
|
+
pid, status = Process.waitpid2(pid)
|
|
56
|
+
assert status.success?
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def test_gets_short
|
|
60
|
+
r = init_request("hello", 5 + "#{@rs}foo".size)
|
|
61
|
+
ti = TeeInput.new(@rd, r)
|
|
62
|
+
status = line = nil
|
|
63
|
+
pid = fork {
|
|
64
|
+
@rd.close
|
|
65
|
+
@wr.write "#{@rs}foo"
|
|
66
|
+
@wr.close
|
|
67
|
+
}
|
|
68
|
+
@wr.close
|
|
69
|
+
line = ti.gets
|
|
70
|
+
assert_equal("hello#{@rs}", line)
|
|
71
|
+
line = ti.gets
|
|
72
|
+
assert_equal "foo", line
|
|
73
|
+
assert_nil ti.gets
|
|
74
|
+
pid, status = Process.waitpid2(pid)
|
|
75
|
+
assert status.success?
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def test_small_body
|
|
79
|
+
r = init_request('hello')
|
|
80
|
+
ti = TeeInput.new(@rd, r)
|
|
81
|
+
assert_equal 0, @parser.content_length
|
|
82
|
+
assert @parser.body_eof?
|
|
83
|
+
assert_equal StringIO, ti.tmp.class
|
|
84
|
+
assert_equal 0, ti.tmp.pos
|
|
85
|
+
assert_equal 5, ti.size
|
|
86
|
+
assert_equal 'hello', ti.read
|
|
87
|
+
assert_equal '', ti.read
|
|
88
|
+
assert_nil ti.read(4096)
|
|
89
|
+
assert_equal 5, ti.size
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def test_read_with_buffer
|
|
93
|
+
r = init_request('hello')
|
|
94
|
+
ti = TeeInput.new(@rd, r)
|
|
95
|
+
buf = ''
|
|
96
|
+
rv = ti.read(4, buf)
|
|
97
|
+
assert_equal 'hell', rv
|
|
98
|
+
assert_equal 'hell', buf
|
|
99
|
+
assert_equal rv.object_id, buf.object_id
|
|
100
|
+
assert_equal 'o', ti.read
|
|
101
|
+
assert_equal nil, ti.read(5, buf)
|
|
102
|
+
assert_equal 0, ti.rewind
|
|
103
|
+
assert_equal 'hello', ti.read(5, buf)
|
|
104
|
+
assert_equal 'hello', buf
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def test_big_body
|
|
108
|
+
r = init_request('.' * Unicorn::Const::MAX_BODY << 'a')
|
|
109
|
+
ti = TeeInput.new(@rd, r)
|
|
110
|
+
assert_equal 0, @parser.content_length
|
|
111
|
+
assert @parser.body_eof?
|
|
112
|
+
assert_kind_of File, ti.tmp
|
|
113
|
+
assert_equal 0, ti.tmp.pos
|
|
114
|
+
assert_equal Unicorn::Const::MAX_BODY + 1, ti.size
|
|
115
|
+
check_tempfiles
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def test_read_in_full_if_content_length
|
|
119
|
+
a, b = 300, 3
|
|
120
|
+
r = init_request('.' * b, 300)
|
|
121
|
+
assert_equal 300, @parser.content_length
|
|
122
|
+
ti = TeeInput.new(@rd, r)
|
|
123
|
+
pid = fork {
|
|
124
|
+
@wr.write('.' * 197)
|
|
125
|
+
sleep 1 # still a *potential* race here that would make the test moot...
|
|
126
|
+
@wr.write('.' * 100)
|
|
127
|
+
}
|
|
128
|
+
assert_equal a, ti.read(a).size
|
|
129
|
+
_, status = Process.waitpid2(pid)
|
|
130
|
+
assert status.success?
|
|
131
|
+
@wr.close
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def test_big_body_multi
|
|
135
|
+
r = init_request('.', Unicorn::Const::MAX_BODY + 1)
|
|
136
|
+
ti = TeeInput.new(@rd, r)
|
|
137
|
+
assert_equal Unicorn::Const::MAX_BODY, @parser.content_length
|
|
138
|
+
assert ! @parser.body_eof?
|
|
139
|
+
assert_kind_of File, ti.tmp
|
|
140
|
+
assert_equal 0, ti.tmp.pos
|
|
141
|
+
assert_equal Unicorn::Const::MAX_BODY + 1, ti.size
|
|
142
|
+
nr = Unicorn::Const::MAX_BODY / 4
|
|
143
|
+
pid = fork {
|
|
144
|
+
@rd.close
|
|
145
|
+
nr.times { @wr.write('....') }
|
|
146
|
+
@wr.close
|
|
147
|
+
}
|
|
148
|
+
@wr.close
|
|
149
|
+
assert_equal '.', ti.read(1)
|
|
150
|
+
assert_equal Unicorn::Const::MAX_BODY + 1, ti.size
|
|
151
|
+
nr.times { |x|
|
|
152
|
+
assert_equal '....', ti.read(4), "nr=#{x}"
|
|
153
|
+
assert_equal Unicorn::Const::MAX_BODY + 1, ti.size
|
|
154
|
+
}
|
|
155
|
+
assert_nil ti.read(1)
|
|
156
|
+
pid, status = Process.waitpid2(pid)
|
|
157
|
+
assert status.success?
|
|
158
|
+
check_tempfiles
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def test_chunked
|
|
162
|
+
@parser = Unicorn::HttpParser.new
|
|
163
|
+
@parser.buf << "POST / HTTP/1.1\r\n" \
|
|
164
|
+
"Host: localhost\r\n" \
|
|
165
|
+
"Transfer-Encoding: chunked\r\n" \
|
|
166
|
+
"\r\n"
|
|
167
|
+
assert @parser.parse
|
|
168
|
+
assert_equal "", @parser.buf
|
|
169
|
+
|
|
170
|
+
pid = fork {
|
|
171
|
+
@rd.close
|
|
172
|
+
5.times { @wr.write("5\r\nabcde\r\n") }
|
|
173
|
+
@wr.write("0\r\n\r\n")
|
|
174
|
+
}
|
|
175
|
+
@wr.close
|
|
176
|
+
ti = TeeInput.new(@rd, @parser)
|
|
177
|
+
assert_nil @parser.content_length
|
|
178
|
+
assert_nil ti.len
|
|
179
|
+
assert ! @parser.body_eof?
|
|
180
|
+
assert_equal 25, ti.size
|
|
181
|
+
assert @parser.body_eof?
|
|
182
|
+
assert_equal 25, ti.len
|
|
183
|
+
assert_equal 0, ti.tmp.pos
|
|
184
|
+
ti.rewind
|
|
185
|
+
assert_equal 0, ti.tmp.pos
|
|
186
|
+
assert_equal 'abcdeabcdeabcdeabcde', ti.read(20)
|
|
187
|
+
assert_equal 20, ti.tmp.pos
|
|
188
|
+
ti.rewind
|
|
189
|
+
assert_equal 0, ti.tmp.pos
|
|
190
|
+
assert_kind_of File, ti.tmp
|
|
191
|
+
status = nil
|
|
192
|
+
pid, status = Process.waitpid2(pid)
|
|
193
|
+
assert status.success?
|
|
194
|
+
check_tempfiles
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def test_chunked_ping_pong
|
|
198
|
+
@parser = Unicorn::HttpParser.new
|
|
199
|
+
buf = @parser.buf
|
|
200
|
+
buf << "POST / HTTP/1.1\r\n" \
|
|
201
|
+
"Host: localhost\r\n" \
|
|
202
|
+
"Transfer-Encoding: chunked\r\n" \
|
|
203
|
+
"\r\n"
|
|
204
|
+
assert @parser.parse
|
|
205
|
+
assert_equal "", buf
|
|
206
|
+
chunks = %w(aa bbb cccc dddd eeee)
|
|
207
|
+
rd, wr = IO.pipe
|
|
208
|
+
|
|
209
|
+
pid = fork {
|
|
210
|
+
chunks.each do |chunk|
|
|
211
|
+
rd.read(1) == "." and
|
|
212
|
+
@wr.write("#{'%x' % [ chunk.size]}\r\n#{chunk}\r\n")
|
|
213
|
+
end
|
|
214
|
+
@wr.write("0\r\n\r\n")
|
|
215
|
+
}
|
|
216
|
+
ti = TeeInput.new(@rd, @parser)
|
|
217
|
+
assert_nil @parser.content_length
|
|
218
|
+
assert_nil ti.len
|
|
219
|
+
assert ! @parser.body_eof?
|
|
220
|
+
chunks.each do |chunk|
|
|
221
|
+
wr.write('.')
|
|
222
|
+
assert_equal chunk, ti.read(16384)
|
|
223
|
+
end
|
|
224
|
+
_, status = Process.waitpid2(pid)
|
|
225
|
+
assert status.success?
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def test_chunked_with_trailer
|
|
229
|
+
@parser = Unicorn::HttpParser.new
|
|
230
|
+
buf = @parser.buf
|
|
231
|
+
buf << "POST / HTTP/1.1\r\n" \
|
|
232
|
+
"Host: localhost\r\n" \
|
|
233
|
+
"Trailer: Hello\r\n" \
|
|
234
|
+
"Transfer-Encoding: chunked\r\n" \
|
|
235
|
+
"\r\n"
|
|
236
|
+
assert @parser.parse
|
|
237
|
+
assert_equal "", buf
|
|
238
|
+
|
|
239
|
+
pid = fork {
|
|
240
|
+
@rd.close
|
|
241
|
+
5.times { @wr.write("5\r\nabcde\r\n") }
|
|
242
|
+
@wr.write("0\r\n")
|
|
243
|
+
@wr.write("Hello: World\r\n\r\n")
|
|
244
|
+
}
|
|
245
|
+
@wr.close
|
|
246
|
+
ti = TeeInput.new(@rd, @parser)
|
|
247
|
+
assert_nil @parser.content_length
|
|
248
|
+
assert_nil ti.len
|
|
249
|
+
assert ! @parser.body_eof?
|
|
250
|
+
assert_equal 25, ti.size
|
|
251
|
+
assert_equal "World", @parser.env['HTTP_HELLO']
|
|
252
|
+
pid, status = Process.waitpid2(pid)
|
|
253
|
+
assert status.success?
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def test_chunked_and_size_slow
|
|
257
|
+
@parser = Unicorn::HttpParser.new
|
|
258
|
+
buf = @parser.buf
|
|
259
|
+
buf << "POST / HTTP/1.1\r\n" \
|
|
260
|
+
"Host: localhost\r\n" \
|
|
261
|
+
"Trailer: Hello\r\n" \
|
|
262
|
+
"Transfer-Encoding: chunked\r\n" \
|
|
263
|
+
"\r\n"
|
|
264
|
+
assert @parser.parse
|
|
265
|
+
assert_equal "", buf
|
|
266
|
+
|
|
267
|
+
@wr.write("9\r\nabcde")
|
|
268
|
+
ti = TeeInput.new(@rd, @parser)
|
|
269
|
+
assert_nil @parser.content_length
|
|
270
|
+
assert_equal "abcde", ti.read(9)
|
|
271
|
+
assert ! @parser.body_eof?
|
|
272
|
+
@wr.write("fghi\r\n0\r\nHello: World\r\n\r\n")
|
|
273
|
+
assert_equal 9, ti.size
|
|
274
|
+
assert_equal "fghi", ti.read(9)
|
|
275
|
+
assert_equal nil, ti.read(9)
|
|
276
|
+
assert_equal "World", @parser.env['HTTP_HELLO']
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
def test_gets_read_mix
|
|
280
|
+
r = init_request("hello\nasdfasdf")
|
|
281
|
+
ti = Unicorn::TeeInput.new(@rd, r)
|
|
282
|
+
assert_equal "hello\n", ti.gets
|
|
283
|
+
assert_equal "asdfasdf", ti.read(9)
|
|
284
|
+
assert_nil ti.read(9)
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
private
|
|
288
|
+
|
|
289
|
+
def init_request(body, size = nil)
|
|
290
|
+
@parser = Unicorn::HttpParser.new
|
|
291
|
+
body = body.to_s.freeze
|
|
292
|
+
buf = @parser.buf
|
|
293
|
+
buf << "POST / HTTP/1.1\r\n" \
|
|
294
|
+
"Host: localhost\r\n" \
|
|
295
|
+
"Content-Length: #{size || body.size}\r\n" \
|
|
296
|
+
"\r\n#{body}"
|
|
297
|
+
assert @parser.parse
|
|
298
|
+
assert_equal body, buf
|
|
299
|
+
@buf = buf
|
|
300
|
+
@parser
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
end
|