unicorn 0.9.2 → 0.90.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. data/.gitignore +1 -0
  2. data/CHANGELOG +3 -0
  3. data/GNUmakefile +7 -7
  4. data/Manifest +20 -23
  5. data/README +16 -13
  6. data/TODO +5 -3
  7. data/bin/unicorn +1 -1
  8. data/bin/unicorn_rails +1 -1
  9. data/ext/unicorn_http/c_util.h +107 -0
  10. data/ext/unicorn_http/common_field_optimization.h +110 -0
  11. data/ext/unicorn_http/ext_help.h +41 -5
  12. data/ext/unicorn_http/extconf.rb +3 -1
  13. data/ext/unicorn_http/global_variables.h +93 -0
  14. data/ext/unicorn_http/unicorn_http.c +2123 -326
  15. data/ext/unicorn_http/unicorn_http.rl +488 -87
  16. data/ext/unicorn_http/unicorn_http_common.rl +12 -1
  17. data/lib/unicorn.rb +0 -2
  18. data/lib/unicorn/app/exec_cgi.rb +0 -1
  19. data/lib/unicorn/app/inetd.rb +2 -0
  20. data/lib/unicorn/app/old_rails/static.rb +0 -2
  21. data/lib/unicorn/const.rb +1 -5
  22. data/lib/unicorn/http_request.rb +13 -29
  23. data/lib/unicorn/http_response.rb +1 -1
  24. data/lib/unicorn/tee_input.rb +48 -46
  25. data/lib/unicorn/util.rb +3 -3
  26. data/test/benchmark/README +0 -5
  27. data/test/exec/test_exec.rb +4 -0
  28. data/test/rails/{app-2.3.2.1 → app-2.3.3.1}/.gitignore +0 -0
  29. data/test/rails/{app-2.3.2.1 → app-2.3.3.1}/Rakefile +0 -0
  30. data/test/rails/{app-2.3.2.1 → app-2.3.3.1}/app/controllers/application_controller.rb +0 -0
  31. data/test/rails/{app-2.3.2.1 → app-2.3.3.1}/app/controllers/foo_controller.rb +0 -0
  32. data/test/rails/{app-2.3.2.1 → app-2.3.3.1}/app/helpers/application_helper.rb +0 -0
  33. data/test/rails/{app-2.3.2.1 → app-2.3.3.1}/config/boot.rb +0 -0
  34. data/test/rails/{app-2.3.2.1 → app-2.3.3.1}/config/database.yml +0 -0
  35. data/test/rails/{app-2.3.2.1 → app-2.3.3.1}/config/environment.rb +0 -0
  36. data/test/rails/{app-2.3.2.1 → app-2.3.3.1}/config/environments/development.rb +0 -0
  37. data/test/rails/{app-2.3.2.1 → app-2.3.3.1}/config/environments/production.rb +0 -0
  38. data/test/rails/{app-2.3.2.1 → app-2.3.3.1}/config/routes.rb +0 -0
  39. data/test/rails/{app-2.3.2.1 → app-2.3.3.1}/db/.gitignore +0 -0
  40. data/test/rails/{app-2.3.2.1 → app-2.3.3.1}/public/404.html +0 -0
  41. data/test/rails/{app-2.3.2.1 → app-2.3.3.1}/public/500.html +0 -0
  42. data/test/unit/test_http_parser.rb +112 -47
  43. data/test/unit/test_http_parser_ng.rb +284 -0
  44. data/test/unit/test_request.rb +25 -7
  45. data/test/unit/test_response.rb +11 -0
  46. data/test/unit/test_server.rb +7 -2
  47. data/test/unit/test_signals.rb +2 -0
  48. data/test/unit/test_tee_input.rb +118 -2
  49. data/test/unit/test_upload.rb +1 -1
  50. data/test/unit/test_util.rb +5 -0
  51. data/unicorn.gemspec +6 -6
  52. metadata +33 -37
  53. data/ext/unicorn_http/unicorn_http.h +0 -1289
  54. data/lib/unicorn/chunked_reader.rb +0 -77
  55. data/lib/unicorn/trailer_parser.rb +0 -52
  56. data/test/benchmark/big_request.rb +0 -44
  57. data/test/benchmark/request.rb +0 -56
  58. data/test/benchmark/response.rb +0 -30
  59. data/test/unit/test_chunked_reader.rb +0 -123
  60. data/test/unit/test_trailer_parser.rb +0 -52
