unicorn-academia 4.7.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.
Files changed (159) hide show
  1. checksums.yaml +7 -0
  2. data/.CHANGELOG.old +25 -0
  3. data/.document +29 -0
  4. data/.gitignore +25 -0
  5. data/.mailmap +26 -0
  6. data/.wrongdoc.yml +10 -0
  7. data/Application_Timeouts +77 -0
  8. data/CONTRIBUTORS +35 -0
  9. data/COPYING +674 -0
  10. data/DESIGN +97 -0
  11. data/Documentation/.gitignore +5 -0
  12. data/Documentation/GNUmakefile +30 -0
  13. data/Documentation/unicorn.1.txt +178 -0
  14. data/Documentation/unicorn_rails.1.txt +175 -0
  15. data/FAQ +53 -0
  16. data/GIT-VERSION-GEN +39 -0
  17. data/GNUmakefile +266 -0
  18. data/HACKING +134 -0
  19. data/ISSUES +36 -0
  20. data/KNOWN_ISSUES +79 -0
  21. data/LICENSE +67 -0
  22. data/Links +56 -0
  23. data/PHILOSOPHY +145 -0
  24. data/README +150 -0
  25. data/Rakefile +60 -0
  26. data/SIGNALS +114 -0
  27. data/Sandbox +103 -0
  28. data/TODO +5 -0
  29. data/TUNING +98 -0
  30. data/bin/unicorn +126 -0
  31. data/bin/unicorn_rails +209 -0
  32. data/examples/big_app_gc.rb +2 -0
  33. data/examples/echo.ru +27 -0
  34. data/examples/git.ru +13 -0
  35. data/examples/init.sh +74 -0
  36. data/examples/logger_mp_safe.rb +25 -0
  37. data/examples/logrotate.conf +29 -0
  38. data/examples/nginx.conf +156 -0
  39. data/examples/unicorn.conf.minimal.rb +13 -0
  40. data/examples/unicorn.conf.rb +102 -0
  41. data/ext/unicorn_http/CFLAGS +13 -0
  42. data/ext/unicorn_http/c_util.h +124 -0
  43. data/ext/unicorn_http/common_field_optimization.h +111 -0
  44. data/ext/unicorn_http/ext_help.h +82 -0
  45. data/ext/unicorn_http/extconf.rb +10 -0
  46. data/ext/unicorn_http/global_variables.h +97 -0
  47. data/ext/unicorn_http/httpdate.c +78 -0
  48. data/ext/unicorn_http/unicorn_http.rl +1036 -0
  49. data/ext/unicorn_http/unicorn_http_common.rl +76 -0
  50. data/lib/unicorn.rb +113 -0
  51. data/lib/unicorn/app/exec_cgi.rb +154 -0
  52. data/lib/unicorn/app/inetd.rb +109 -0
  53. data/lib/unicorn/app/old_rails.rb +35 -0
  54. data/lib/unicorn/app/old_rails/static.rb +59 -0
  55. data/lib/unicorn/cgi_wrapper.rb +147 -0
  56. data/lib/unicorn/configurator.rb +679 -0
  57. data/lib/unicorn/const.rb +44 -0
  58. data/lib/unicorn/http_request.rb +122 -0
  59. data/lib/unicorn/http_response.rb +75 -0
  60. data/lib/unicorn/http_server.rb +808 -0
  61. data/lib/unicorn/launcher.rb +62 -0
  62. data/lib/unicorn/oob_gc.rb +71 -0
  63. data/lib/unicorn/preread_input.rb +33 -0
  64. data/lib/unicorn/socket_helper.rb +231 -0
  65. data/lib/unicorn/ssl_client.rb +11 -0
  66. data/lib/unicorn/ssl_configurator.rb +104 -0
  67. data/lib/unicorn/ssl_server.rb +42 -0
  68. data/lib/unicorn/stream_input.rb +149 -0
  69. data/lib/unicorn/tee_input.rb +126 -0
  70. data/lib/unicorn/tmpio.rb +29 -0
  71. data/lib/unicorn/util.rb +89 -0
  72. data/lib/unicorn/worker.rb +88 -0
  73. data/local.mk.sample +59 -0
  74. data/script/isolate_for_tests +32 -0
  75. data/setup.rb +1586 -0
  76. data/t/.gitignore +5 -0
  77. data/t/GNUmakefile +82 -0
  78. data/t/README +42 -0
  79. data/t/bin/content-md5-put +36 -0
  80. data/t/bin/sha1sum.rb +17 -0
  81. data/t/bin/unused_listen +40 -0
  82. data/t/broken-app.ru +12 -0
  83. data/t/detach.ru +11 -0
  84. data/t/env.ru +3 -0
  85. data/t/fails-rack-lint.ru +5 -0
  86. data/t/heartbeat-timeout.ru +12 -0
  87. data/t/hijack.ru +42 -0
  88. data/t/listener_names.ru +4 -0
  89. data/t/my-tap-lib.sh +201 -0
  90. data/t/oob_gc.ru +20 -0
  91. data/t/oob_gc_path.ru +20 -0
  92. data/t/pid.ru +3 -0
  93. data/t/preread_input.ru +17 -0
  94. data/t/rack-input-tests.ru +21 -0
  95. data/t/sslgen.sh +71 -0
  96. data/t/t0000-http-basic.sh +50 -0
  97. data/t/t0001-reload-bad-config.sh +53 -0
  98. data/t/t0002-config-conflict.sh +49 -0
  99. data/t/t0002-parser-error.sh +94 -0
  100. data/t/t0003-working_directory.sh +51 -0
  101. data/t/t0004-heartbeat-timeout.sh +69 -0
  102. data/t/t0004-working_directory_broken.sh +24 -0
  103. data/t/t0005-working_directory_app.rb.sh +40 -0
  104. data/t/t0006-reopen-logs.sh +83 -0
  105. data/t/t0006.ru +13 -0
  106. data/t/t0007-working_directory_no_embed_cli.sh +44 -0
  107. data/t/t0008-back_out_of_upgrade.sh +110 -0
  108. data/t/t0009-broken-app.sh +56 -0
  109. data/t/t0009-winch_ttin.sh +59 -0
  110. data/t/t0010-reap-logging.sh +55 -0
  111. data/t/t0011-active-unix-socket.sh +79 -0
  112. data/t/t0012-reload-empty-config.sh +85 -0
  113. data/t/t0013-rewindable-input-false.sh +24 -0
  114. data/t/t0013.ru +12 -0
  115. data/t/t0014-rewindable-input-true.sh +24 -0
  116. data/t/t0014.ru +12 -0
  117. data/t/t0015-configurator-internals.sh +25 -0
  118. data/t/t0016-trust-x-forwarded-false.sh +30 -0
  119. data/t/t0017-trust-x-forwarded-true.sh +30 -0
  120. data/t/t0018-write-on-close.sh +23 -0
  121. data/t/t0019-max_header_len.sh +49 -0
  122. data/t/t0020-at_exit-handler.sh +49 -0
  123. data/t/t0021-process_detach.sh +29 -0
  124. data/t/t0022-listener_names-preload_app.sh +32 -0
  125. data/t/t0100-rack-input-tests.sh +124 -0
  126. data/t/t0116-client_body_buffer_size.sh +80 -0
  127. data/t/t0116.ru +16 -0
  128. data/t/t0200-rack-hijack.sh +27 -0
  129. data/t/t0300-no-default-middleware.sh +15 -0
  130. data/t/t0600-https-server-basic.sh +48 -0
  131. data/t/t9000-preread-input.sh +48 -0
  132. data/t/t9001-oob_gc.sh +47 -0
  133. data/t/t9002-oob_gc-path.sh +75 -0
  134. data/t/test-lib.sh +128 -0
  135. data/t/write-on-close.ru +11 -0
  136. data/test/aggregate.rb +15 -0
  137. data/test/benchmark/README +50 -0
  138. data/test/benchmark/dd.ru +18 -0
  139. data/test/benchmark/stack.ru +8 -0
  140. data/test/exec/README +5 -0
  141. data/test/exec/test_exec.rb +1047 -0
  142. data/test/test_helper.rb +297 -0
  143. data/test/unit/test_configurator.rb +175 -0
  144. data/test/unit/test_droplet.rb +28 -0
  145. data/test/unit/test_http_parser.rb +854 -0
  146. data/test/unit/test_http_parser_ng.rb +731 -0
  147. data/test/unit/test_http_parser_xftrust.rb +38 -0
  148. data/test/unit/test_request.rb +182 -0
  149. data/test/unit/test_response.rb +99 -0
  150. data/test/unit/test_server.rb +268 -0
  151. data/test/unit/test_signals.rb +188 -0
  152. data/test/unit/test_sni_hostnames.rb +47 -0
  153. data/test/unit/test_socket_helper.rb +195 -0
  154. data/test/unit/test_stream_input.rb +203 -0
  155. data/test/unit/test_tee_input.rb +294 -0
  156. data/test/unit/test_upload.rb +306 -0
  157. data/test/unit/test_util.rb +105 -0
  158. data/unicorn-academia.gemspec +44 -0
  159. metadata +328 -0
