unicorn-simon 0.0.1
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 +156 -0
- data/.olddoc.yml +18 -0
- data/Application_Timeouts +77 -0
- data/CONTRIBUTORS +35 -0
- data/COPYING +674 -0
- data/DESIGN +95 -0
- data/Documentation/.gitignore +5 -0
- data/Documentation/GNUmakefile +30 -0
- data/Documentation/unicorn.1.txt +187 -0
- data/Documentation/unicorn_rails.1.txt +175 -0
- data/FAQ +70 -0
- data/GIT-VERSION-FILE +1 -0
- data/GIT-VERSION-GEN +39 -0
- data/GNUmakefile +253 -0
- data/HACKING +120 -0
- data/ISSUES +90 -0
- data/KNOWN_ISSUES +79 -0
- data/LATEST +30 -0
- data/LICENSE +67 -0
- data/Links +56 -0
- data/NEWS +2465 -0
- data/PHILOSOPHY +139 -0
- data/README +138 -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 +126 -0
- data/bin/unicorn_rails +209 -0
- data/examples/big_app_gc.rb +2 -0
- data/examples/echo.ru +27 -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 +155 -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 +33 -0
- data/ext/unicorn_http/CFLAGS +13 -0
- data/ext/unicorn_http/c_util.h +124 -0
- data/ext/unicorn_http/common_field_optimization.h +111 -0
- data/ext/unicorn_http/ext_help.h +62 -0
- data/ext/unicorn_http/extconf.rb +11 -0
- data/ext/unicorn_http/global_variables.h +97 -0
- data/ext/unicorn_http/httpdate.c +78 -0
- data/ext/unicorn_http/unicorn_http.c +4274 -0
- data/ext/unicorn_http/unicorn_http.rl +980 -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 +664 -0
- data/lib/unicorn/const.rb +21 -0
- data/lib/unicorn/http_request.rb +122 -0
- data/lib/unicorn/http_response.rb +60 -0
- data/lib/unicorn/http_server.rb +824 -0
- data/lib/unicorn/launcher.rb +62 -0
- data/lib/unicorn/oob_gc.rb +82 -0
- data/lib/unicorn/preread_input.rb +33 -0
- data/lib/unicorn/socket_helper.rb +195 -0
- data/lib/unicorn/stream_input.rb +146 -0
- data/lib/unicorn/tee_input.rb +133 -0
- data/lib/unicorn/tmpio.rb +27 -0
- data/lib/unicorn/util.rb +90 -0
- data/lib/unicorn/version.rb +1 -0
- data/lib/unicorn/worker.rb +140 -0
- data/lib/unicorn.rb +123 -0
- data/man/man1/unicorn.1 +221 -0
- data/man/man1/unicorn_rails.1 +212 -0
- data/setup.rb +1586 -0
- data/t/.gitignore +4 -0
- data/t/GNUmakefile +74 -0
- data/t/README +42 -0
- data/t/bin/content-md5-put +36 -0
- data/t/bin/sha1sum.rb +17 -0
- data/t/bin/unused_listen +40 -0
- data/t/broken-app.ru +12 -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/hijack.ru +43 -0
- data/t/listener_names.ru +4 -0
- data/t/my-tap-lib.sh +201 -0
- data/t/oob_gc.ru +20 -0
- data/t/oob_gc_path.ru +20 -0
- data/t/pid.ru +3 -0
- data/t/preread_input.ru +17 -0
- data/t/rack-input-tests.ru +21 -0
- data/t/t0000-http-basic.sh +50 -0
- data/t/t0001-reload-bad-config.sh +53 -0
- data/t/t0002-config-conflict.sh +49 -0
- data/t/t0002-parser-error.sh +94 -0
- data/t/t0003-working_directory.sh +51 -0
- data/t/t0004-heartbeat-timeout.sh +69 -0
- data/t/t0004-working_directory_broken.sh +24 -0
- data/t/t0005-working_directory_app.rb.sh +40 -0
- data/t/t0006-reopen-logs.sh +83 -0
- data/t/t0006.ru +13 -0
- data/t/t0007-working_directory_no_embed_cli.sh +44 -0
- data/t/t0008-back_out_of_upgrade.sh +110 -0
- data/t/t0009-broken-app.sh +56 -0
- data/t/t0009-winch_ttin.sh +59 -0
- data/t/t0010-reap-logging.sh +55 -0
- data/t/t0011-active-unix-socket.sh +79 -0
- data/t/t0012-reload-empty-config.sh +85 -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/t0018-write-on-close.sh +23 -0
- data/t/t0019-max_header_len.sh +49 -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/t0100-rack-input-tests.sh +124 -0
- data/t/t0116-client_body_buffer_size.sh +80 -0
- data/t/t0116.ru +16 -0
- data/t/t0200-rack-hijack.sh +30 -0
- data/t/t0300-no-default-middleware.sh +20 -0
- data/t/t9000-preread-input.sh +48 -0
- data/t/t9001-oob_gc.sh +47 -0
- data/t/t9002-oob_gc-path.sh +75 -0
- data/t/test-lib.sh +128 -0
- data/t/write-on-close.ru +11 -0
- data/test/aggregate.rb +15 -0
- data/test/benchmark/README +50 -0
- data/test/benchmark/dd.ru +18 -0
- data/test/benchmark/stack.ru +8 -0
- data/test/exec/README +5 -0
- data/test/exec/test_exec.rb +1099 -0
- data/test/test_helper.rb +298 -0
- data/test/unit/test_configurator.rb +175 -0
- data/test/unit/test_droplet.rb +28 -0
- data/test/unit/test_http_parser.rb +886 -0
- data/test/unit/test_http_parser_ng.rb +633 -0
- data/test/unit/test_request.rb +182 -0
- data/test/unit/test_response.rb +111 -0
- data/test/unit/test_server.rb +268 -0
- data/test/unit/test_signals.rb +188 -0
- data/test/unit/test_socket_helper.rb +197 -0
- data/test/unit/test_stream_input.rb +203 -0
- data/test/unit/test_tee_input.rb +304 -0
- data/test/unit/test_upload.rb +306 -0
- data/test/unit/test_util.rb +105 -0
- data/unicorn.gemspec +50 -0
- metadata +310 -0
|
@@ -0,0 +1,304 @@
|
|
|
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
|
+
|
|
13
|
+
def setup
|
|
14
|
+
@rs = $/
|
|
15
|
+
@rd, @wr = Kgio::UNIXSocket.pair
|
|
16
|
+
@rd.sync = @wr.sync = true
|
|
17
|
+
@start_pid = $$
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def teardown
|
|
21
|
+
return if $$ != @start_pid
|
|
22
|
+
$/ = @rs
|
|
23
|
+
@rd.close rescue nil
|
|
24
|
+
@wr.close rescue nil
|
|
25
|
+
begin
|
|
26
|
+
Process.wait
|
|
27
|
+
rescue Errno::ECHILD
|
|
28
|
+
break
|
|
29
|
+
end while true
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def check_tempfiles
|
|
33
|
+
tmp = @parser.env["rack.tempfiles"]
|
|
34
|
+
assert_instance_of Array, tmp
|
|
35
|
+
assert_operator tmp.size, :>=, 1
|
|
36
|
+
assert_instance_of Unicorn::TmpIO, tmp[0]
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def test_gets_long
|
|
40
|
+
r = init_request("hello", 5 + (4096 * 4 * 3) + "#$/foo#$/".size)
|
|
41
|
+
ti = TeeInput.new(@rd, r)
|
|
42
|
+
status = line = nil
|
|
43
|
+
pid = fork {
|
|
44
|
+
@rd.close
|
|
45
|
+
3.times { @wr.write("ffff" * 4096) }
|
|
46
|
+
@wr.write "#$/foo#$/"
|
|
47
|
+
@wr.close
|
|
48
|
+
}
|
|
49
|
+
@wr.close
|
|
50
|
+
line = ti.gets
|
|
51
|
+
assert_equal(4096 * 4 * 3 + 5 + $/.size, line.size)
|
|
52
|
+
assert_equal("hello" << ("ffff" * 4096 * 3) << "#$/", line)
|
|
53
|
+
line = ti.gets
|
|
54
|
+
assert_equal "foo#$/", line
|
|
55
|
+
assert_nil ti.gets
|
|
56
|
+
pid, status = Process.waitpid2(pid)
|
|
57
|
+
assert status.success?
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def test_gets_short
|
|
61
|
+
r = init_request("hello", 5 + "#$/foo".size)
|
|
62
|
+
ti = TeeInput.new(@rd, r)
|
|
63
|
+
status = line = nil
|
|
64
|
+
pid = fork {
|
|
65
|
+
@rd.close
|
|
66
|
+
@wr.write "#$/foo"
|
|
67
|
+
@wr.close
|
|
68
|
+
}
|
|
69
|
+
@wr.close
|
|
70
|
+
line = ti.gets
|
|
71
|
+
assert_equal("hello#$/", line)
|
|
72
|
+
line = ti.gets
|
|
73
|
+
assert_equal "foo", line
|
|
74
|
+
assert_nil ti.gets
|
|
75
|
+
pid, status = Process.waitpid2(pid)
|
|
76
|
+
assert status.success?
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def test_small_body
|
|
80
|
+
r = init_request('hello')
|
|
81
|
+
ti = TeeInput.new(@rd, r)
|
|
82
|
+
assert_equal 0, @parser.content_length
|
|
83
|
+
assert @parser.body_eof?
|
|
84
|
+
assert_equal StringIO, ti.tmp.class
|
|
85
|
+
assert_equal 0, ti.tmp.pos
|
|
86
|
+
assert_equal 5, ti.size
|
|
87
|
+
assert_equal 'hello', ti.read
|
|
88
|
+
assert_equal '', ti.read
|
|
89
|
+
assert_nil ti.read(4096)
|
|
90
|
+
assert_equal 5, ti.size
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def test_read_with_buffer
|
|
94
|
+
r = init_request('hello')
|
|
95
|
+
ti = TeeInput.new(@rd, r)
|
|
96
|
+
buf = ''
|
|
97
|
+
rv = ti.read(4, buf)
|
|
98
|
+
assert_equal 'hell', rv
|
|
99
|
+
assert_equal 'hell', buf
|
|
100
|
+
assert_equal rv.object_id, buf.object_id
|
|
101
|
+
assert_equal 'o', ti.read
|
|
102
|
+
assert_equal nil, ti.read(5, buf)
|
|
103
|
+
assert_equal 0, ti.rewind
|
|
104
|
+
assert_equal 'hello', ti.read(5, buf)
|
|
105
|
+
assert_equal 'hello', buf
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def test_big_body
|
|
109
|
+
r = init_request('.' * Unicorn::Const::MAX_BODY << 'a')
|
|
110
|
+
ti = TeeInput.new(@rd, r)
|
|
111
|
+
assert_equal 0, @parser.content_length
|
|
112
|
+
assert @parser.body_eof?
|
|
113
|
+
assert_kind_of File, ti.tmp
|
|
114
|
+
assert_equal 0, ti.tmp.pos
|
|
115
|
+
assert_equal Unicorn::Const::MAX_BODY + 1, ti.size
|
|
116
|
+
check_tempfiles
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def test_read_in_full_if_content_length
|
|
120
|
+
a, b = 300, 3
|
|
121
|
+
r = init_request('.' * b, 300)
|
|
122
|
+
assert_equal 300, @parser.content_length
|
|
123
|
+
ti = TeeInput.new(@rd, r)
|
|
124
|
+
pid = fork {
|
|
125
|
+
@wr.write('.' * 197)
|
|
126
|
+
sleep 1 # still a *potential* race here that would make the test moot...
|
|
127
|
+
@wr.write('.' * 100)
|
|
128
|
+
}
|
|
129
|
+
assert_equal a, ti.read(a).size
|
|
130
|
+
_, status = Process.waitpid2(pid)
|
|
131
|
+
assert status.success?
|
|
132
|
+
@wr.close
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def test_big_body_multi
|
|
136
|
+
r = init_request('.', Unicorn::Const::MAX_BODY + 1)
|
|
137
|
+
ti = TeeInput.new(@rd, r)
|
|
138
|
+
assert_equal Unicorn::Const::MAX_BODY, @parser.content_length
|
|
139
|
+
assert ! @parser.body_eof?
|
|
140
|
+
assert_kind_of File, ti.tmp
|
|
141
|
+
assert_equal 0, ti.tmp.pos
|
|
142
|
+
assert_equal Unicorn::Const::MAX_BODY + 1, ti.size
|
|
143
|
+
nr = Unicorn::Const::MAX_BODY / 4
|
|
144
|
+
pid = fork {
|
|
145
|
+
@rd.close
|
|
146
|
+
nr.times { @wr.write('....') }
|
|
147
|
+
@wr.close
|
|
148
|
+
}
|
|
149
|
+
@wr.close
|
|
150
|
+
assert_equal '.', ti.read(1)
|
|
151
|
+
assert_equal Unicorn::Const::MAX_BODY + 1, ti.size
|
|
152
|
+
nr.times { |x|
|
|
153
|
+
assert_equal '....', ti.read(4), "nr=#{x}"
|
|
154
|
+
assert_equal Unicorn::Const::MAX_BODY + 1, ti.size
|
|
155
|
+
}
|
|
156
|
+
assert_nil ti.read(1)
|
|
157
|
+
pid, status = Process.waitpid2(pid)
|
|
158
|
+
assert status.success?
|
|
159
|
+
check_tempfiles
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def test_chunked
|
|
163
|
+
@parser = Unicorn::HttpParser.new
|
|
164
|
+
@parser.buf << "POST / HTTP/1.1\r\n" \
|
|
165
|
+
"Host: localhost\r\n" \
|
|
166
|
+
"Transfer-Encoding: chunked\r\n" \
|
|
167
|
+
"\r\n"
|
|
168
|
+
assert @parser.parse
|
|
169
|
+
assert_equal "", @parser.buf
|
|
170
|
+
|
|
171
|
+
pid = fork {
|
|
172
|
+
@rd.close
|
|
173
|
+
5.times { @wr.write("5\r\nabcde\r\n") }
|
|
174
|
+
@wr.write("0\r\n\r\n")
|
|
175
|
+
}
|
|
176
|
+
@wr.close
|
|
177
|
+
ti = TeeInput.new(@rd, @parser)
|
|
178
|
+
assert_nil @parser.content_length
|
|
179
|
+
assert_nil ti.len
|
|
180
|
+
assert ! @parser.body_eof?
|
|
181
|
+
assert_equal 25, ti.size
|
|
182
|
+
assert @parser.body_eof?
|
|
183
|
+
assert_equal 25, ti.len
|
|
184
|
+
assert_equal 0, ti.tmp.pos
|
|
185
|
+
ti.rewind
|
|
186
|
+
assert_equal 0, ti.tmp.pos
|
|
187
|
+
assert_equal 'abcdeabcdeabcdeabcde', ti.read(20)
|
|
188
|
+
assert_equal 20, ti.tmp.pos
|
|
189
|
+
ti.rewind
|
|
190
|
+
assert_equal 0, ti.tmp.pos
|
|
191
|
+
assert_kind_of File, ti.tmp
|
|
192
|
+
status = nil
|
|
193
|
+
pid, status = Process.waitpid2(pid)
|
|
194
|
+
assert status.success?
|
|
195
|
+
check_tempfiles
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def test_chunked_ping_pong
|
|
199
|
+
@parser = Unicorn::HttpParser.new
|
|
200
|
+
buf = @parser.buf
|
|
201
|
+
buf << "POST / HTTP/1.1\r\n" \
|
|
202
|
+
"Host: localhost\r\n" \
|
|
203
|
+
"Transfer-Encoding: chunked\r\n" \
|
|
204
|
+
"\r\n"
|
|
205
|
+
assert @parser.parse
|
|
206
|
+
assert_equal "", buf
|
|
207
|
+
chunks = %w(aa bbb cccc dddd eeee)
|
|
208
|
+
rd, wr = IO.pipe
|
|
209
|
+
|
|
210
|
+
pid = fork {
|
|
211
|
+
chunks.each do |chunk|
|
|
212
|
+
rd.read(1) == "." and
|
|
213
|
+
@wr.write("#{'%x' % [ chunk.size]}\r\n#{chunk}\r\n")
|
|
214
|
+
end
|
|
215
|
+
@wr.write("0\r\n\r\n")
|
|
216
|
+
}
|
|
217
|
+
ti = TeeInput.new(@rd, @parser)
|
|
218
|
+
assert_nil @parser.content_length
|
|
219
|
+
assert_nil ti.len
|
|
220
|
+
assert ! @parser.body_eof?
|
|
221
|
+
chunks.each do |chunk|
|
|
222
|
+
wr.write('.')
|
|
223
|
+
assert_equal chunk, ti.read(16384)
|
|
224
|
+
end
|
|
225
|
+
_, status = Process.waitpid2(pid)
|
|
226
|
+
assert status.success?
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def test_chunked_with_trailer
|
|
230
|
+
@parser = Unicorn::HttpParser.new
|
|
231
|
+
buf = @parser.buf
|
|
232
|
+
buf << "POST / HTTP/1.1\r\n" \
|
|
233
|
+
"Host: localhost\r\n" \
|
|
234
|
+
"Trailer: Hello\r\n" \
|
|
235
|
+
"Transfer-Encoding: chunked\r\n" \
|
|
236
|
+
"\r\n"
|
|
237
|
+
assert @parser.parse
|
|
238
|
+
assert_equal "", buf
|
|
239
|
+
|
|
240
|
+
pid = fork {
|
|
241
|
+
@rd.close
|
|
242
|
+
5.times { @wr.write("5\r\nabcde\r\n") }
|
|
243
|
+
@wr.write("0\r\n")
|
|
244
|
+
@wr.write("Hello: World\r\n\r\n")
|
|
245
|
+
}
|
|
246
|
+
@wr.close
|
|
247
|
+
ti = TeeInput.new(@rd, @parser)
|
|
248
|
+
assert_nil @parser.content_length
|
|
249
|
+
assert_nil ti.len
|
|
250
|
+
assert ! @parser.body_eof?
|
|
251
|
+
assert_equal 25, ti.size
|
|
252
|
+
assert_equal "World", @parser.env['HTTP_HELLO']
|
|
253
|
+
pid, status = Process.waitpid2(pid)
|
|
254
|
+
assert status.success?
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def test_chunked_and_size_slow
|
|
258
|
+
@parser = Unicorn::HttpParser.new
|
|
259
|
+
buf = @parser.buf
|
|
260
|
+
buf << "POST / HTTP/1.1\r\n" \
|
|
261
|
+
"Host: localhost\r\n" \
|
|
262
|
+
"Trailer: Hello\r\n" \
|
|
263
|
+
"Transfer-Encoding: chunked\r\n" \
|
|
264
|
+
"\r\n"
|
|
265
|
+
assert @parser.parse
|
|
266
|
+
assert_equal "", buf
|
|
267
|
+
|
|
268
|
+
@wr.write("9\r\nabcde")
|
|
269
|
+
ti = TeeInput.new(@rd, @parser)
|
|
270
|
+
assert_nil @parser.content_length
|
|
271
|
+
assert_equal "abcde", ti.read(9)
|
|
272
|
+
assert ! @parser.body_eof?
|
|
273
|
+
@wr.write("fghi\r\n0\r\nHello: World\r\n\r\n")
|
|
274
|
+
assert_equal 9, ti.size
|
|
275
|
+
assert_equal "fghi", ti.read(9)
|
|
276
|
+
assert_equal nil, ti.read(9)
|
|
277
|
+
assert_equal "World", @parser.env['HTTP_HELLO']
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
def test_gets_read_mix
|
|
281
|
+
r = init_request("hello\nasdfasdf")
|
|
282
|
+
ti = Unicorn::TeeInput.new(@rd, r)
|
|
283
|
+
assert_equal "hello\n", ti.gets
|
|
284
|
+
assert_equal "asdfasdf", ti.read(9)
|
|
285
|
+
assert_nil ti.read(9)
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
private
|
|
289
|
+
|
|
290
|
+
def init_request(body, size = nil)
|
|
291
|
+
@parser = Unicorn::HttpParser.new
|
|
292
|
+
body = body.to_s.freeze
|
|
293
|
+
buf = @parser.buf
|
|
294
|
+
buf << "POST / HTTP/1.1\r\n" \
|
|
295
|
+
"Host: localhost\r\n" \
|
|
296
|
+
"Content-Length: #{size || body.size}\r\n" \
|
|
297
|
+
"\r\n#{body}"
|
|
298
|
+
assert @parser.parse
|
|
299
|
+
assert_equal body, buf
|
|
300
|
+
@buf = buf
|
|
301
|
+
@parser
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
end
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
# -*- encoding: binary -*-
|
|
2
|
+
|
|
3
|
+
# Copyright (c) 2009 Eric Wong
|
|
4
|
+
require './test/test_helper'
|
|
5
|
+
require 'digest/md5'
|
|
6
|
+
|
|
7
|
+
include Unicorn
|
|
8
|
+
|
|
9
|
+
class UploadTest < Test::Unit::TestCase
|
|
10
|
+
|
|
11
|
+
def setup
|
|
12
|
+
@addr = ENV['UNICORN_TEST_ADDR'] || '127.0.0.1'
|
|
13
|
+
@port = unused_port
|
|
14
|
+
@hdr = {'Content-Type' => 'text/plain', 'Content-Length' => '0'}
|
|
15
|
+
@bs = 4096
|
|
16
|
+
@count = 256
|
|
17
|
+
@server = nil
|
|
18
|
+
|
|
19
|
+
# we want random binary data to test 1.9 encoding-aware IO craziness
|
|
20
|
+
@random = File.open('/dev/urandom','rb')
|
|
21
|
+
@sha1 = Digest::SHA1.new
|
|
22
|
+
@sha1_app = lambda do |env|
|
|
23
|
+
input = env['rack.input']
|
|
24
|
+
resp = {}
|
|
25
|
+
|
|
26
|
+
@sha1.reset
|
|
27
|
+
while buf = input.read(@bs)
|
|
28
|
+
@sha1.update(buf)
|
|
29
|
+
end
|
|
30
|
+
resp[:sha1] = @sha1.hexdigest
|
|
31
|
+
|
|
32
|
+
# rewind and read again
|
|
33
|
+
input.rewind
|
|
34
|
+
@sha1.reset
|
|
35
|
+
while buf = input.read(@bs)
|
|
36
|
+
@sha1.update(buf)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
if resp[:sha1] == @sha1.hexdigest
|
|
40
|
+
resp[:sysread_read_byte_match] = true
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
if expect_size = env['HTTP_X_EXPECT_SIZE']
|
|
44
|
+
if expect_size.to_i == input.size
|
|
45
|
+
resp[:expect_size_match] = true
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
resp[:size] = input.size
|
|
49
|
+
resp[:content_md5] = env['HTTP_CONTENT_MD5']
|
|
50
|
+
|
|
51
|
+
[ 200, @hdr.merge({'X-Resp' => resp.inspect}), [] ]
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def teardown
|
|
56
|
+
redirect_test_io { @server.stop(false) } if @server
|
|
57
|
+
@random.close
|
|
58
|
+
reset_sig_handlers
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def test_put
|
|
62
|
+
start_server(@sha1_app)
|
|
63
|
+
sock = TCPSocket.new(@addr, @port)
|
|
64
|
+
sock.syswrite("PUT / HTTP/1.0\r\nContent-Length: #{length}\r\n\r\n")
|
|
65
|
+
@count.times do |i|
|
|
66
|
+
buf = @random.sysread(@bs)
|
|
67
|
+
@sha1.update(buf)
|
|
68
|
+
sock.syswrite(buf)
|
|
69
|
+
end
|
|
70
|
+
read = sock.read.split(/\r\n/)
|
|
71
|
+
assert_equal "HTTP/1.1 200 OK", read[0]
|
|
72
|
+
resp = eval(read.grep(/^X-Resp: /).first.sub!(/X-Resp: /, ''))
|
|
73
|
+
assert_equal length, resp[:size]
|
|
74
|
+
assert_equal @sha1.hexdigest, resp[:sha1]
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def test_put_content_md5
|
|
78
|
+
md5 = Digest::MD5.new
|
|
79
|
+
start_server(@sha1_app)
|
|
80
|
+
sock = TCPSocket.new(@addr, @port)
|
|
81
|
+
sock.syswrite("PUT / HTTP/1.0\r\nTransfer-Encoding: chunked\r\n" \
|
|
82
|
+
"Trailer: Content-MD5\r\n\r\n")
|
|
83
|
+
@count.times do |i|
|
|
84
|
+
buf = @random.sysread(@bs)
|
|
85
|
+
@sha1.update(buf)
|
|
86
|
+
md5.update(buf)
|
|
87
|
+
sock.syswrite("#{'%x' % buf.size}\r\n")
|
|
88
|
+
sock.syswrite(buf << "\r\n")
|
|
89
|
+
end
|
|
90
|
+
sock.syswrite("0\r\n")
|
|
91
|
+
|
|
92
|
+
content_md5 = [ md5.digest! ].pack('m').strip.freeze
|
|
93
|
+
sock.syswrite("Content-MD5: #{content_md5}\r\n\r\n")
|
|
94
|
+
read = sock.read.split(/\r\n/)
|
|
95
|
+
assert_equal "HTTP/1.1 200 OK", read[0]
|
|
96
|
+
resp = eval(read.grep(/^X-Resp: /).first.sub!(/X-Resp: /, ''))
|
|
97
|
+
assert_equal length, resp[:size]
|
|
98
|
+
assert_equal @sha1.hexdigest, resp[:sha1]
|
|
99
|
+
assert_equal content_md5, resp[:content_md5]
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def test_put_trickle_small
|
|
103
|
+
@count, @bs = 2, 128
|
|
104
|
+
start_server(@sha1_app)
|
|
105
|
+
assert_equal 256, length
|
|
106
|
+
sock = TCPSocket.new(@addr, @port)
|
|
107
|
+
hdr = "PUT / HTTP/1.0\r\nContent-Length: #{length}\r\n\r\n"
|
|
108
|
+
@count.times do
|
|
109
|
+
buf = @random.sysread(@bs)
|
|
110
|
+
@sha1.update(buf)
|
|
111
|
+
hdr << buf
|
|
112
|
+
sock.syswrite(hdr)
|
|
113
|
+
hdr = ''
|
|
114
|
+
sleep 0.6
|
|
115
|
+
end
|
|
116
|
+
read = sock.read.split(/\r\n/)
|
|
117
|
+
assert_equal "HTTP/1.1 200 OK", read[0]
|
|
118
|
+
resp = eval(read.grep(/^X-Resp: /).first.sub!(/X-Resp: /, ''))
|
|
119
|
+
assert_equal length, resp[:size]
|
|
120
|
+
assert_equal @sha1.hexdigest, resp[:sha1]
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def test_put_keepalive_truncates_small_overwrite
|
|
124
|
+
start_server(@sha1_app)
|
|
125
|
+
sock = TCPSocket.new(@addr, @port)
|
|
126
|
+
to_upload = length + 1
|
|
127
|
+
sock.syswrite("PUT / HTTP/1.0\r\nContent-Length: #{to_upload}\r\n\r\n")
|
|
128
|
+
@count.times do
|
|
129
|
+
buf = @random.sysread(@bs)
|
|
130
|
+
@sha1.update(buf)
|
|
131
|
+
sock.syswrite(buf)
|
|
132
|
+
end
|
|
133
|
+
sock.syswrite('12345') # write 4 bytes more than we expected
|
|
134
|
+
@sha1.update('1')
|
|
135
|
+
|
|
136
|
+
buf = sock.readpartial(4096)
|
|
137
|
+
while buf !~ /\r\n\r\n/
|
|
138
|
+
buf << sock.readpartial(4096)
|
|
139
|
+
end
|
|
140
|
+
read = buf.split(/\r\n/)
|
|
141
|
+
assert_equal "HTTP/1.1 200 OK", read[0]
|
|
142
|
+
resp = eval(read.grep(/^X-Resp: /).first.sub!(/X-Resp: /, ''))
|
|
143
|
+
assert_equal to_upload, resp[:size]
|
|
144
|
+
assert_equal @sha1.hexdigest, resp[:sha1]
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def test_put_excessive_overwrite_closed
|
|
148
|
+
tmp = Tempfile.new('overwrite_check')
|
|
149
|
+
tmp.sync = true
|
|
150
|
+
start_server(lambda { |env|
|
|
151
|
+
nr = 0
|
|
152
|
+
while buf = env['rack.input'].read(65536)
|
|
153
|
+
nr += buf.size
|
|
154
|
+
end
|
|
155
|
+
tmp.write(nr.to_s)
|
|
156
|
+
[ 200, @hdr, [] ]
|
|
157
|
+
})
|
|
158
|
+
sock = TCPSocket.new(@addr, @port)
|
|
159
|
+
buf = ' ' * @bs
|
|
160
|
+
sock.syswrite("PUT / HTTP/1.0\r\nContent-Length: #{length}\r\n\r\n")
|
|
161
|
+
|
|
162
|
+
@count.times { sock.syswrite(buf) }
|
|
163
|
+
assert_raise(Errno::ECONNRESET, Errno::EPIPE) do
|
|
164
|
+
::Unicorn::Const::CHUNK_SIZE.times { sock.syswrite(buf) }
|
|
165
|
+
end
|
|
166
|
+
sock.gets
|
|
167
|
+
tmp.rewind
|
|
168
|
+
assert_equal length, tmp.read.to_i
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# Despite reading numerous articles and inspecting the 1.9.1-p0 C
|
|
172
|
+
# source, Eric Wong will never trust that we're always handling
|
|
173
|
+
# encoding-aware IO objects correctly. Thus this test uses shell
|
|
174
|
+
# utilities that should always operate on files/sockets on a
|
|
175
|
+
# byte-level.
|
|
176
|
+
def test_uncomfortable_with_onenine_encodings
|
|
177
|
+
# POSIX doesn't require all of these to be present on a system
|
|
178
|
+
which('curl') or return
|
|
179
|
+
which('sha1sum') or return
|
|
180
|
+
which('dd') or return
|
|
181
|
+
|
|
182
|
+
start_server(@sha1_app)
|
|
183
|
+
|
|
184
|
+
tmp = Tempfile.new('dd_dest')
|
|
185
|
+
assert(system("dd", "if=#{@random.path}", "of=#{tmp.path}",
|
|
186
|
+
"bs=#{@bs}", "count=#{@count}"),
|
|
187
|
+
"dd #@random to #{tmp}")
|
|
188
|
+
sha1_re = %r!\b([a-f0-9]{40})\b!
|
|
189
|
+
sha1_out = `sha1sum #{tmp.path}`
|
|
190
|
+
assert $?.success?, 'sha1sum ran OK'
|
|
191
|
+
|
|
192
|
+
assert_match(sha1_re, sha1_out)
|
|
193
|
+
sha1 = sha1_re.match(sha1_out)[1]
|
|
194
|
+
resp = `curl -isSfN -T#{tmp.path} http://#@addr:#@port/`
|
|
195
|
+
assert $?.success?, 'curl ran OK'
|
|
196
|
+
assert_match(%r!\b#{sha1}\b!, resp)
|
|
197
|
+
assert_match(/sysread_read_byte_match/, resp)
|
|
198
|
+
|
|
199
|
+
# small StringIO path
|
|
200
|
+
assert(system("dd", "if=#{@random.path}", "of=#{tmp.path}",
|
|
201
|
+
"bs=1024", "count=1"),
|
|
202
|
+
"dd #@random to #{tmp}")
|
|
203
|
+
sha1_re = %r!\b([a-f0-9]{40})\b!
|
|
204
|
+
sha1_out = `sha1sum #{tmp.path}`
|
|
205
|
+
assert $?.success?, 'sha1sum ran OK'
|
|
206
|
+
|
|
207
|
+
assert_match(sha1_re, sha1_out)
|
|
208
|
+
sha1 = sha1_re.match(sha1_out)[1]
|
|
209
|
+
resp = `curl -isSfN -T#{tmp.path} http://#@addr:#@port/`
|
|
210
|
+
assert $?.success?, 'curl ran OK'
|
|
211
|
+
assert_match(%r!\b#{sha1}\b!, resp)
|
|
212
|
+
assert_match(/sysread_read_byte_match/, resp)
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def test_chunked_upload_via_curl
|
|
216
|
+
# POSIX doesn't require all of these to be present on a system
|
|
217
|
+
which('curl') or return
|
|
218
|
+
which('sha1sum') or return
|
|
219
|
+
which('dd') or return
|
|
220
|
+
|
|
221
|
+
start_server(@sha1_app)
|
|
222
|
+
|
|
223
|
+
tmp = Tempfile.new('dd_dest')
|
|
224
|
+
assert(system("dd", "if=#{@random.path}", "of=#{tmp.path}",
|
|
225
|
+
"bs=#{@bs}", "count=#{@count}"),
|
|
226
|
+
"dd #@random to #{tmp}")
|
|
227
|
+
sha1_re = %r!\b([a-f0-9]{40})\b!
|
|
228
|
+
sha1_out = `sha1sum #{tmp.path}`
|
|
229
|
+
assert $?.success?, 'sha1sum ran OK'
|
|
230
|
+
|
|
231
|
+
assert_match(sha1_re, sha1_out)
|
|
232
|
+
sha1 = sha1_re.match(sha1_out)[1]
|
|
233
|
+
cmd = "curl -H 'X-Expect-Size: #{tmp.size}' --tcp-nodelay \
|
|
234
|
+
-isSf --no-buffer -T- " \
|
|
235
|
+
"http://#@addr:#@port/"
|
|
236
|
+
resp = Tempfile.new('resp')
|
|
237
|
+
resp.sync = true
|
|
238
|
+
|
|
239
|
+
rd, wr = IO.pipe
|
|
240
|
+
wr.sync = rd.sync = true
|
|
241
|
+
pid = fork {
|
|
242
|
+
STDIN.reopen(rd)
|
|
243
|
+
rd.close
|
|
244
|
+
wr.close
|
|
245
|
+
STDOUT.reopen(resp)
|
|
246
|
+
exec cmd
|
|
247
|
+
}
|
|
248
|
+
rd.close
|
|
249
|
+
|
|
250
|
+
tmp.rewind
|
|
251
|
+
@count.times { |i|
|
|
252
|
+
wr.write(tmp.read(@bs))
|
|
253
|
+
sleep(rand / 10) if 0 == i % 8
|
|
254
|
+
}
|
|
255
|
+
wr.close
|
|
256
|
+
pid, status = Process.waitpid2(pid)
|
|
257
|
+
|
|
258
|
+
resp.rewind
|
|
259
|
+
resp = resp.read
|
|
260
|
+
assert status.success?, 'curl ran OK'
|
|
261
|
+
assert_match(%r!\b#{sha1}\b!, resp)
|
|
262
|
+
assert_match(/sysread_read_byte_match/, resp)
|
|
263
|
+
assert_match(/expect_size_match/, resp)
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
def test_curl_chunked_small
|
|
267
|
+
# POSIX doesn't require all of these to be present on a system
|
|
268
|
+
which('curl') or return
|
|
269
|
+
which('sha1sum') or return
|
|
270
|
+
which('dd') or return
|
|
271
|
+
|
|
272
|
+
start_server(@sha1_app)
|
|
273
|
+
|
|
274
|
+
tmp = Tempfile.new('dd_dest')
|
|
275
|
+
# small StringIO path
|
|
276
|
+
assert(system("dd", "if=#{@random.path}", "of=#{tmp.path}",
|
|
277
|
+
"bs=1024", "count=1"),
|
|
278
|
+
"dd #@random to #{tmp}")
|
|
279
|
+
sha1_re = %r!\b([a-f0-9]{40})\b!
|
|
280
|
+
sha1_out = `sha1sum #{tmp.path}`
|
|
281
|
+
assert $?.success?, 'sha1sum ran OK'
|
|
282
|
+
|
|
283
|
+
assert_match(sha1_re, sha1_out)
|
|
284
|
+
sha1 = sha1_re.match(sha1_out)[1]
|
|
285
|
+
resp = `curl -H 'X-Expect-Size: #{tmp.size}' --tcp-nodelay \
|
|
286
|
+
-isSf --no-buffer -T- http://#@addr:#@port/ < #{tmp.path}`
|
|
287
|
+
assert $?.success?, 'curl ran OK'
|
|
288
|
+
assert_match(%r!\b#{sha1}\b!, resp)
|
|
289
|
+
assert_match(/sysread_read_byte_match/, resp)
|
|
290
|
+
assert_match(/expect_size_match/, resp)
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
private
|
|
294
|
+
|
|
295
|
+
def length
|
|
296
|
+
@bs * @count
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
def start_server(app)
|
|
300
|
+
redirect_test_io do
|
|
301
|
+
@server = HttpServer.new(app, :listeners => [ "#{@addr}:#{@port}" ] )
|
|
302
|
+
@server.start
|
|
303
|
+
end
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
end
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# -*- encoding: binary -*-
|
|
2
|
+
|
|
3
|
+
require './test/test_helper'
|
|
4
|
+
require 'tempfile'
|
|
5
|
+
|
|
6
|
+
class TestUtil < Test::Unit::TestCase
|
|
7
|
+
|
|
8
|
+
EXPECT_FLAGS = File::WRONLY | File::APPEND
|
|
9
|
+
def test_reopen_logs_noop
|
|
10
|
+
tmp = Tempfile.new('')
|
|
11
|
+
fp = File.open(tmp.path, 'ab')
|
|
12
|
+
fp.sync = true
|
|
13
|
+
ext = fp.external_encoding rescue nil
|
|
14
|
+
int = fp.internal_encoding rescue nil
|
|
15
|
+
before = fp.stat.inspect
|
|
16
|
+
Unicorn::Util.reopen_logs
|
|
17
|
+
assert_equal before, File.stat(fp.path).inspect
|
|
18
|
+
assert_equal ext, (fp.external_encoding rescue nil)
|
|
19
|
+
assert_equal int, (fp.internal_encoding rescue nil)
|
|
20
|
+
assert_equal(EXPECT_FLAGS, EXPECT_FLAGS & fp.fcntl(Fcntl::F_GETFL))
|
|
21
|
+
tmp.close!
|
|
22
|
+
fp.close
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def test_reopen_logs_renamed
|
|
26
|
+
tmp = Tempfile.new('')
|
|
27
|
+
tmp_path = tmp.path.freeze
|
|
28
|
+
fp = File.open(tmp_path, 'ab')
|
|
29
|
+
fp.sync = true
|
|
30
|
+
|
|
31
|
+
ext = fp.external_encoding rescue nil
|
|
32
|
+
int = fp.internal_encoding rescue nil
|
|
33
|
+
before = fp.stat.inspect
|
|
34
|
+
to = Tempfile.new('')
|
|
35
|
+
File.rename(tmp_path, to.path)
|
|
36
|
+
assert ! File.exist?(tmp_path)
|
|
37
|
+
Unicorn::Util.reopen_logs
|
|
38
|
+
assert_equal tmp_path, tmp.path
|
|
39
|
+
assert File.exist?(tmp_path)
|
|
40
|
+
assert before != File.stat(tmp_path).inspect
|
|
41
|
+
assert_equal fp.stat.inspect, File.stat(tmp_path).inspect
|
|
42
|
+
assert_equal ext, (fp.external_encoding rescue nil)
|
|
43
|
+
assert_equal int, (fp.internal_encoding rescue nil)
|
|
44
|
+
assert_equal(EXPECT_FLAGS, EXPECT_FLAGS & fp.fcntl(Fcntl::F_GETFL))
|
|
45
|
+
assert fp.sync
|
|
46
|
+
tmp.close!
|
|
47
|
+
to.close!
|
|
48
|
+
fp.close
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def test_reopen_logs_renamed_with_encoding
|
|
52
|
+
tmp = Tempfile.new('')
|
|
53
|
+
tmp_path = tmp.path.dup.freeze
|
|
54
|
+
Encoding.list.each { |encoding|
|
|
55
|
+
File.open(tmp_path, "a:#{encoding.to_s}") { |fp|
|
|
56
|
+
fp.sync = true
|
|
57
|
+
assert_equal encoding, fp.external_encoding
|
|
58
|
+
assert_nil fp.internal_encoding
|
|
59
|
+
File.unlink(tmp_path)
|
|
60
|
+
assert ! File.exist?(tmp_path)
|
|
61
|
+
Unicorn::Util.reopen_logs
|
|
62
|
+
assert_equal tmp_path, fp.path
|
|
63
|
+
assert File.exist?(tmp_path)
|
|
64
|
+
assert_equal fp.stat.inspect, File.stat(tmp_path).inspect
|
|
65
|
+
assert_equal encoding, fp.external_encoding
|
|
66
|
+
assert_nil fp.internal_encoding
|
|
67
|
+
assert_equal(EXPECT_FLAGS, EXPECT_FLAGS & fp.fcntl(Fcntl::F_GETFL))
|
|
68
|
+
assert fp.sync
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
tmp.close!
|
|
72
|
+
end if STDIN.respond_to?(:external_encoding)
|
|
73
|
+
|
|
74
|
+
def test_reopen_logs_renamed_with_internal_encoding
|
|
75
|
+
tmp = Tempfile.new('')
|
|
76
|
+
tmp_path = tmp.path.dup.freeze
|
|
77
|
+
Encoding.list.each { |ext|
|
|
78
|
+
Encoding.list.each { |int|
|
|
79
|
+
next if ext == int
|
|
80
|
+
File.open(tmp_path, "a:#{ext.to_s}:#{int.to_s}") { |fp|
|
|
81
|
+
fp.sync = true
|
|
82
|
+
assert_equal ext, fp.external_encoding
|
|
83
|
+
|
|
84
|
+
if ext != Encoding::BINARY
|
|
85
|
+
assert_equal int, fp.internal_encoding
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
File.unlink(tmp_path)
|
|
89
|
+
assert ! File.exist?(tmp_path)
|
|
90
|
+
Unicorn::Util.reopen_logs
|
|
91
|
+
assert_equal tmp_path, fp.path
|
|
92
|
+
assert File.exist?(tmp_path)
|
|
93
|
+
assert_equal fp.stat.inspect, File.stat(tmp_path).inspect
|
|
94
|
+
assert_equal ext, fp.external_encoding
|
|
95
|
+
if ext != Encoding::BINARY
|
|
96
|
+
assert_equal int, fp.internal_encoding
|
|
97
|
+
end
|
|
98
|
+
assert_equal(EXPECT_FLAGS, EXPECT_FLAGS & fp.fcntl(Fcntl::F_GETFL))
|
|
99
|
+
assert fp.sync
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
tmp.close!
|
|
104
|
+
end if STDIN.respond_to?(:external_encoding)
|
|
105
|
+
end
|