@@ -0,0 +1,284 @@
1
+ # coding: binary
2
+ require 'test/test_helper'
3
+ require 'digest/md5'
4
+
5
+ include Unicorn
6
+
7
+ class HttpParserNgTest < Test::Unit::TestCase
8
+
9
+ def setup
10
+ @parser = HttpParser.new
11
+ end
12
+
13
+ def test_identity_step_headers
14
+ req = {}
15
+ str = "PUT / HTTP/1.1\r\n"
16
+ assert ! @parser.headers(req, str)
17
+ str << "Content-Length: 123\r\n"
18
+ assert ! @parser.headers(req, str)
19
+ str << "\r\n"
20
+ assert_equal req.object_id, @parser.headers(req, str).object_id
21
+ assert_equal '123', req['CONTENT_LENGTH']
22
+ assert_equal 0, str.size
23
+ assert ! @parser.keepalive?
24
+ end
25
+
26
+ def test_identity_oneshot_header
27
+ req = {}
28
+ str = "PUT / HTTP/1.1\r\nContent-Length: 123\r\n\r\n"
29
+ assert_equal req.object_id, @parser.headers(req, str).object_id
30
+ assert_equal '123', req['CONTENT_LENGTH']
31
+ assert_equal 0, str.size
32
+ assert ! @parser.keepalive?
33
+ end
34
+
35
+ def test_identity_oneshot_header_with_body
36
+ body = ('a' * 123).freeze
37
+ req = {}
38
+ str = "PUT / HTTP/1.1\r\n" \
39
+ "Content-Length: #{body.length}\r\n" \
40
+ "\r\n#{body}"
41
+ assert_equal req.object_id, @parser.headers(req, str).object_id
42
+ assert_equal '123', req['CONTENT_LENGTH']
43
+ assert_equal 123, str.size
44
+ assert_equal body, str
45
+ tmp = ''
46
+ assert_nil @parser.filter_body(tmp, str)
47
+ assert_equal 0, str.size
48
+ assert_equal tmp, body
49
+ assert_equal "", @parser.filter_body(tmp, str)
50
+ assert ! @parser.keepalive?
51
+ end
52
+
53
+ def test_identity_oneshot_header_with_body_partial
54
+ str = "PUT / HTTP/1.1\r\nContent-Length: 123\r\n\r\na"
55
+ assert_equal Hash, @parser.headers({}, str).class
56
+ assert_equal 1, str.size
57
+ assert_equal 'a', str
58
+ tmp = ''
59
+ assert_nil @parser.filter_body(tmp, str)
60
+ assert_equal "", str
61
+ assert_equal "a", tmp
62
+ str << ' ' * 122
63
+ rv = @parser.filter_body(tmp, str)
64
+ assert_equal 122, tmp.size
65
+ assert_nil rv
66
+ assert_equal "", str
67
+ assert_equal str.object_id, @parser.filter_body(tmp, str).object_id
68
+ assert ! @parser.keepalive?
69
+ end
70
+
71
+ def test_identity_oneshot_header_with_body_slop
72
+ str = "PUT / HTTP/1.1\r\nContent-Length: 1\r\n\r\naG"
73
+ assert_equal Hash, @parser.headers({}, str).class
74
+ assert_equal 2, str.size
75
+ assert_equal 'aG', str
76
+ tmp = ''
77
+ assert_nil @parser.filter_body(tmp, str)
78
+ assert_equal "G", str
79
+ assert_equal "G", @parser.filter_body(tmp, str)
80
+ assert_equal 1, tmp.size
81
+ assert_equal "a", tmp
82
+ assert ! @parser.keepalive?
83
+ end
84
+
85
+ def test_chunked
86
+ str = "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n"
87
+ req = {}
88
+ assert_equal req, @parser.headers(req, str)
89
+ assert_equal 0, str.size
90
+ tmp = ""
91
+ assert_nil @parser.filter_body(tmp, "6")
92
+ assert_equal 0, tmp.size
93
+ assert_nil @parser.filter_body(tmp, rv = "\r\n")
94
+ assert_equal 0, rv.size
95
+ assert_equal 0, tmp.size
96
+ tmp = ""
97
+ assert_nil @parser.filter_body(tmp, "..")
98
+ assert_equal "..", tmp
99
+ assert_nil @parser.filter_body(tmp, "abcd\r\n0\r\n")
100
+ assert_equal "abcd", tmp
101
+ rv = "PUT"
102
+ assert_equal rv.object_id, @parser.filter_body(tmp, rv).object_id
103
+ assert_equal "PUT", rv
104
+ assert ! @parser.keepalive?
105
+ end
106
+
107
+ def test_two_chunks
108
+ str = "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n"
109
+ req = {}
110
+ assert_equal req, @parser.headers(req, str)
111
+ assert_equal 0, str.size
112
+ tmp = ""
113
+ assert_nil @parser.filter_body(tmp, "6")
114
+ assert_equal 0, tmp.size
115
+ assert_nil @parser.filter_body(tmp, rv = "\r\n")
116
+ assert_equal "", rv
117
+ assert_equal 0, tmp.size
118
+ tmp = ""
119
+ assert_nil @parser.filter_body(tmp, "..")
120
+ assert_equal 2, tmp.size
121
+ assert_equal "..", tmp
122
+ assert_nil @parser.filter_body(tmp, "abcd\r\n1")
123
+ assert_equal "abcd", tmp
124
+ assert_nil @parser.filter_body(tmp, "\r")
125
+ assert_equal "", tmp
126
+ assert_nil @parser.filter_body(tmp, "\n")
127
+ assert_equal "", tmp
128
+ assert_nil @parser.filter_body(tmp, "z")
129
+ assert_equal "z", tmp
130
+ assert_nil @parser.filter_body(tmp, "\r\n")
131
+ assert_nil @parser.filter_body(tmp, "0")
132
+ assert_nil @parser.filter_body(tmp, "\r")
133
+ rv = @parser.filter_body(tmp, buf = "\nGET")
134
+ assert_equal "GET", rv
135
+ assert_equal buf.object_id, rv.object_id
136
+ assert ! @parser.keepalive?
137
+ end
138
+
139
+ def test_big_chunk
140
+ str = "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n" \
141
+ "4000\r\nabcd"
142
+ req = {}
143
+ assert_equal req, @parser.headers(req, str)
144
+ tmp = ''
145
+ assert_nil @parser.filter_body(tmp, str)
146
+ assert_equal '', str
147
+ str = ' ' * 16300
148
+ assert_nil @parser.filter_body(tmp, str)
149
+ assert_equal '', str
150
+ str = ' ' * 80
151
+ assert_nil @parser.filter_body(tmp, str)
152
+ assert_equal '', str
153
+ assert ! @parser.body_eof?
154
+ assert_equal "", @parser.filter_body(tmp, "\r\n0\r\n")
155
+ assert @parser.body_eof?
156
+ assert ! @parser.keepalive?
157
+ end
158
+
159
+ def test_two_chunks_oneshot
160
+ str = "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n" \
161
+ "1\r\na\r\n2\r\n..\r\n0\r\n"
162
+ req = {}
163
+ assert_equal req, @parser.headers(req, str)
164
+ tmp = ''
165
+ assert_nil @parser.filter_body(tmp, str)
166
+ assert_equal 'a..', tmp
167
+ rv = @parser.filter_body(tmp, str)
168
+ assert_equal rv.object_id, str.object_id
169
+ assert ! @parser.keepalive?
170
+ end
171
+
172
+ def test_trailers
173
+ str = "PUT / HTTP/1.1\r\n" \
174
+ "Trailer: Content-MD5\r\n" \
175
+ "transfer-Encoding: chunked\r\n\r\n" \
176
+ "1\r\na\r\n2\r\n..\r\n0\r\n"
177
+ req = {}
178
+ assert_equal req, @parser.headers(req, str)
179
+ assert_equal 'Content-MD5', req['HTTP_TRAILER']
180
+ assert_nil req['HTTP_CONTENT_MD5']
181
+ tmp = ''
182
+ assert_nil @parser.filter_body(tmp, str)
183
+ assert_equal 'a..', tmp
184
+ md5_b64 = [ Digest::MD5.digest(tmp) ].pack('m').strip.freeze
185
+ rv = @parser.filter_body(tmp, str)
186
+ assert_equal rv.object_id, str.object_id
187
+ assert_equal '', str
188
+ md5_hdr = "Content-MD5: #{md5_b64}\r\n".freeze
189
+ str << md5_hdr
190
+ assert_nil @parser.trailers(req, str)
191
+ assert_equal md5_b64, req['HTTP_CONTENT_MD5']
192
+ assert_equal "CONTENT_MD5: #{md5_b64}\r\n", str
193
+ assert_nil @parser.trailers(req, str << "\r")
194
+ assert_equal req, @parser.trailers(req, str << "\nGET / ")
195
+ assert_equal "GET / ", str
196
+ assert ! @parser.keepalive?
197
+ end
198
+
199
+ def test_max_chunk
200
+ str = "PUT / HTTP/1.1\r\n" \
201
+ "transfer-Encoding: chunked\r\n\r\n" \
202
+ "#{HttpParser::CHUNK_MAX.to_s(16)}\r\na\r\n2\r\n..\r\n0\r\n"
203
+ req = {}
204
+ assert_equal req, @parser.headers(req, str)
205
+ assert_nil @parser.content_length
206
+ assert_nothing_raised { @parser.filter_body('', str) }
207
+ assert ! @parser.keepalive?
208
+ end
209
+
210
+ def test_max_body
211
+ n = HttpParser::LENGTH_MAX
212
+ str = "PUT / HTTP/1.1\r\nContent-Length: #{n}\r\n\r\n"
213
+ req = {}
214
+ assert_nothing_raised { @parser.headers(req, str) }
215
+ assert_equal n, req['CONTENT_LENGTH'].to_i
216
+ assert ! @parser.keepalive?
217
+ end
218
+
219
+ def test_overflow_chunk
220
+ n = HttpParser::CHUNK_MAX + 1
221
+ str = "PUT / HTTP/1.1\r\n" \
222
+ "transfer-Encoding: chunked\r\n\r\n" \
223
+ "#{n.to_s(16)}\r\na\r\n2\r\n..\r\n0\r\n"
224
+ req = {}
225
+ assert_equal req, @parser.headers(req, str)
226
+ assert_nil @parser.content_length
227
+ assert_raise(HttpParserError) { @parser.filter_body('', str) }
228
+ assert ! @parser.keepalive?
229
+ end
230
+
231
+ def test_overflow_content_length
232
+ n = HttpParser::LENGTH_MAX + 1
233
+ str = "PUT / HTTP/1.1\r\nContent-Length: #{n}\r\n\r\n"
234
+ assert_raise(HttpParserError) { @parser.headers({}, str) }
235
+ assert ! @parser.keepalive?
236
+ end
237
+
238
+ def test_bad_chunk
239
+ str = "PUT / HTTP/1.1\r\n" \
240
+ "transfer-Encoding: chunked\r\n\r\n" \
241
+ "#zzz\r\na\r\n2\r\n..\r\n0\r\n"
242
+ req = {}
243
+ assert_equal req, @parser.headers(req, str)
244
+ assert_nil @parser.content_length
245
+ assert_raise(HttpParserError) { @parser.filter_body('', str) }
246
+ assert ! @parser.keepalive?
247
+ end
248
+
249
+ def test_bad_content_length
250
+ str = "PUT / HTTP/1.1\r\nContent-Length: 7ff\r\n\r\n"
251
+ assert_raise(HttpParserError) { @parser.headers({}, str) }
252
+ assert ! @parser.keepalive?
253
+ end
254
+
255
+ def test_bad_trailers
256
+ str = "PUT / HTTP/1.1\r\n" \
257
+ "Trailer: Transfer-Encoding\r\n" \
258
+ "transfer-Encoding: chunked\r\n\r\n" \
259
+ "1\r\na\r\n2\r\n..\r\n0\r\n"
260
+ req = {}
261
+ assert_equal req, @parser.headers(req, str)
262
+ assert_equal 'Transfer-Encoding', req['HTTP_TRAILER']
263
+ tmp = ''
264
+ assert_nil @parser.filter_body(tmp, str)
265
+ assert_equal 'a..', tmp
266
+ assert_equal '', str
267
+ str << "Transfer-Encoding: identity\r\n\r\n"
268
+ assert_raise(HttpParserError) { @parser.trailers(req, str) }
269
+ assert ! @parser.keepalive?
270
+ end
271
+
272
+ def test_repeat_headers
273
+ str = "PUT / HTTP/1.1\r\n" \
274
+ "Trailer: Content-MD5\r\n" \
275
+ "Trailer: Content-SHA1\r\n" \
276
+ "transfer-Encoding: chunked\r\n\r\n" \
277
+ "1\r\na\r\n2\r\n..\r\n0\r\n"
278
+ req = {}
279
+ assert_equal req, @parser.headers(req, str)
280
+ assert_equal 'Content-MD5,Content-SHA1', req['HTTP_TRAILER']
281
+ assert ! @parser.keepalive?
282
+ end
283
+
284
+ end
@@ -2,13 +2,6 @@
2
2
  # You can redistribute it and/or modify it under the same terms as Ruby.
