unicorn-maintained 6.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (151) hide show
  1. checksums.yaml +7 -0
  2. data/.CHANGELOG.old +25 -0
  3. data/.document +28 -0
  4. data/.gitattributes +5 -0
  5. data/.gitignore +25 -0
  6. data/.mailmap +26 -0
  7. data/.manifest +149 -0
  8. data/.olddoc.yml +25 -0
  9. data/Application_Timeouts +77 -0
  10. data/CONTRIBUTORS +39 -0
  11. data/COPYING +674 -0
  12. data/DESIGN +99 -0
  13. data/Documentation/.gitignore +3 -0
  14. data/Documentation/unicorn.1 +222 -0
  15. data/Documentation/unicorn_rails.1 +207 -0
  16. data/FAQ +70 -0
  17. data/GIT-VERSION-FILE +1 -0
  18. data/GIT-VERSION-GEN +39 -0
  19. data/GNUmakefile +317 -0
  20. data/HACKING +112 -0
  21. data/ISSUES +102 -0
  22. data/KNOWN_ISSUES +79 -0
  23. data/LATEST +1 -0
  24. data/LICENSE +67 -0
  25. data/Links +58 -0
  26. data/NEWS +1 -0
  27. data/PHILOSOPHY +139 -0
  28. data/README +156 -0
  29. data/Rakefile +16 -0
  30. data/SIGNALS +123 -0
  31. data/Sandbox +104 -0
  32. data/TODO +3 -0
  33. data/TUNING +119 -0
  34. data/archive/.gitignore +3 -0
  35. data/archive/slrnpull.conf +4 -0
  36. data/bin/unicorn +128 -0
  37. data/bin/unicorn_rails +209 -0
  38. data/examples/big_app_gc.rb +2 -0
  39. data/examples/echo.ru +26 -0
  40. data/examples/init.sh +102 -0
  41. data/examples/logger_mp_safe.rb +25 -0
  42. data/examples/logrotate.conf +44 -0
  43. data/examples/nginx.conf +156 -0
  44. data/examples/unicorn.conf.minimal.rb +13 -0
  45. data/examples/unicorn.conf.rb +110 -0
  46. data/examples/unicorn.socket +11 -0
  47. data/examples/unicorn@.service +40 -0
  48. data/ext/unicorn_http/CFLAGS +13 -0
  49. data/ext/unicorn_http/c_util.h +116 -0
  50. data/ext/unicorn_http/common_field_optimization.h +128 -0
  51. data/ext/unicorn_http/epollexclusive.h +128 -0
  52. data/ext/unicorn_http/ext_help.h +38 -0
  53. data/ext/unicorn_http/extconf.rb +39 -0
  54. data/ext/unicorn_http/global_variables.h +97 -0
  55. data/ext/unicorn_http/httpdate.c +91 -0
  56. data/ext/unicorn_http/unicorn_http.c +4334 -0
  57. data/ext/unicorn_http/unicorn_http.rl +1040 -0
  58. data/ext/unicorn_http/unicorn_http_common.rl +76 -0
  59. data/lib/unicorn/app/old_rails/static.rb +59 -0
  60. data/lib/unicorn/app/old_rails.rb +35 -0
  61. data/lib/unicorn/cgi_wrapper.rb +147 -0
  62. data/lib/unicorn/configurator.rb +748 -0
  63. data/lib/unicorn/const.rb +21 -0
  64. data/lib/unicorn/http_request.rb +201 -0
  65. data/lib/unicorn/http_response.rb +93 -0
  66. data/lib/unicorn/http_server.rb +859 -0
  67. data/lib/unicorn/launcher.rb +62 -0
  68. data/lib/unicorn/oob_gc.rb +81 -0
  69. data/lib/unicorn/preread_input.rb +33 -0
  70. data/lib/unicorn/select_waiter.rb +6 -0
  71. data/lib/unicorn/socket_helper.rb +185 -0
  72. data/lib/unicorn/stream_input.rb +151 -0
  73. data/lib/unicorn/tee_input.rb +131 -0
  74. data/lib/unicorn/tmpio.rb +33 -0
  75. data/lib/unicorn/util.rb +90 -0
  76. data/lib/unicorn/version.rb +1 -0
  77. data/lib/unicorn/worker.rb +165 -0
  78. data/lib/unicorn.rb +136 -0
  79. data/man/man1/unicorn.1 +222 -0
  80. data/man/man1/unicorn_rails.1 +207 -0
  81. data/setup.rb +1586 -0
  82. data/t/.gitignore +4 -0
  83. data/t/GNUmakefile +5 -0
  84. data/t/README +49 -0
  85. data/t/active-unix-socket.t +117 -0
  86. data/t/bin/unused_listen +40 -0
  87. data/t/broken-app.ru +12 -0
  88. data/t/client_body_buffer_size.ru +14 -0
  89. data/t/client_body_buffer_size.t +80 -0
  90. data/t/detach.ru +11 -0
  91. data/t/env.ru +3 -0
  92. data/t/fails-rack-lint.ru +5 -0
  93. data/t/heartbeat-timeout.ru +12 -0
  94. data/t/heartbeat-timeout.t +62 -0
  95. data/t/integration.ru +115 -0
  96. data/t/integration.t +356 -0
  97. data/t/lib.perl +258 -0
  98. data/t/listener_names.ru +4 -0
  99. data/t/my-tap-lib.sh +201 -0
  100. data/t/oob_gc.ru +17 -0
  101. data/t/oob_gc_path.ru +17 -0
  102. data/t/pid.ru +3 -0
  103. data/t/preread_input.ru +22 -0
  104. data/t/reload-bad-config.t +54 -0
  105. data/t/reopen-logs.ru +13 -0
  106. data/t/reopen-logs.t +39 -0
  107. data/t/t0008-back_out_of_upgrade.sh +110 -0
  108. data/t/t0009-broken-app.sh +56 -0
  109. data/t/t0010-reap-logging.sh +55 -0
  110. data/t/t0012-reload-empty-config.sh +86 -0
  111. data/t/t0013-rewindable-input-false.sh +24 -0
  112. data/t/t0013.ru +12 -0
  113. data/t/t0014-rewindable-input-true.sh +24 -0
  114. data/t/t0014.ru +12 -0
  115. data/t/t0015-configurator-internals.sh +25 -0
  116. data/t/t0020-at_exit-handler.sh +49 -0
  117. data/t/t0021-process_detach.sh +29 -0
  118. data/t/t0022-listener_names-preload_app.sh +32 -0
  119. data/t/t0300-no-default-middleware.sh +20 -0
  120. data/t/t0301-no-default-middleware-ignored-in-config.sh +25 -0
  121. data/t/t0301.ru +13 -0
  122. data/t/t9001-oob_gc.sh +47 -0
  123. data/t/t9002-oob_gc-path.sh +75 -0
  124. data/t/test-lib.sh +125 -0
  125. data/t/winch_ttin.t +67 -0
  126. data/t/working_directory.t +94 -0
  127. data/test/aggregate.rb +15 -0
  128. data/test/benchmark/README +60 -0
  129. data/test/benchmark/dd.ru +18 -0
  130. data/test/benchmark/ddstream.ru +50 -0
  131. data/test/benchmark/readinput.ru +40 -0
  132. data/test/benchmark/stack.ru +8 -0
  133. data/test/benchmark/uconnect.perl +66 -0
  134. data/test/exec/README +5 -0
  135. data/test/exec/test_exec.rb +1029 -0
  136. data/test/test_helper.rb +306 -0
  137. data/test/unit/test_ccc.rb +91 -0
  138. data/test/unit/test_configurator.rb +175 -0
  139. data/test/unit/test_droplet.rb +28 -0
  140. data/test/unit/test_http_parser.rb +884 -0
  141. data/test/unit/test_http_parser_ng.rb +714 -0
  142. data/test/unit/test_request.rb +169 -0
  143. data/test/unit/test_server.rb +244 -0
  144. data/test/unit/test_signals.rb +188 -0
  145. data/test/unit/test_socket_helper.rb +159 -0
  146. data/test/unit/test_stream_input.rb +210 -0
  147. data/test/unit/test_tee_input.rb +303 -0
  148. data/test/unit/test_util.rb +131 -0
  149. data/test/unit/test_waiter.rb +34 -0
  150. data/unicorn.gemspec +48 -0
  151. 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