unicorn 0.9.2 → 0.90.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 (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