about summary refs log tree commit homepage
path: root/test/unit/test_http_parser_ng.rb
diff options
context:
space:
mode:
Diffstat (limited to 'test/unit/test_http_parser_ng.rb')
-rw-r--r--test/unit/test_http_parser_ng.rb463
1 files changed, 327 insertions, 136 deletions
diff --git a/test/unit/test_http_parser_ng.rb b/test/unit/test_http_parser_ng.rb
index cb30f32..e57428c 100644
--- a/test/unit/test_http_parser_ng.rb
+++ b/test/unit/test_http_parser_ng.rb
@@ -8,57 +8,164 @@ include Unicorn
 class HttpParserNgTest < Test::Unit::TestCase
 
   def setup
+    HttpParser.keepalive_requests = HttpParser::KEEPALIVE_REQUESTS_DEFAULT
     @parser = HttpParser.new
   end
 
+  def test_keepalive_requests_default_constant
+    assert_kind_of Integer, HttpParser::KEEPALIVE_REQUESTS_DEFAULT
+    assert HttpParser::KEEPALIVE_REQUESTS_DEFAULT >= 0
+  end
+
+  def test_keepalive_requests_setting
+    HttpParser.keepalive_requests = 0
+    assert_equal 0, HttpParser.keepalive_requests
+    HttpParser.keepalive_requests = nil
+    assert HttpParser.keepalive_requests >= 0xffffffff
+    HttpParser.keepalive_requests = 1
+    assert_equal 1, HttpParser.keepalive_requests
+    HttpParser.keepalive_requests = 666
+    assert_equal 666, HttpParser.keepalive_requests
+
+    assert_raises(TypeError) { HttpParser.keepalive_requests = "666" }
+    assert_raises(TypeError) { HttpParser.keepalive_requests = [] }
+  end
+
+  def test_keepalive_requests_with_next?
+    req = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n".freeze
+    expect = {
+      "SERVER_NAME" => "example.com",
+      "HTTP_HOST" => "example.com",
+      "rack.url_scheme" => "http",
+      "REQUEST_PATH" => "/",
+      "SERVER_PROTOCOL" => "HTTP/1.1",
+      "PATH_INFO" => "/",
+      "HTTP_VERSION" => "HTTP/1.1",
+      "REQUEST_URI" => "/",
+      "SERVER_PORT" => "80",
+      "REQUEST_METHOD" => "GET",
+      "QUERY_STRING" => ""
+    }.freeze
+    HttpParser::KEEPALIVE_REQUESTS_DEFAULT.times do |nr|
+      @parser.buf << req
+      assert_equal expect, @parser.parse
+      assert @parser.next?
+    end
+    @parser.buf << req
+    assert_equal expect, @parser.parse
+    assert ! @parser.next?
+  end
+
+  def test_fewer_keepalive_requests_with_next?
+    HttpParser.keepalive_requests = 5
+    @parser = HttpParser.new
+    req = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n".freeze
+    expect = {
+      "SERVER_NAME" => "example.com",
+      "HTTP_HOST" => "example.com",
+      "rack.url_scheme" => "http",
+      "REQUEST_PATH" => "/",
+      "SERVER_PROTOCOL" => "HTTP/1.1",
+      "PATH_INFO" => "/",
+      "HTTP_VERSION" => "HTTP/1.1",
+      "REQUEST_URI" => "/",
+      "SERVER_PORT" => "80",
+      "REQUEST_METHOD" => "GET",
+      "QUERY_STRING" => ""
+    }.freeze
+    5.times do |nr|
+      @parser.buf << req
+      assert_equal expect, @parser.parse
+      assert @parser.next?
+    end
+    @parser.buf << req
+    assert_equal expect, @parser.parse
+    assert ! @parser.next?
+  end
+
+  def test_default_keepalive_is_off
+    assert ! @parser.keepalive?
+    assert ! @parser.next?
+    assert_nothing_raised do
+      @parser.buf << "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"
+      @parser.parse
+    end
+    assert @parser.keepalive?
+    @parser.clear
+    assert ! @parser.keepalive?
+    assert ! @parser.next?
+  end
+
   def test_identity_byte_headers
-    req = {}
+    req = @parser.env
     str = "PUT / HTTP/1.1\r\n"
     str << "Content-Length: 123\r\n"
     str << "\r"
