unicorn 4.9.0 → 6.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (95) hide show
  1. checksums.yaml +5 -5
  2. data/.gitattributes +5 -0
  3. data/.olddoc.yml +13 -6
  4. data/Application_Timeouts +7 -7
  5. data/DESIGN +2 -4
  6. data/Documentation/.gitignore +1 -3
  7. data/Documentation/unicorn.1 +222 -0
  8. data/Documentation/unicorn_rails.1 +207 -0
  9. data/FAQ +17 -8
  10. data/GIT-VERSION-GEN +1 -1
  11. data/GNUmakefile +121 -56
  12. data/HACKING +1 -2
  13. data/ISSUES +40 -41
  14. data/KNOWN_ISSUES +11 -11
  15. data/LICENSE +2 -2
  16. data/Links +24 -25
  17. data/PHILOSOPHY +0 -6
  18. data/README +46 -39
  19. data/SIGNALS +2 -2
  20. data/Sandbox +10 -9
  21. data/TODO +0 -2
  22. data/TUNING +30 -9
  23. data/archive/slrnpull.conf +1 -1
  24. data/bin/unicorn +4 -2
  25. data/bin/unicorn_rails +3 -3
  26. data/examples/big_app_gc.rb +1 -1
  27. data/examples/init.sh +36 -8
  28. data/examples/logrotate.conf +17 -2
  29. data/examples/nginx.conf +14 -14
  30. data/examples/unicorn.conf.minimal.rb +2 -2
  31. data/examples/unicorn.conf.rb +3 -6
  32. data/examples/unicorn.socket +11 -0
  33. data/examples/unicorn@.service +40 -0
  34. data/ext/unicorn_http/common_field_optimization.h +23 -5
  35. data/ext/unicorn_http/ext_help.h +0 -20
  36. data/ext/unicorn_http/extconf.rb +37 -1
  37. data/ext/unicorn_http/global_variables.h +1 -1
  38. data/ext/unicorn_http/httpdate.c +2 -2
  39. data/ext/unicorn_http/unicorn_http.rl +167 -170
  40. data/ext/unicorn_http/unicorn_http_common.rl +1 -1
  41. data/lib/unicorn.rb +66 -46
  42. data/lib/unicorn/configurator.rb +110 -44
  43. data/lib/unicorn/const.rb +2 -25
  44. data/lib/unicorn/http_request.rb +110 -31
  45. data/lib/unicorn/http_response.rb +17 -31
  46. data/lib/unicorn/http_server.rb +238 -157
  47. data/lib/unicorn/launcher.rb +1 -1
  48. data/lib/unicorn/oob_gc.rb +6 -6
  49. data/lib/unicorn/socket_helper.rb +58 -78
  50. data/lib/unicorn/stream_input.rb +8 -7
  51. data/lib/unicorn/tee_input.rb +8 -10
  52. data/lib/unicorn/tmpio.rb +8 -7
  53. data/lib/unicorn/util.rb +5 -4
  54. data/lib/unicorn/worker.rb +36 -23
  55. data/t/GNUmakefile +3 -72
  56. data/t/README +4 -4
  57. data/t/t0011-active-unix-socket.sh +1 -1
  58. data/t/t0012-reload-empty-config.sh +2 -1
  59. data/t/t0301-no-default-middleware-ignored-in-config.sh +25 -0
  60. data/t/t0301.ru +13 -0
  61. data/t/test-lib.sh +2 -2
  62. data/test/benchmark/README +14 -4
  63. data/test/benchmark/ddstream.ru +50 -0
  64. data/test/benchmark/readinput.ru +40 -0
  65. data/test/benchmark/uconnect.perl +66 -0
  66. data/test/exec/test_exec.rb +73 -19
  67. data/test/test_helper.rb +40 -31
  68. data/test/unit/test_ccc.rb +91 -0
  69. data/test/unit/test_droplet.rb +1 -1
  70. data/test/unit/test_http_parser.rb +46 -16
  71. data/test/unit/test_http_parser_ng.rb +97 -114
  72. data/test/unit/test_request.rb +10 -10
  73. data/test/unit/test_response.rb +28 -16
  74. data/test/unit/test_server.rb +86 -12
  75. data/test/unit/test_signals.rb +8 -8
  76. data/test/unit/test_socket_helper.rb +14 -10
  77. data/test/unit/test_upload.rb +9 -14
  78. data/test/unit/test_util.rb +27 -2
  79. data/unicorn.gemspec +27 -19
  80. metadata +24 -45
  81. data/Documentation/GNUmakefile +0 -30
  82. data/Documentation/unicorn.1.txt +0 -185
  83. data/Documentation/unicorn_rails.1.txt +0 -175
  84. data/examples/git.ru +0 -13
  85. data/lib/unicorn/app/exec_cgi.rb +0 -154
  86. data/lib/unicorn/app/inetd.rb +0 -109
  87. data/lib/unicorn/ssl_client.rb +0 -11
  88. data/lib/unicorn/ssl_configurator.rb +0 -104
  89. data/lib/unicorn/ssl_server.rb +0 -42
  90. data/t/hijack.ru +0 -42
  91. data/t/t0016-trust-x-forwarded-false.sh +0 -30
  92. data/t/t0017-trust-x-forwarded-true.sh +0 -30
  93. data/t/t0200-rack-hijack.sh +0 -27
  94. data/test/unit/test_http_parser_xftrust.rb +0 -38
  95. data/test/unit/test_sni_hostnames.rb +0 -47