3
3
 
4
4
  require 'test/test_helper'
5
- begin
6
- require 'rack'
7
- require 'rack/lint'
8
- rescue LoadError
9
- warn "Unable to load rack, skipping test"
10
- exit 0
11
- end
12
5
 
13
6
  include Unicorn
14
7
 
@@ -120,6 +113,31 @@ class RequestTest < Test::Unit::TestCase
120
113
  assert_nothing_raised { res = @lint.call(env) }
121
114
  end
122
115
 
116
+ def test_no_content_stringio
117
+ client = MockRequest.new("GET / HTTP/1.1\r\nHost: foo\r\n\r\n")
118
+ res = env = nil
119
+ assert_nothing_raised { env = @request.read(client) }
120
+ assert_equal StringIO, env['rack.input'].class
121
+ end
122
+
123
+ def test_zero_content_stringio
124
+ client = MockRequest.new("PUT / HTTP/1.1\r\n" \
125
+ "Content-Length: 0\r\n" \
126
+ "Host: foo\r\n\r\n")
127
+ res = env = nil
128
+ assert_nothing_raised { env = @request.read(client) }
129
+ assert_equal StringIO, env['rack.input'].class
130
+ end
131
+
132
+ def test_real_content_not_stringio
133
+ client = MockRequest.new("PUT / HTTP/1.1\r\n" \
134
+ "Content-Length: 1\r\n" \
135
+ "Host: foo\r\n\r\n")
136
+ res = env = nil
137
+ assert_nothing_raised { env = @request.read(client) }
138
+ assert_equal Unicorn::TeeInput, env['rack.input'].class
139
+ end
140
+
123
141
  def test_rack_lint_put