-    hdr = ""
+    hdr = @parser.buf
     str.each_byte { |byte|
-      assert_nil @parser.headers(req, hdr << byte.chr)
+      hdr << byte.chr
+      assert_nil @parser.parse
     }
     hdr << "\n"
-    assert_equal req.object_id, @parser.headers(req, hdr).object_id
+    assert_equal req.object_id, @parser.parse.object_id
     assert_equal '123', req['CONTENT_LENGTH']
     assert_equal 0, hdr.size
     assert ! @parser.keepalive?
     assert @parser.headers?
     assert_equal 123, @parser.content_length
+    dst = ""
+    buf = '.' * 123
+    @parser.filter_body(dst, buf)
+    assert_equal '.' * 123, dst
+    assert_equal "", buf
+    assert @parser.keepalive?
   end
 
   def test_identity_step_headers
-    req = {}
-    str = "PUT / HTTP/1.1\r\n"
-    assert ! @parser.headers(req, str)
+    req = @parser.env
+    str = @parser.buf
+    str << "PUT / HTTP/1.1\r\n"
+    assert ! @parser.parse
     str << "Content-Length: 123\r\n"
-    assert ! @parser.headers(req, str)
+    assert ! @parser.parse
     str << "\r\n"
-    assert_equal req.object_id, @parser.headers(req, str).object_id
+    assert_equal req.object_id, @parser.parse.object_id
     assert_equal '123', req['CONTENT_LENGTH']
     assert_equal 0, str.size
     assert ! @parser.keepalive?
     assert @parser.headers?
+    dst = ""
+    buf = '.' * 123
+    @parser.filter_body(dst, buf)
+    assert_equal '.' * 123, dst
+    assert_equal "", buf
+    assert @parser.keepalive?
   end
 
   def test_identity_oneshot_header
-    req = {}
-    str = "PUT / HTTP/1.1\r\nContent-Length: 123\r\n\r\n"
-    assert_equal req.object_id, @parser.headers(req, str).object_id
+    req = @parser.env
+    str = @parser.buf
+    str << "PUT / HTTP/1.1\r\nContent-Length: 123\r\n\r\n"
+    assert_equal req.object_id, @parser.parse.object_id
     assert_equal '123', req['CONTENT_LENGTH']
     assert_equal 0, str.size
     assert ! @parser.keepalive?
+    assert @parser.headers?
+    dst = ""
+    buf = '.' * 123
+    @parser.filter_body(dst, buf)
+    assert_equal '.' * 123, dst
+    assert_equal "", buf
   end
 
   def test_identity_oneshot_header_with_body
     body = ('a' * 123).freeze
-    req = {}
-    str = "PUT / HTTP/1.1\r\n" \
-          "Content-Length: #{body.length}\r\n" \
-          "\r\n#{body}"
-    assert_equal req.object_id, @parser.headers(req, str).object_id
+    req = @parser.env
+    str = @parser.buf
+    str << "PUT / HTTP/1.1\r\n" \
+           "Content-Length: #{body.length}\r\n" \
+           "\r\n#{body}"
+    assert_equal req.object_id, @parser.parse.object_id
     assert_equal '123', req['CONTENT_LENGTH']
     assert_equal 123, str.size
     assert_equal body, str
@@ -67,12 +174,13 @@ class HttpParserNgTest < Test::Unit::TestCase
     assert_equal 0, str.size
     assert_equal tmp, body
     assert_equal "", @parser.filter_body(tmp, str)
-    assert ! @parser.keepalive?
+    assert @parser.keepalive?
   end
 
   def test_identity_oneshot_header_with_body_partial
-    str = "PUT / HTTP/1.1\r\nContent-Length: 123\r\n\r\na"
-    assert_equal Hash, @parser.headers({}, str).class
+    str = @parser.buf
+    str << "PUT / HTTP/1.1\r\nContent-Length: 123\r\n\r\na"
+    assert_equal Hash, @parser.parse.class
     assert_equal 1, str.size
     assert_equal 'a', str
     tmp = ''
@@ -85,12 +193,13 @@ class HttpParserNgTest < Test::Unit::TestCase
     assert_nil rv
     assert_equal "", str
     assert_equal str.object_id, @parser.filter_body(tmp, str).object_id
-    assert ! @parser.keepalive?
+    assert @parser.keepalive?
   end
 
   def test_identity_oneshot_header_with_body_slop