@@ -0,0 +1,294 @@
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 test_gets_long
33
+ r = init_request("hello", 5 + (4096 * 4 * 3) + "#$/foo#$/".size)
34
+ ti = TeeInput.new(@rd, r)
35
+ status = line = nil
36
+ pid = fork {
37
+ @rd.close
38
+ 3.times { @wr.write("ffff" * 4096) }
39
+ @wr.write "#$/foo#$/"
40
+ @wr.close
41
+ }
42
+ @wr.close
43
+ line = ti.gets
44
+ assert_equal(4096 * 4 * 3 + 5 + $/.size, line.size)
45
+ assert_equal("hello" << ("ffff" * 4096 * 3) << "#$/", line)
46
+ line = ti.gets
47
+ assert_equal "foo#$/", line
48
+ assert_nil ti.gets
49
+ pid, status = Process.waitpid2(pid)
50
+ assert status.success?
51
+ end
52
+
53
+ def test_gets_short
54
+ r = init_request("hello", 5 + "#$/foo".size)
55
+ ti = TeeInput.new(@rd, r)
56
+ status = line = nil
57
+ pid = fork {
58
+ @rd.close
59
+ @wr.write "#$/foo"
60
+ @wr.close
61
+ }
62
+ @wr.close
63
+ line = ti.gets
64
+ assert_equal("hello#$/", line)
65
+ line = ti.gets
66
+ assert_equal "foo", line
67
+ assert_nil ti.gets
68
+ pid, status = Process.waitpid2(pid)
69
+ assert status.success?
70
+ end
71
+
72
+ def test_small_body
73
+ r = init_request('hello')
74
+ ti = TeeInput.new(@rd, r)
75
+ assert_equal 0, @parser.content_length
76
+ assert @parser.body_eof?
77
+ assert_equal StringIO, ti.tmp.class
78
+ assert_equal 0, ti.tmp.pos
79
+ assert_equal 5, ti.size
80
+ assert_equal 'hello', ti.read
81
+ assert_equal '', ti.read
82
+ assert_nil ti.read(4096)
83
+ assert_equal 5, ti.size
84
+ end
85
+
86
+ def test_read_with_buffer
87
+ r = init_request('hello')
88
+ ti = TeeInput.new(@rd, r)
89
+ buf = ''
90
+ rv = ti.read(4, buf)
91
+ assert_equal 'hell', rv
92
+ assert_equal 'hell', buf
93
+ assert_equal rv.object_id, buf.object_id
94
+ assert_equal 'o', ti.read
95
+ assert_equal nil, ti.read(5, buf)
96
+ assert_equal 0, ti.rewind
97
+ assert_equal 'hello', ti.read(5, buf)
98
+ assert_equal 'hello', buf
99
+ end
100
+
101
+ def test_big_body
102
+ r = init_request('.' * Unicorn::Const::MAX_BODY << 'a')
103
+ ti = TeeInput.new(@rd, r)
104
+ assert_equal 0, @parser.content_length
105
+ assert @parser.body_eof?
106
+ assert_kind_of File, ti.tmp
107
+ assert_equal 0, ti.tmp.pos
108
+ assert_equal Unicorn::Const::MAX_BODY + 1, ti.size
109
+ end
110
+
111
+ def test_read_in_full_if_content_length
112
+ a, b = 300, 3
113
+ r = init_request('.' * b, 300)
114
+ assert_equal 300, @parser.content_length
115
+ ti = TeeInput.new(@rd, r)
116
+ pid = fork {
117
+ @wr.write('.' * 197)
118
+ sleep 1 # still a *potential* race here that would make the test moot...
119
+ @wr.write('.' * 100)
120
+ }
121
+ assert_equal a, ti.read(a).size
122
+ _, status = Process.waitpid2(pid)
123
+ assert status.success?
124
+ @wr.close
125
+ end
126
+
127
+ def test_big_body_multi
128
+ r = init_request('.', Unicorn::Const::MAX_BODY + 1)
129
+ ti = TeeInput.new(@rd, r)
130
+ assert_equal Unicorn::Const::MAX_BODY, @parser.content_length
131
+ assert ! @parser.body_eof?
132
+ assert_kind_of File, ti.tmp
133
+ assert_equal 0, ti.tmp.pos
134
+ assert_equal Unicorn::Const::MAX_BODY + 1, ti.size
135
+ nr = Unicorn::Const::MAX_BODY / 4
136
+ pid = fork {
137
+ @rd.close
138
+ nr.times { @wr.write('....') }
139
+ @wr.close
140
+ }
141
+ @wr.close
142
+ assert_equal '.', ti.read(1)
143
+ assert_equal Unicorn::Const::MAX_BODY + 1, ti.size
144
+ nr.times { |x|
145
+ assert_equal '....', ti.read(4), "nr=#{x}"
146
+ assert_equal Unicorn::Const::MAX_BODY + 1, ti.size
147
+ }
148
+ assert_nil ti.read(1)
149
+ pid, status = Process.waitpid2(pid)
150
+ assert status.success?
151
+ end
152
+
153
+ def test_chunked
154
+ @parser = Unicorn::HttpParser.new
155
+ @parser.buf << "POST / HTTP/1.1\r\n" \
156
+ "Host: localhost\r\n" \
157
+ "Transfer-Encoding: chunked\r\n" \
158
+ "\r\n"
159
+ assert @parser.parse
160
+ assert_equal "", @parser.buf
161
+
162
+ pid = fork {
163
+ @rd.close
164
+ 5.times { @wr.write("5\r\nabcde\r\n") }
165
+ @wr.write("0\r\n\r\n")
166
+ }
167
+ @wr.close
168
+ ti = TeeInput.new(@rd, @parser)
169
+ assert_nil @parser.content_length
170
+ assert_nil ti.len
171
+ assert ! @parser.body_eof?
172
+ assert_equal 25, ti.size
173
+ assert @parser.body_eof?
174
+ assert_equal 25, ti.len
175
+ assert_equal 0, ti.tmp.pos
176
+ ti.rewind
177
+ assert_equal 0, ti.tmp.pos
178
+ assert_equal 'abcdeabcdeabcdeabcde', ti.read(20)
179
+ assert_equal 20, ti.tmp.pos
180
+ ti.rewind
181
+ assert_equal 0, ti.tmp.pos
182
+ assert_kind_of File, ti.tmp
183
+ status = nil
184
+ pid, status = Process.waitpid2(pid)
185
+ assert status.success?
186
+ end
187
+
188
+ def test_chunked_ping_pong
189
+ @parser = Unicorn::HttpParser.new
190
+ buf = @parser.buf
191
+ buf << "POST / HTTP/1.1\r\n" \
192
+ "Host: localhost\r\n" \
193
+ "Transfer-Encoding: chunked\r\n" \
194
+ "\r\n"
195
+ assert @parser.parse
196
+ assert_equal "", buf
197
+ chunks = %w(aa bbb cccc dddd eeee)
198
+ rd, wr = IO.pipe
199
+
200
+ pid = fork {
201
+ chunks.each do |chunk|
202
+ rd.read(1) == "." and
203
+ @wr.write("#{'%x' % [ chunk.size]}\r\n#{chunk}\r\n")
204
+ end
205
+ @wr.write("0\r\n\r\n")
206
+ }
207
+ ti = TeeInput.new(@rd, @parser)
208
+ assert_nil @parser.content_length
209
+ assert_nil ti.len
210
+ assert ! @parser.body_eof?
211
+ chunks.each do |chunk|
212
+ wr.write('.')
213
+ assert_equal chunk, ti.read(16384)
214
+ end
215
+ _, status = Process.waitpid2(pid)
216
+ assert status.success?
217
+ end
218
+
219
+ def test_chunked_with_trailer
220
+ @parser = Unicorn::HttpParser.new
221
+ buf = @parser.buf
222
+ buf << "POST / HTTP/1.1\r\n" \
223
+ "Host: localhost\r\n" \
224
+ "Trailer: Hello\r\n" \
225
+ "Transfer-Encoding: chunked\r\n" \
226
+ "\r\n"
227
+ assert @parser.parse
228
+ assert_equal "", buf
229
+
230
+ pid = fork {
231
+ @rd.close
232
+ 5.times { @wr.write("5\r\nabcde\r\n") }
233
+ @wr.write("0\r\n")
234
+ @wr.write("Hello: World\r\n\r\n")
235
+ }
236
+ @wr.close
237
+ ti = TeeInput.new(@rd, @parser)
238
+ assert_nil @parser.content_length
239
+ assert_nil ti.len
240
+ assert ! @parser.body_eof?
241
+ assert_equal 25, ti.size
242
+ assert_equal "World", @parser.env['HTTP_HELLO']
243
+ pid, status = Process.waitpid2(pid)
244
+ assert status.success?
245
+ end
246
+
247
+ def test_chunked_and_size_slow
248
+ @parser = Unicorn::HttpParser.new
249
+ buf = @parser.buf
250
+ buf << "POST / HTTP/1.1\r\n" \
251
+ "Host: localhost\r\n" \
252
+ "Trailer: Hello\r\n" \
253
+ "Transfer-Encoding: chunked\r\n" \
254
+ "\r\n"
255
+ assert @parser.parse
256
+ assert_equal "", buf
257
+
258
+ @wr.write("9\r\nabcde")
259
+ ti = TeeInput.new(@rd, @parser)
260
+ assert_nil @parser.content_length
261
+ assert_equal "abcde", ti.read(9)
262
+ assert ! @parser.body_eof?
263
+ @wr.write("fghi\r\n0\r\nHello: World\r\n\r\n")
264
+ assert_equal 9, ti.size
265
+ assert_equal "fghi", ti.read(9)
266
+ assert_equal nil, ti.read(9)
267
+ assert_equal "World", @parser.env['HTTP_HELLO']
268
+ end
269
+
270
+ def test_gets_read_mix
271
+ r = init_request("hello\nasdfasdf")
272
+ ti = Unicorn::TeeInput.new(@rd, r)
273
+ assert_equal "hello\n", ti.gets
274
+ assert_equal "asdfasdf", ti.read(9)
275
+ assert_nil ti.read(9)
276
+ end
277
+
278
+ private
279
+
280
+ def init_request(body, size = nil)
281
+ @parser = Unicorn::HttpParser.new
282
+ body = body.to_s.freeze
283
+ buf = @parser.buf
284
+ buf << "POST / HTTP/1.1\r\n" \
285
+ "Host: localhost\r\n" \
286
+ "Content-Length: #{size || body.size}\r\n" \
287
+ "\r\n#{body}"
288
+ assert @parser.parse
289
+ assert_equal body, buf
290
+ @buf = buf
291
+ @parser
292
+ end
293
+
294
+ 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