data/test/test_helper.rb CHANGED
@@ -28,22 +28,43 @@
28
28
  require 'fileutils'
29
29
  require 'logger'
30
30
  require 'unicorn'
31
+ require 'io/nonblock'
31
32
 
32
33
  if ENV['DEBUG']
33
34
  require 'ruby-debug'
34
35
  Debugger.start
35
36
  end
36
37
 
38
+ unless RUBY_VERSION < '3.1'
39
+ warn "Unicorn was only tested against MRI up to 3.0.\n" \
40
+ "It might not properly work with #{RUBY_VERSION}"
41
+ end
42
+
37
43
  def redirect_test_io
38
44
  orig_err = STDERR.dup
39
45
  orig_out = STDOUT.dup
40
- STDERR.reopen("test_stderr.#{$$}.log", "a")
41
- STDOUT.reopen("test_stdout.#{$$}.log", "a")
46
+ rdr_pid = $$
47
+ new_out = File.open("test_stdout.#$$.log", "a")
48
+ new_err = File.open("test_stderr.#$$.log", "a")
49
+ new_out.sync = new_err.sync = true
50
+
51
+ if tail = ENV['TAIL'] # "tail -F" if GNU, "tail -f" otherwise
52
+ require 'shellwords'
53
+ cmd = tail.shellsplit
54
+ cmd << new_out.path
55
+ cmd << new_err.path
56
+ pid = Process.spawn(*cmd, { 1 => 2, :pgroup => true })
57
+ sleep 0.1 # wait for tail(1) to startup
58
+ end
59
+ STDERR.reopen(new_err)
60
+ STDOUT.reopen(new_out)
42
61
  STDERR.sync = STDOUT.sync = true
43
62
 
44
63
  at_exit do
45
- File.unlink("test_stderr.#{$$}.log") rescue nil
46
- File.unlink("test_stdout.#{$$}.log") rescue nil
64
+ if rdr_pid == $$
65
+ File.unlink(new_out.path) rescue nil
66
+ File.unlink(new_err.path) rescue nil
67
+ end
47
68
  end
48
69
 
49
70
  begin
@@ -51,6 +72,7 @@ def redirect_test_io
51
72
  ensure
52
73
  STDERR.reopen(orig_err)
53
74
  STDOUT.reopen(orig_out)
75
+ Process.kill(:TERM, pid) if pid
54
76
  end
55
77
  end
56
78
 
@@ -265,33 +287,20 @@ def wait_for_death(pid)
265
287
  raise "PID:#{pid} never died!"
266
288
  end
267
289
 