-    str = "PUT / HTTP/1.1\r\nContent-Length: 1\r\n\r\naG"
-    assert_equal Hash, @parser.headers({}, str).class
+    str = @parser.buf
+    str << "PUT / HTTP/1.1\r\nContent-Length: 1\r\n\r\naG"
+    assert_equal Hash, @parser.parse.class
     assert_equal 2, str.size
     assert_equal 'aG', str
     tmp = ''
@@ -99,92 +208,100 @@ class HttpParserNgTest < Test::Unit::TestCase
     assert_equal "G", @parser.filter_body(tmp, str)
     assert_equal 1, tmp.size
     assert_equal "a", tmp
-    assert ! @parser.keepalive?
+    assert @parser.keepalive?
   end
 
   def test_chunked
-    str = "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n"
-    req = {}
-    assert_equal req, @parser.headers(req, str)
+    str = @parser.buf
+    req = @parser.env
+    str << "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n"
+    assert_equal req, @parser.parse, "msg=#{str}"
     assert_equal 0, str.size
     tmp = ""
-    assert_nil @parser.filter_body(tmp, "6")
+    assert_nil @parser.filter_body(tmp, str << "6")
     assert_equal 0, tmp.size
-    assert_nil @parser.filter_body(tmp, rv = "\r\n")
-    assert_equal 0, rv.size
+    assert_nil @parser.filter_body(tmp, str << "\r\n")
+    assert_equal 0, str.size
     assert_equal 0, tmp.size
     tmp = ""
-    assert_nil @parser.filter_body(tmp, "..")
+    assert_nil @parser.filter_body(tmp, str << "..")
     assert_equal "..", tmp
-    assert_nil @parser.filter_body(tmp, "abcd\r\n0\r\n")
+    assert_nil @parser.filter_body(tmp, str << "abcd\r\n0\r\n")
     assert_equal "abcd", tmp
-    rv = "PUT"
-    assert_equal rv.object_id, @parser.filter_body(tmp, rv).object_id
-    assert_equal "PUT", rv
+    assert_equal str.object_id, @parser.filter_body(tmp, str << "PUT").object_id
+    assert_equal "PUT", str
     assert ! @parser.keepalive?
+    str << "TY: FOO\r\n\r\n"
+    assert_equal req, @parser.parse
+    assert_equal "FOO", req["HTTP_PUTTY"]
+    assert @parser.keepalive?
   end
 
   def test_two_chunks
-    str = "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n"
-    req = {}
-    assert_equal req, @parser.headers(req, str)
+    str = @parser.buf
+    str << "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n"
+    req = @parser.env
+    assert_equal req, @parser.parse
     assert_equal 0, str.size
     tmp = ""
-    assert_nil @parser.filter_body(tmp, "6")
+    assert_nil @parser.filter_body(tmp, str << "6")
     assert_equal 0, tmp.size
-    assert_nil @parser.filter_body(tmp, rv = "\r\n")
-    assert_equal "", rv
+    assert_nil @parser.filter_body(tmp, str << "\r\n")
+    assert_equal "", str
     assert_equal 0, tmp.size
     tmp = ""
-    assert_nil @parser.filter_body(tmp, "..")
+    assert_nil @parser.filter_body(tmp, str << "..")
     assert_equal 2, tmp.size
     assert_equal "..", tmp
-    assert_nil @parser.filter_body(tmp, "abcd\r\n1")
+    assert_nil @parser.filter_body(tmp, str << "abcd\r\n1")
     assert_equal "abcd", tmp
-    assert_nil @parser.filter_body(tmp, "\r")
+    assert_nil @parser.filter_body(tmp, str << "\r")
     assert_equal "", tmp
-    assert_nil @parser.filter_body(tmp, "\n")
+    assert_nil @parser.filter_body(tmp, str << "\n")
     assert_equal "", tmp
-    assert_nil @parser.filter_body(tmp, "z")
+    assert_nil @parser.filter_body(tmp, str << "z")
     assert_equal "z", tmp
-    assert_nil @parser.filter_body(tmp, "\r\n")
-    assert_nil @parser.filter_body(tmp, "0")
-    assert_nil @parser.filter_body(tmp, "\r")
-    rv = @parser.filter_body(tmp, buf = "\nGET")
+    assert_nil @parser.filter_body(tmp, str << "\r\n")
+    assert_nil @parser.filter_body(tmp, str << "0")
+    assert_nil @parser.filter_body(tmp, str << "\r")
+    rv = @parser.filter_body(tmp, str << "\nGET")
     assert_equal "GET", rv
