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