124
142
  client = MockRequest.new(
125
143
  "PUT / HTTP/1.1\r\n" \
@@ -94,4 +94,15 @@ class ResponseTest < Test::Unit::TestCase
94
94
  assert_match(expect_body, out.string.split(/\r\n/).last)
95
95
  end
96
96
 
97
+ def test_unknown_status_pass_through
98
+ out = StringIO.new
99
+ HttpResponse.write(out,["666 I AM THE BEAST", {}, [] ])
100
+ assert out.closed?
101
+ headers = out.string.split(/\r\n\r\n/).first.split(/\r\n/)
102
+ assert %r{\AHTTP/\d\.\d 666 I AM THE BEAST\z}.match(headers[0])
103
+ status = headers.grep(/\AStatus:/i).first
104
+ assert status
105
+ assert_equal "Status: 666 I AM THE BEAST", status
106
+ end
107
+
97
108
  end
@@ -33,6 +33,8 @@ class WebServerTest < Test::Unit::TestCase
33
33
 
34
34
  def teardown
35
35
  redirect_test_io do
36
+ wait_workers_ready("test_stderr.#$$.log", 1)
37
+ File.truncate("test_stderr.#$$.log", 0)
36
38
  @server.stop(true)
37
39
  end
38
40
  end
@@ -53,8 +55,10 @@ class WebServerTest < Test::Unit::TestCase
53
55
  end
54
56
  results = hit(["http://localhost:#@port/"])
55
57
  worker_pid = results[0].to_i
58
+ assert worker_pid != 0
56
59
  tmp.sysseek(0)
57
60
  loader_pid = tmp.sysread(4096).to_i
61
+ assert loader_pid != 0
58
62
  assert_equal worker_pid, loader_pid
59
63
  teardown
60
64
 
@@ -65,6 +69,7 @@ class WebServerTest < Test::Unit::TestCase
65
69
  end
66
70
  results = hit(["http://localhost:#@port/"])
67
71
  worker_pid = results[0].to_i
72
+ assert worker_pid != 0
68
73
  tmp.sysseek(0)
69
74
  loader_pid = tmp.sysread(4096).to_i
70
75
  assert_equal $$, loader_pid
@@ -158,9 +163,9 @@ class WebServerTest < Test::Unit::TestCase
158
163
  do_test(long, Unicorn::Const::CHUNK_SIZE * 2 -400)
159
164
  end
160
165
 
161
- def test_file_streamed_request_bad_method
166
+ def test_file_streamed_request_bad_body
162
167
  body = "a" * (Unicorn::Const::MAX_BODY * 2)
163
- long = "GET /test HTTP/1.1\r\nContent-length: #{body.length}\r\n\r\n" + body
168
+ long = "GET /test HTTP/1.1\r\nContent-ength: #{body.length}\r\n\r\n" + body
164
169
  assert_raises(EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,
165
170
  Errno::EBADF) {
166
171
  do_test(long, Unicorn::Const::CHUNK_SIZE * 2 -400)
@@ -53,8 +53,10 @@ class SignalsTest < Test::Unit::TestCase
53
53
  buf =~ /\bX-Pid: (\d+)\b/ or raise Exception
54
54
  child = $1.to_i
55
55
  wait_master_ready("test_stderr.#{pid}.log")
56
+ wait_workers_ready("test_stderr.#{pid}.log", 1)
56
57
  Process.kill(:KILL, pid)
57
58
  Process.waitpid(pid)
59
+ File.unlink("test_stderr.#{pid}.log", "test_stdout.#{pid}.log")
58
60
  t0 = Time.now
59
61
  end
60
62
  assert child
@@ -26,7 +26,8 @@ class TestTeeInput < Test::Unit::TestCase
26
26
  end
27
27
 
28
28
  def test_gets_long
29
- ti = Unicorn::TeeInput.new(@rd, nil, "hello")
29
+ init_parser("hello", 5 + (4096 * 4 * 3) + "#$/foo#$/".size)
30
+ ti = Unicorn::TeeInput.new(@rd, @env, @parser, @buf)
30
31
  status = line = nil
31
32
  pid = fork {
32
33
  @rd.close
@@ -46,7 +47,8 @@ class TestTeeInput < Test::Unit::TestCase
46
47
  end
47
48
 
48
49
  def test_gets_short
49
- ti = Unicorn::TeeInput.new(@rd, nil, "hello")
50
+ init_parser("hello", 5 + "#$/foo".size)
51
+ ti = Unicorn::TeeInput.new(@rd, @env, @parser, @buf)
50
52
  status = line = nil
51
53
  pid = fork {
52
54
  @rd.close
@@ -63,4 +65,118 @@ class TestTeeInput < Test::Unit::TestCase
63
65
  assert status.success?
64
66
  end
65
67
 
68
+ def test_small_body
69
+ init_parser('hello')
70
+ ti = Unicorn::TeeInput.new(@rd, @env, @parser, @buf)
71
+ assert_equal 0, @parser.content_length
72
+ assert @parser.body_eof?
73
+ assert_equal StringIO, ti.instance_eval { @tmp.class }
74
+ assert_equal 0, ti.instance_eval { @tmp.pos }
75
+ assert_equal 5, ti.size
76
+ assert_equal 'hello', ti.read
77
+ assert_equal '', ti.read
78
+ assert_nil ti.read(4096)
79
+ end
80
+
81
+ def test_read_with_buffer
82
+ init_parser('hello')
83
+ ti = Unicorn::TeeInput.new(@rd, @env, @parser, @buf)
84
+ buf = ''
85
+ rv = ti.read(4, buf)
86
+ assert_equal 'hell', rv
87
+ assert_equal 'hell', buf
88
+ assert_equal rv.object_id, buf.object_id
89
+ assert_equal 'o', ti.read
90
+ assert_equal nil, ti.read(5, buf)
91
+ assert_equal 0, ti.rewind
92
+ assert_equal 'hello', ti.read(5, buf)
93
+ assert_equal 'hello', buf
94
+ end
95
+
96
+ def test_big_body
97
+ init_parser('.' * Unicorn::Const::MAX_BODY << 'a')
98
+ ti = Unicorn::TeeInput.new(@rd, @env, @parser, @buf)
99
+ assert_equal 0, @parser.content_length
100
+ assert @parser.body_eof?
101
+ assert_kind_of File, ti.instance_eval { @tmp }
102
+ assert_equal 0, ti.instance_eval { @tmp.pos }
103
+ assert_equal Unicorn::Const::MAX_BODY + 1, ti.size
104
+ end
105
+
106
+ def test_big_body_multi
107
+ init_parser('.', Unicorn::Const::MAX_BODY + 1)
108
+ ti = Unicorn::TeeInput.new(@rd, @env, @parser, @buf)
109
+ assert_equal Unicorn::Const::MAX_BODY, @parser.content_length
110
+ assert ! @parser.body_eof?
111
+ assert_kind_of File, ti.instance_eval { @tmp }
112
+ assert_equal 0, ti.instance_eval { @tmp.pos }
113
+ assert_equal 1, ti.instance_eval { tmp_size }
114
+ assert_equal Unicorn::Const::MAX_BODY + 1, ti.size
115
+ nr = Unicorn::Const::MAX_BODY / 4
116
+ pid = fork {
117
+ @rd.close
118
+ nr.times { @wr.write('....') }
119
+ @wr.close
120
+ }
121
+ @wr.close
122
+ assert_equal '.', ti.read(1)
123
+ assert_equal Unicorn::Const::MAX_BODY + 1, ti.size
124
+ nr.times {
125
+ assert_equal '....', ti.read(4)
126
+ assert_equal Unicorn::Const::MAX_BODY + 1, ti.size
127
+ }
128
+ assert_nil ti.read(1)
129
+ status = nil
130
+ assert_nothing_raised { pid, status = Process.waitpid2(pid) }
131
+ assert status.success?
132
+ end
133
+
134
+ def test_chunked
135
+ @parser = Unicorn::HttpParser.new
136
+ @buf = "POST / HTTP/1.1\r\n" \
137
+ "Host: localhost\r\n" \
138
+ "Transfer-Encoding: chunked\r\n" \
139
+ "\r\n"
140
+ assert_equal @env, @parser.headers(@env, @buf)
141
+ assert_equal "", @buf
142
+
143
+ pid = fork {
144
+ @rd.close
145
+ 5.times { @wr.write("5\r\nabcde\r\n") }
146
+ @wr.write("0\r\n")
147
+ }
148
+ @wr.close
149
+ ti = Unicorn::TeeInput.new(@rd, @env, @parser, @buf)
150
+ assert_nil @parser.content_length
151
+ assert_nil ti.instance_eval { @size }
152
+ assert ! @parser.body_eof?
153
+ assert_equal 25, ti.size
154
+ assert @parser.body_eof?
155
+ assert_equal 25, ti.instance_eval { @size }
156
+ assert_equal 0, ti.instance_eval { @tmp.pos }
157
+ assert_nothing_raised { ti.rewind }
158
+ assert_equal 0, ti.instance_eval { @tmp.pos }
159
+ assert_equal 'abcdeabcdeabcdeabcde', ti.read(20)
160
+ assert_equal 20, ti.instance_eval { @tmp.pos }
161
+ assert_nothing_raised { ti.rewind }
162
+ assert_equal 0, ti.instance_eval { @tmp.pos }
163
+ assert_kind_of File, ti.instance_eval { @tmp }
164
+ status = nil
165
+ assert_nothing_raised { pid, status = Process.waitpid2(pid) }
166
+ assert status.success?
167
+ end
168
+
169
+ private
170
+
171
+ def init_parser(body, size = nil)
172
+ @parser = Unicorn::HttpParser.new
173
+ body = body.to_s.freeze
174
+ @buf = "POST / HTTP/1.1\r\n" \
175
+ "Host: localhost\r\n" \
176
+ "Content-Length: #{size || body.size}\r\n" \
177
+ "\r\n#{body}"
178
+ assert_equal @env, @parser.headers(@env, @buf)
179
+ assert_equal body, @buf
180
+ end
181
+
66
182
  end