-    assert_equal buf.object_id, rv.object_id
+    assert_equal str.object_id, rv.object_id
     assert ! @parser.keepalive?
   end
 
   def test_big_chunk
-    str = "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n" \
-          "4000\r\nabcd"
-    req = {}
-    assert_equal req, @parser.headers(req, str)
+    str = @parser.buf
+    str << "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n" \
+           "4000\r\nabcd"
+    req = @parser.env
+    assert_equal req, @parser.parse
     tmp = ''
     assert_nil @parser.filter_body(tmp, str)
     assert_equal '', str
-    str = ' ' * 16300
+    str << ' ' * 16300
     assert_nil @parser.filter_body(tmp, str)
     assert_equal '', str
-    str = ' ' * 80
+    str << ' ' * 80
     assert_nil @parser.filter_body(tmp, str)
     assert_equal '', str
     assert ! @parser.body_eof?
-    assert_equal "", @parser.filter_body(tmp, "\r\n0\r\n")
+    assert_equal "", @parser.filter_body(tmp, str << "\r\n0\r\n")
     assert_equal "", tmp
     assert @parser.body_eof?
-    assert_equal req, @parser.trailers(req, moo = "\r\n")
-    assert_equal "", moo
+    str << "\r\n"
+    assert_equal req, @parser.parse
+    assert_equal "", str
     assert @parser.body_eof?
-    assert ! @parser.keepalive?
+    assert @parser.keepalive?
   end
 
   def test_two_chunks_oneshot
-    str = "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n" \
-          "1\r\na\r\n2\r\n..\r\n0\r\n"
-    req = {}
-    assert_equal req, @parser.headers(req, str)
+    str = @parser.buf
+    req = @parser.env
+    str << "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n" \
+           "1\r\na\r\n2\r\n..\r\n0\r\n"
+    assert_equal req, @parser.parse
     tmp = ''
     assert_nil @parser.filter_body(tmp, str)
     assert_equal 'a..', tmp
@@ -195,31 +312,33 @@ class HttpParserNgTest < Test::Unit::TestCase
 
   def test_chunks_bytewise
     chunked = "10\r\nabcdefghijklmnop\r\n11\r\n0123456789abcdefg\r\n0\r\n"
-    str = "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n#{chunked}"
-    req = {}
-    assert_equal req, @parser.headers(req, str)
-    assert_equal chunked, str
+    str = "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n"
+    buf = @parser.buf
+    buf << str
+    req = @parser.env
+    assert_equal req, @parser.parse
+    assert_equal "", buf
     tmp = ''
-    buf = ''
     body = ''
-    str = str[0..-2]
+    str = chunked[0..-2]
     str.each_byte { |byte|
       assert_nil @parser.filter_body(tmp, buf << byte.chr)
       body << tmp
     }
     assert_equal 'abcdefghijklmnop0123456789abcdefg', body
-    rv = @parser.filter_body(tmp, buf << "\n")
+    rv = @parser.filter_body(tmp, buf<< "\n")
     assert_equal rv.object_id, buf.object_id
     assert ! @parser.keepalive?
   end
 
   def test_trailers
-    str = "PUT / HTTP/1.1\r\n" \
-          "Trailer: Content-MD5\r\n" \
-          "transfer-Encoding: chunked\r\n\r\n" \
-          "1\r\na\r\n2\r\n..\r\n0\r\n"
-    req = {}
-    assert_equal req, @parser.headers(req, str)
+    req = @parser.env
+    str = @parser.buf
+    str << "PUT / HTTP/1.1\r\n" \
+           "Trailer: Content-MD5\r\n" \
+           "transfer-Encoding: chunked\r\n\r\n" \
+           "1\r\na\r\n2\r\n..\r\n0\r\n"
+    assert_equal req, @parser.parse
     assert_equal 'Content-MD5', req['HTTP_TRAILER']
     assert_nil req['HTTP_CONTENT_MD5']
     tmp = ''