268
- # executes +cmd+ and chunks its STDOUT
269
- def chunked_spawn(stdout, *cmd)
270
- fork {
271
- crd, cwr = IO.pipe
272
- crd.binmode
273
- cwr.binmode
274
- crd.sync = cwr.sync = true
275
-
276
- pid = fork {
277
- STDOUT.reopen(cwr)
278
- crd.close
279
- cwr.close
280
- exec(*cmd)
281
- }
282
- cwr.close
283
- begin
284
- buf = crd.readpartial(16384)
285
- stdout.write("#{'%x' % buf.size}\r\n#{buf}")
286
- rescue EOFError
287
- stdout.write("0\r\n")
288
- pid, status = Process.waitpid(pid)
289
- exit status.exitstatus
290
- end while true
291
- }
290
+ def reset_sig_handlers
291
+ %w(WINCH QUIT INT TERM USR1 USR2 HUP TTIN TTOU CHLD).each do |sig|
292
+ trap(sig, "DEFAULT")
293
+ end
292
294
  end
293
295
 
294
- def reset_sig_handlers
295
- sigs = %w(CHLD).concat(Unicorn::HttpServer::QUEUE_SIGS)
296
- sigs.each { |sig| trap(sig, "DEFAULT") }
296
+ def tcp_socket(*args)
297
+ sock = TCPSocket.new(*args)
298
+ sock.nonblock = false
299
+ sock
300
+ end
301
+
302
+ def unix_socket(*args)
303
+ sock = UNIXSocket.new(*args)
304
+ sock.nonblock = false
305
+ sock
297
306
  end
@@ -0,0 +1,91 @@
1
+ require 'socket'
2
+ require 'unicorn'
3
+ require 'io/wait'
4
+ require 'tempfile'
5
+ require 'test/unit'
6
+ require './test/test_helper'
7
+
8
+ class TestCccTCPI < Test::Unit::TestCase
9
+ def test_ccc_tcpi
10
+ start_pid = $$
11
+ host = '127.0.0.1'
12
+ srv = TCPServer.new(host, 0)
13
+ port = srv.addr[1]
14
+ err = Tempfile.new('unicorn_ccc')
15
+ rd, wr = IO.pipe
16
+ sleep_pipe = IO.pipe
17
+ pid = fork do
18
+ sleep_pipe[1].close
19
+ reqs = 0
20
+ rd.close
21
+ worker_pid = nil
22
+ app = lambda do |env|
23
+ worker_pid ||= begin
24
+ at_exit { wr.write(reqs.to_s) if worker_pid == $$ }
25
+ $$
26
+ end
27
+ reqs += 1
28
+
29
+ # will wake up when writer closes
30
+ sleep_pipe[0].read if env['PATH_INFO'] == '/sleep'
31
+
32
+ [ 200, [ %w(Content-Length 0), %w(Content-Type text/plain) ], [] ]
33
+ end
34
+ ENV['UNICORN_FD'] = srv.fileno.to_s
35
+ opts = {
36
+ listeners: [ "#{host}:#{port}" ],
37
+ stderr_path: err.path,
38
+ check_client_connection: true,
39
+ }
40
+ uni = Unicorn::HttpServer.new(app, opts)
41
+ uni.start.join
42
+ end
43
+ wr.close
44
+
45
+ # make sure the server is running, at least
46
+ client = tcp_socket(host, port)
47
+ client.write("GET / HTTP/1.1\r\nHost: example.com\r\n\r\n")
48
+ assert client.wait(10), 'never got response from server'
49
+ res = client.read
50
+ assert_match %r{\AHTTP/1\.1 200}, res, 'got part of first response'
51
+ assert_match %r{\r\n\r\n\z}, res, 'got end of response, server is ready'
52
+ client.close
53
+
54
+ # start a slow request...
55
+ sleeper = tcp_socket(host, port)
56
+ sleeper.write("GET /sleep HTTP/1.1\r\nHost: example.com\r\n\r\n")
57
+
58
+ # and a bunch of aborted ones
59
+ nr = 100
60
+ nr.times do |i|
61
+ client = tcp_socket(host, port)
62
+ client.write("GET /collections/#{rand(10000)} HTTP/1.1\r\n" \
63
+ "Host: example.com\r\n\r\n")
64
+ client.close
65
+ end
66
+ sleep_pipe[1].close # wake up the reader in the worker
67
+ res = sleeper.read
68
+ assert_match %r{\AHTTP/1\.1 200}, res, 'got part of first sleeper response'
69
+ assert_match %r{\r\n\r\n\z}, res, 'got end of sleeper response'
70
+ sleeper.close
71
+ kpid = pid
72
+ pid = nil
73
+ Process.kill(:QUIT, kpid)
74
+ _, status = Process.waitpid2(kpid)
75
+ assert status.success?
76
+ reqs = rd.read.to_i
77
+ warn "server got #{reqs} requests with #{nr} CCC aborted\n" if $DEBUG
78
+ assert_operator reqs, :<, nr
79
+ assert_operator reqs, :>=, 2, 'first 2 requests got through, at least'
80
+ ensure
81
+ return if start_pid != $$
82
+ srv.close if srv
83
+ if pid
84
+ Process.kill(:QUIT, pid)
85
+ _, status = Process.waitpid2(pid)
86
+ assert status.success?
87
+ end
88
+ err.close! if err
89
+ rd.close if rd
90
+ end
91
+ end
@@ -4,7 +4,7 @@
4
4
  class TestDroplet < Test::Unit::TestCase