@@ -234,19 +353,22 @@ class HttpParserNgTest < Test::Unit::TestCase
     assert_nil @parser.trailers(req, str)
     assert_equal md5_b64, req['HTTP_CONTENT_MD5']
     assert_equal "CONTENT_MD5: #{md5_b64}\r\n", str
-    assert_nil @parser.trailers(req, str << "\r")
-    assert_equal req, @parser.trailers(req, str << "\nGET / ")
+    str << "\r"
+    assert_nil @parser.parse
+    str << "\nGET / "
+    assert_equal req, @parser.parse
     assert_equal "GET / ", str
-    assert ! @parser.keepalive?
+    assert @parser.keepalive?
   end
 
   def test_trailers_slowly
-    str = "PUT / HTTP/1.1\r\n" \
-          "Trailer: Content-MD5\r\n" \
-          "transfer-Encoding: chunked\r\n\r\n" \
-          "1\r\na\r\n2\r\n..\r\n0\r\n"
-    req = {}
-    assert_equal req, @parser.headers(req, str)
+    str = @parser.buf
+    str << "PUT / HTTP/1.1\r\n" \
+           "Trailer: Content-MD5\r\n" \
+           "transfer-Encoding: chunked\r\n\r\n" \
+           "1\r\na\r\n2\r\n..\r\n0\r\n"
+    req = @parser.env
+    assert_equal req, @parser.parse
     assert_equal 'Content-MD5', req['HTTP_TRAILER']
     assert_nil req['HTTP_CONTENT_MD5']
     tmp = ''
@@ -264,16 +386,19 @@ class HttpParserNgTest < Test::Unit::TestCase
     }
     assert_equal md5_b64, req['HTTP_CONTENT_MD5']
     assert_equal "CONTENT_MD5: #{md5_b64}\r\n", str
-    assert_nil @parser.trailers(req, str << "\r")
-    assert_equal req, @parser.trailers(req, str << "\n")
+    str << "\r"
+    assert_nil @parser.parse
+    str << "\n"
+    assert_equal req, @parser.parse
   end
 
   def test_max_chunk
-    str = "PUT / HTTP/1.1\r\n" \
-          "transfer-Encoding: chunked\r\n\r\n" \
-          "#{HttpParser::CHUNK_MAX.to_s(16)}\r\na\r\n2\r\n..\r\n0\r\n"
-    req = {}
-    assert_equal req, @parser.headers(req, str)
+    str = @parser.buf
+    str << "PUT / HTTP/1.1\r\n" \
+           "transfer-Encoding: chunked\r\n\r\n" \
+           "#{HttpParser::CHUNK_MAX.to_s(16)}\r\na\r\n2\r\n..\r\n0\r\n"
+    req = @parser.env
+    assert_equal req, @parser.parse
     assert_nil @parser.content_length
     assert_nothing_raised { @parser.filter_body('', str) }
     assert ! @parser.keepalive?
@@ -281,64 +406,61 @@ class HttpParserNgTest < Test::Unit::TestCase
 
   def test_max_body
     n = HttpParser::LENGTH_MAX
-    str = "PUT / HTTP/1.1\r\nContent-Length: #{n}\r\n\r\n"
-    req = {}
-    assert_nothing_raised { @parser.headers(req, str) }
+    @parser.buf << "PUT / HTTP/1.1\r\nContent-Length: #{n}\r\n\r\n"
+    req = @parser.env
+    assert_nothing_raised { @parser.headers(req, @parser.buf) }
     assert_equal n, req['CONTENT_LENGTH'].to_i
     assert ! @parser.keepalive?
   end
 
   def test_overflow_chunk
     n = HttpParser::CHUNK_MAX + 1
-    str = "PUT / HTTP/1.1\r\n" \
-          "transfer-Encoding: chunked\r\n\r\n" \
-          "#{n.to_s(16)}\r\na\r\n2\r\n..\r\n0\r\n"
-    req = {}
-    assert_equal req, @parser.headers(req, str)
+    str = @parser.buf
+    req = @parser.env
+    str << "PUT / HTTP/1.1\r\n" \
+           "transfer-Encoding: chunked\r\n\r\n" \
+           "#{n.to_s(16)}\r\na\r\n2\r\n..\r\n0\r\n"
+    assert_equal req, @parser.parse
     assert_nil @parser.content_length
     assert_raise(HttpParserError) { @parser.filter_body('', str) }