5
5
  def test_create_many_droplets
6
6
  now = Time.now.to_i
7
- tmp = (0..1024).map do |i|
7
+ (0..1024).each do |i|
8
8
  droplet = Unicorn::Worker.new(i)
9
9
  assert droplet.respond_to?(:tick)
10
10
  assert_equal 0, droplet.tick
@@ -230,6 +230,24 @@ def test_nasty_pound_header
230
230
  assert_equal expect, req['HTTP_X_SSL_BULLSHIT']
231
231
  end
232
232
 
233
+ def test_multiline_header_0d0a
234
+ parser = HttpParser.new
235
+ parser.buf << "GET / HTTP/1.0\r\n" \
236
+ "X-Multiline-Header: foo bar\r\n\tcha cha\r\n\tzha zha\r\n\r\n"
237
+ req = parser.env
238
+ assert_equal req, parser.parse
239
+ assert_equal 'foo bar cha cha zha zha', req['HTTP_X_MULTILINE_HEADER']
240
+ end
241
+
242
+ def test_multiline_header_0a
243
+ parser = HttpParser.new
244
+ parser.buf << "GET / HTTP/1.0\n" \
245
+ "X-Multiline-Header: foo bar\n\tcha cha\n\tzha zha\n\n"
246
+ req = parser.env
247
+ assert_equal req, parser.parse
248
+ assert_equal 'foo bar cha cha zha zha', req['HTTP_X_MULTILINE_HEADER']
249
+ end
250
+
233
251
  def test_continuation_eats_leading_spaces
234
252
  parser = HttpParser.new
235
253
  header = "GET / HTTP/1.1\r\n" \
@@ -833,22 +851,34 @@ def test_empty_header
833
851
  assert_equal '', parser.env['HTTP_HOST']
834
852
  end
835
853
 
836
- # so we don't care about the portability of this test
837
- # if it doesn't leak on Linux, it won't leak anywhere else
838
- # unless your C compiler or platform is otherwise broken
839
- LINUX_PROC_PID_STATUS = "/proc/self/status"
840
- def test_memory_leak
841
- match_rss = /^VmRSS:\s+(\d+)/
842
- if File.read(LINUX_PROC_PID_STATUS) =~ match_rss
843
- before = $1.to_i
844
- 1000000.times { Unicorn::HttpParser.new }
845
- File.read(LINUX_PROC_PID_STATUS) =~ match_rss
846
- after = $1.to_i
847
- diff = after - before
848
- assert(diff < 10000, "memory grew more than 10M: #{diff}")
854
+ def test_memsize
855
+ require 'objspace'
856
+ if ObjectSpace.respond_to?(:memsize_of)
857
+ n = ObjectSpace.memsize_of(Unicorn::HttpParser.new)
858
+ assert_kind_of Integer, n
859
+ # need to update this when 128-bit machines come out
860
+ # n.b. actual struct size on 64-bit is 56 bytes + 40 bytes for RVALUE
861
+ # Ruby <= 2.2 objspace did not count the 40-byte RVALUE, 2.3 does.
862
+ assert_operator n, :<=, 96
863
+ assert_operator n, :>, 0
849
864
  end