-    assert ! @parser.keepalive?
   end
 
   def test_overflow_content_length
     n = HttpParser::LENGTH_MAX + 1
-    str = "PUT / HTTP/1.1\r\nContent-Length: #{n}\r\n\r\n"
-    assert_raise(HttpParserError) { @parser.headers({}, str) }
-    assert ! @parser.keepalive?
+    @parser.buf << "PUT / HTTP/1.1\r\nContent-Length: #{n}\r\n\r\n"
+    assert_raise(HttpParserError) { @parser.parse }
   end
 
   def test_bad_chunk
-    str = "PUT / HTTP/1.1\r\n" \
-          "transfer-Encoding: chunked\r\n\r\n" \
-          "#zzz\r\na\r\n2\r\n..\r\n0\r\n"
-    req = {}
-    assert_equal req, @parser.headers(req, str)
+    @parser.buf << "PUT / HTTP/1.1\r\n" \
+                   "transfer-Encoding: chunked\r\n\r\n" \
+                   "#zzz\r\na\r\n2\r\n..\r\n0\r\n"
+    req = @parser.env
+    assert_equal req, @parser.parse
     assert_nil @parser.content_length
-    assert_raise(HttpParserError) { @parser.filter_body('', str) }
-    assert ! @parser.keepalive?
+    assert_raise(HttpParserError) { @parser.filter_body("", @parser.buf) }
   end
 
   def test_bad_content_length
-    str = "PUT / HTTP/1.1\r\nContent-Length: 7ff\r\n\r\n"
-    assert_raise(HttpParserError) { @parser.headers({}, str) }
-    assert ! @parser.keepalive?
+    @parser.buf << "PUT / HTTP/1.1\r\nContent-Length: 7ff\r\n\r\n"
+    assert_raise(HttpParserError) { @parser.parse }
   end
 
   def test_bad_trailers
-    str = "PUT / HTTP/1.1\r\n" \
-          "Trailer: Transfer-Encoding\r\n" \
-          "transfer-Encoding: chunked\r\n\r\n" \
-          "1\r\na\r\n2\r\n..\r\n0\r\n"
-    req = {}
-    assert_equal req, @parser.headers(req, str)
+    str = @parser.buf
+    req = @parser.env
+    str << "PUT / HTTP/1.1\r\n" \
+           "Trailer: Transfer-Encoding\r\n" \
+           "transfer-Encoding: chunked\r\n\r\n" \
+           "1\r\na\r\n2\r\n..\r\n0\r\n"
+    assert_equal req, @parser.parse
     assert_equal 'Transfer-Encoding', req['HTTP_TRAILER']
     tmp = ''
     assert_nil @parser.filter_body(tmp, str)
     assert_equal 'a..', tmp
     assert_equal '', str
     str << "Transfer-Encoding: identity\r\n\r\n"
-    assert_raise(HttpParserError) { @parser.trailers(req, str) }
-    assert ! @parser.keepalive?
+    assert_raise(HttpParserError) { @parser.parse }
   end
 
   def test_repeat_headers
@@ -347,18 +469,19 @@ class HttpParserNgTest < Test::Unit::TestCase
           "Trailer: Content-SHA1\r\n" \
           "transfer-Encoding: chunked\r\n\r\n" \
           "1\r\na\r\n2\r\n..\r\n0\r\n"
-    req = {}
-    assert_equal req, @parser.headers(req, str)
+    req = @parser.env
+    @parser.buf << str
+    assert_equal req, @parser.parse
     assert_equal 'Content-MD5,Content-SHA1', req['HTTP_TRAILER']
     assert ! @parser.keepalive?
   end
 
   def test_parse_simple_request
     parser = HttpParser.new
-    req = {}
-    http = "GET /read-rfc1945-if-you-dont-believe-me\r\n"
-    assert_equal req, parser.headers(req, http)
-    assert_equal '', http
+    req = parser.env
+    parser.buf << "GET /read-rfc1945-if-you-dont-believe-me\r\n"
+    assert_equal req, parser.parse
+    assert_equal '', parser.buf
     expect = {
       "SERVER_NAME"=>"localhost",
       "rack.url_scheme"=>"http",
@@ -388,7 +511,8 @@ class HttpParserNgTest < Test::Unit::TestCase
       "*" => { qs => "", pi => "" },
     }.each do |uri,expect|
       assert_equal req, @parser.headers(req.clear, str % [ uri ])