850
- end if RUBY_PLATFORM =~ /linux/ &&
851
- File.readable?(LINUX_PROC_PID_STATUS) &&
852
- !defined?(RUBY_ENGINE)
865
+ rescue LoadError
866
+ # not all Ruby implementations have objspace
867
+ end
853
868
 
869
+ def test_dedupe
870
+ parser = HttpParser.new
871
+ # n.b. String#freeze optimization doesn't work under modern test-unit
872
+ exp = -'HTTP_HOST'
873
+ get = "GET / HTTP/1.1\r\nHost: example.com\r\nHavpbea-fhpxf: true\r\n\r\n"
874
+ assert parser.add_parse(get)
875
+ key = parser.env.keys.detect { |k| k == exp }
876
+ assert_same exp, key
877
+
878
+ if RUBY_VERSION.to_r >= 2.6 # 2.6.0-rc1+
879
+ exp = -'HTTP_HAVPBEA_FHPXF'
880
+ key = parser.env.keys.detect { |k| k == exp }
881
+ assert_same exp, key
882
+ end
883
+ end if RUBY_VERSION.to_r >= 2.5 && RUBY_ENGINE == 'ruby'
854
884
  end
@@ -8,10 +8,29 @@
8
8
  class HttpParserNgTest < Test::Unit::TestCase
9
9
 
10
10
  def setup
11
- HttpParser.keepalive_requests = HttpParser::KEEPALIVE_REQUESTS_DEFAULT
12
11
  @parser = HttpParser.new
13
12
  end
14
13
 
14
+ # RFC 7230 allows gzip/deflate/compress Transfer-Encoding,
15
+ # but "chunked" must be last if used
16
+ def test_is_chunked
17
+ [ 'chunked,chunked', 'chunked,gzip', 'chunked,gzip,chunked' ].each do |x|
18
+ assert_raise(HttpParserError) { HttpParser.is_chunked?(x) }
19
+ end
20
+ [ 'gzip, chunked', 'gzip,chunked', 'gzip ,chunked' ].each do |x|
21
+ assert HttpParser.is_chunked?(x)
22
+ end
23
+ [ 'gzip', 'xhunked', 'xchunked' ].each do |x|
24
+ assert !HttpParser.is_chunked?(x)
25
+ end
26
+ end
27
+
28
+ def test_parser_max_len
29
+ assert_raises(RangeError) do
30
+ HttpParser.max_header_len = 0xffffffff + 1
31
+ end
32
+ end
33
+
15
34
  def test_next_clear
16
35
  r = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"
17
36
  @parser.buf << r
@@ -29,23 +48,15 @@ def test_next_clear
29
48
  assert_equal false, @parser.response_start_sent
30
49
  end
31
50
 
32
- def test_keepalive_requests_default_constant
33
- assert_kind_of Integer, HttpParser::KEEPALIVE_REQUESTS_DEFAULT
34
- assert HttpParser::KEEPALIVE_REQUESTS_DEFAULT >= 0
35
- end
36
-
37
- def test_keepalive_requests_setting
38
- HttpParser.keepalive_requests = 0
39
- assert_equal 0, HttpParser.keepalive_requests
40
- HttpParser.keepalive_requests = nil
41
- assert HttpParser.keepalive_requests >= 0xffffffff
42
- HttpParser.keepalive_requests = 1
43
- assert_equal 1, HttpParser.keepalive_requests
44
- HttpParser.keepalive_requests = 666
45
- assert_equal 666, HttpParser.keepalive_requests
46
-
47
- assert_raises(TypeError) { HttpParser.keepalive_requests = "666" }
48
- assert_raises(TypeError) { HttpParser.keepalive_requests = [] }
51
+ def test_response_start_sent
52
+ assert_equal false, @parser.response_start_sent, "default is false"
53
+ @parser.response_start_sent = true
54
+ assert_equal true, @parser.response_start_sent
55
+ @parser.response_start_sent = false
56
+ assert_equal false, @parser.response_start_sent
57
+ @parser.response_start_sent = true
58
+ @parser.clear
59
+ assert_equal false, @parser.response_start_sent
49
60
  end
50
61
 
51
62
  def test_connection_TE
@@ -71,41 +82,11 @@ def test_keepalive_requests_with_next?
71
82
  "REQUEST_METHOD" => "GET",
72
83
  "QUERY_STRING" => ""
73
84
  }.freeze
74
- HttpParser::KEEPALIVE_REQUESTS_DEFAULT.times do |nr|
85
+ 100.times do |nr|
75
86
  @parser.buf << req
76
87
  assert_equal expect, @parser.parse
77
88
  assert @parser.next?
78
89
  end
79
- @parser.buf << req
80
- assert_equal expect, @parser.parse
81
- assert ! @parser.next?
82
- end
83
-
84
- def test_fewer_keepalive_requests_with_next?
85
- HttpParser.keepalive_requests = 5
86
- @parser = HttpParser.new
87
- req = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n".freeze
88
- expect = {
89
- "SERVER_NAME" => "example.com",
90
- "HTTP_HOST" => "example.com",
91
- "rack.url_scheme" => "http",
92
- "REQUEST_PATH" => "/",
93
- "SERVER_PROTOCOL" => "HTTP/1.1",
94
- "PATH_INFO" => "/",
95
- "HTTP_VERSION" => "HTTP/1.1",
96
- "REQUEST_URI" => "/",
97
- "SERVER_PORT" => "80",
98
- "REQUEST_METHOD" => "GET",
99
- "QUERY_STRING" => ""
100
- }.freeze
101
- 5.times do |nr|
102
- @parser.buf << req
103
- assert_equal expect, @parser.parse
104
- assert @parser.next?
105
- end
106
- @parser.buf << req
107
- assert_equal expect, @parser.parse
108
- assert ! @parser.next?
109
90
  end
110
91
 
111
92
  def test_default_keepalive_is_off
@@ -599,6 +580,73 @@ def test_invalid_content_length
599
580
  end
600
581
  end
601
582
 
583
+ def test_duplicate_content_length
584
+ str = "PUT / HTTP/1.1\r\n" \
585
+ "Content-Length: 1\r\n" \
586
+ "Content-Length: 9\r\n" \
587
+ "\r\n"
588
+ assert_raises(HttpParserError) { @parser.headers({}, str) }
589
+ end
590
+
591
+ def test_chunked_overrides_content_length
592
+ order = [ 'Transfer-Encoding: chunked', 'Content-Length: 666' ]
593
+ %w(a b).each do |x|
594
+ str = "PUT /#{x} HTTP/1.1\r\n" \
595
+ "#{order.join("\r\n")}" \
596
+ "\r\n\r\na\r\nhelloworld\r\n0\r\n\r\n"
597
+ order.reverse!
598
+ env = @parser.headers({}, str)
599
+ assert_nil @parser.content_length
600
+ assert_equal 'chunked', env['HTTP_TRANSFER_ENCODING']
601
+ assert_equal '666', env['CONTENT_LENGTH'],
602
+ 'Content-Length logged so the app can log a possible client bug/attack'
603
+ @parser.filter_body(dst = '', str)
604
+ assert_equal 'helloworld', dst
605
+ @parser.parse # handle the non-existent trailer
606
+ assert @parser.next?
607
+ end
608
+ end
609
+
610
+ def test_chunked_order_good
611
+ str = "PUT /x HTTP/1.1\r\n" \
612
+ "Transfer-Encoding: gzip\r\n" \
613
+ "Transfer-Encoding: chunked\r\n" \
614
+ "\r\n"
615
+ env = @parser.headers({}, str)
616
+ assert_equal 'gzip,chunked', env['HTTP_TRANSFER_ENCODING']
617
+ assert_nil @parser.content_length
618
+
619
+ @parser.clear
620
+ str = "PUT /x HTTP/1.1\r\n" \
621
+ "Transfer-Encoding: gzip, chunked\r\n" \
622
+ "\r\n"
623
+ env = @parser.headers({}, str)
624
+ assert_equal 'gzip, chunked', env['HTTP_TRANSFER_ENCODING']
625
+ assert_nil @parser.content_length
626
+ end
627
+
628
+ def test_chunked_order_bad
629
+ str = "PUT /x HTTP/1.1\r\n" \
630
+ "Transfer-Encoding: chunked\r\n" \
631
+ "Transfer-Encoding: gzip\r\n" \
632
+ "\r\n"
633
+ assert_raise(HttpParserError) { @parser.headers({}, str) }
634
+ end
635
+
636
+ def test_double_chunked
637
+ str = "PUT /x HTTP/1.1\r\n" \
638
+ "Transfer-Encoding: chunked\r\n" \
639
+ "Transfer-Encoding: chunked\r\n" \
640
+ "\r\n"
641
+ assert_raise(HttpParserError) { @parser.headers({}, str) }
642
+
643
+ @parser.clear
644
+ str = "PUT /x HTTP/1.1\r\n" \
645
+ "Transfer-Encoding: chunked,chunked\r\n" \
646
+ "\r\n"
647
+ assert_raise(HttpParserError) { @parser.headers({}, str) }
648
+ end
649
+
602
650
  def test_backtrace_is_empty