-      @parser.reset
+      req = req.dup
+      @parser.clear
       assert_equal uri, req["REQUEST_URI"], "REQUEST_URI mismatch"
       assert_equal expect[qs], req[qs], "#{qs} mismatch"
       assert_equal expect[pi], req[pi], "#{pi} mismatch"
@@ -412,7 +536,8 @@ class HttpParserNgTest < Test::Unit::TestCase
       "/1?a=b;c=d&e=f" => { qs => "a=b;c=d&e=f", pi => "/1" },
     }.each do |uri,expect|
       assert_equal req, @parser.headers(req.clear, str % [ uri ])
-      @parser.reset
+      req = req.dup
+      @parser.clear
       assert_equal uri, req["REQUEST_URI"], "REQUEST_URI mismatch"
       assert_equal "example.com", req["HTTP_HOST"], "Host: mismatch"
       assert_equal expect[qs], req[qs], "#{qs} mismatch"
@@ -440,11 +565,22 @@ class HttpParserNgTest < Test::Unit::TestCase
     end
   end
 
+  def test_backtrace_is_empty
+    begin
+      @parser.headers({}, "AAADFSFDSFD\r\n\r\n")
+      assert false, "should never get here line:#{__LINE__}"
+    rescue HttpParserError => e
+      assert_equal [], e.backtrace
+      return
+    end
+    assert false, "should never get here line:#{__LINE__}"
+  end
+
   def test_ignore_version_header
-    http = "GET / HTTP/1.1\r\nVersion: hello\r\n\r\n"
-    req = {}
-    assert_equal req, @parser.headers(req, http)
-    assert_equal '', http
+    @parser.buf << "GET / HTTP/1.1\r\nVersion: hello\r\n\r\n"
+    req = @parser.env
+    assert_equal req, @parser.parse
+    assert_equal '', @parser.buf
     expect = {
       "SERVER_NAME" => "localhost",
       "rack.url_scheme" => "http",
@@ -460,4 +596,59 @@ class HttpParserNgTest < Test::Unit::TestCase
     assert_equal expect, req
   end
 
+  def test_pipelined_requests
+    host = "example.com"
+    expect = {
+      "HTTP_HOST" => host,
+      "SERVER_NAME" => host,
+      "REQUEST_PATH" => "/",
+      "rack.url_scheme" => "http",
+      "SERVER_PROTOCOL" => "HTTP/1.1",
+      "PATH_INFO" => "/",
+      "HTTP_VERSION" => "HTTP/1.1",
+      "REQUEST_URI" => "/",
+      "SERVER_PORT" => "80",
+      "REQUEST_METHOD" => "GET",
+      "QUERY_STRING" => ""
+    }
+    req1 = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"
+    req2 = "GET / HTTP/1.1\r\nHost: www.example.com\r\n\r\n"
+    @parser.buf << (req1 + req2)
+    env1 = @parser.parse.dup
+    assert_equal expect, env1
+    assert_equal req2, @parser.buf
+    assert ! @parser.env.empty?
+    assert @parser.next?
+    assert @parser.keepalive?
+    assert @parser.headers?
+    assert_equal expect, @parser.env
+    env2 = @parser.parse.dup
+    host.replace "www.example.com"
+    assert_equal "www.example.com", expect["HTTP_HOST"]
+    assert_equal "www.example.com", expect["SERVER_NAME"]
+    assert_equal expect, env2
+    assert_equal "", @parser.buf
+  end
+
+  def test_keepalive_requests_disabled
+    req = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n".freeze
+    expect = {
+      "SERVER_NAME" => "example.com",
+      "HTTP_HOST" => "example.com",
+      "rack.url_scheme" => "http",
+      "REQUEST_PATH" => "/",
+      "SERVER_PROTOCOL" => "HTTP/1.1",
+      "PATH_INFO" => "/",
+      "HTTP_VERSION" => "HTTP/1.1",
+      "REQUEST_URI" => "/",
+      "SERVER_PORT" => "80",
+      "REQUEST_METHOD" => "GET",
+      "QUERY_STRING" => ""
+    }.freeze
+    HttpParser.keepalive_requests = 0
+    @parser = HttpParser.new
+    @parser.buf << req
+    assert_equal expect, @parser.parse
+    assert ! @parser.next?
+  end
 end