603
651
  begin
604
652
  @parser.headers({}, "AAADFSFDSFD\r\n\r\n")
@@ -663,69 +711,4 @@ def test_pipelined_requests
663
711
  assert_equal expect, env2
664
712
  assert_equal "", @parser.buf
665
713
  end
666
-
667
- def test_keepalive_requests_disabled
668
- req = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n".freeze
669
- expect = {
670
- "SERVER_NAME" => "example.com",
671
- "HTTP_HOST" => "example.com",
672
- "rack.url_scheme" => "http",
673
- "REQUEST_PATH" => "/",
674
- "SERVER_PROTOCOL" => "HTTP/1.1",
675
- "PATH_INFO" => "/",
676
- "HTTP_VERSION" => "HTTP/1.1",
677
- "REQUEST_URI" => "/",
678
- "SERVER_PORT" => "80",
679
- "REQUEST_METHOD" => "GET",
680
- "QUERY_STRING" => ""
681
- }.freeze
682
- HttpParser.keepalive_requests = 0
683
- @parser = HttpParser.new
684
- @parser.buf << req
685
- assert_equal expect, @parser.parse
686
- assert ! @parser.next?
687
- end
688
-
689
- def test_chunk_only
690
- tmp = ""
691
- assert_equal @parser, @parser.dechunk!
692
- assert_nil @parser.filter_body(tmp, "6\r\n")
693
- assert_equal "", tmp
694
- assert_nil @parser.filter_body(tmp, "abcdef")
695
- assert_equal "abcdef", tmp
696
- assert_nil @parser.filter_body(tmp, "\r\n")
697
- assert_equal "", tmp
698
- src = "0\r\n\r\n"
699
- assert_equal src.object_id, @parser.filter_body(tmp, src).object_id
700
- assert_equal "", tmp
701
- end
702
-
703
- def test_chunk_only_bad_align
704
- tmp = ""
705
- assert_equal @parser, @parser.dechunk!
706
- assert_nil @parser.filter_body(tmp, "6\r\na")
707
- assert_equal "a", tmp
708
- assert_nil @parser.filter_body(tmp, "bcde")
709
- assert_equal "bcde", tmp
710
- assert_nil @parser.filter_body(tmp, "f\r")
711
- assert_equal "f", tmp
712
- src = "\n0\r\n\r\n"
713
- assert_equal src.object_id, @parser.filter_body(tmp, src).object_id
714
- assert_equal "", tmp
715
- end
716
-
717
- def test_chunk_only_reset_ok
718
- tmp = ""
719
- assert_equal @parser, @parser.dechunk!
720
- src = "1\r\na\r\n0\r\n\r\n"
721
- assert_nil @parser.filter_body(tmp, src)
722
- assert_equal "a", tmp
723
- assert_equal src.object_id, @parser.filter_body(tmp, src).object_id
724
-
725
- assert_equal @parser, @parser.dechunk!
726
- src = "0\r\n\r\n"
727
- assert_equal src.object_id, @parser.filter_body(tmp, src).object_id
728
- assert_equal "", tmp
729
- assert_equal src, @parser.filter_body(tmp, src)
730
- end
731
714
  end