# -*- encoding: binary -*- require 'test/unit' require 'pp' require 'socket' require 'kcar' require 'digest/sha1' $stderr.sync = true class TestSession < Test::Unit::TestCase def setup @s, @c = UNIXSocket.pair end def test_http_status_only_pipelined resp = "HTTP/1.1 404 Not Found\r\n\r\n" \ "HTTP/1.1 404 Not Found\r\n\r\n" pid = fork do @s << resp @s.close end @s.close @response = Kcar::Response.new(@c) status, headers, body = @response.rack assert_equal status, "404 Not Found" assert_equal({}, headers) tmp = [] assert_nothing_raised { body.each { |chunk| tmp << chunk.dup } } assert_equal [], tmp assert @response.parser.keepalive? assert @response.parser.body_eof? body.close status, headers, body = @response.rack assert_equal status, "404 Not Found" assert_equal({},headers) tmp = [] assert_nothing_raised { body.each { |chunk| tmp << chunk.dup } } assert_equal [], tmp _, status = Process.waitpid2(pid) assert status.success? body.close end def test_http_small_pipelined_identity resp = "HTTP/1.1 200 OK\r\nContent-Length: 12\r\n\r\nhello world\n" \ "HTTP/1.1 200 OK\r\nContent-Length: 14\r\n\r\ngoodbye world\n" pid = fork do @s << resp @s.close end @s.close @response = Kcar::Response.new(@c) status, headers, body = @response.rack assert_equal status, "200 OK" assert_equal({'Content-Length'=>'12'},headers) tmp = [] assert_nothing_raised { body.each { |chunk| tmp << chunk.dup } } assert_equal [ "hello world\n" ], tmp body.close assert ! @c.closed? status, headers, body = @response.rack assert_equal status, "200 OK" assert_equal({'Content-Length'=>'14'},headers) tmp = [] assert_nothing_raised { body.each { |chunk| tmp << chunk.dup } } assert_equal [ "goodbye world\n" ], tmp _, status = Process.waitpid2(pid) assert status.success? body.close end def test_http_big_pipelined_identity nr = 1024 * 512 width = 80 length = nr * width template = "%0#{width - 1}x\n" expect = Digest::SHA1.new nr.times { |i| expect << sprintf(template, i) } pid = fork do @s.sync = false @s << "HTTP/1.1 200 OK\r\nContent-Length: #{length}\r\n\r\n" nr.times { |i| @s.printf(template, i) } @s << "HTTP/1.1 200 OK\r\nContent-Length: #{length}\r\n\r\n" nr.times { |i| @s.printf(template, i) } @s.close end @s.close @response = Kcar::Response.new(@c) 2.times do |i| status, headers, body = @response.rack assert_equal status, "200 OK" assert_equal({'Content-Length'=>length.to_s}, headers) sha1 = Digest::SHA1.new assert_nothing_raised { body.each { |chunk| sha1 << chunk } } assert_equal expect, sha1, "#{expect.hexdigest} != #{sha1.hexdigest}" body.close assert ! @c.closed? end _, status = Process.waitpid2(pid) assert status.success? end def test_http_one_zero pid = fork do @s << "HTTP/1.0 200 OK\r\n\r\nHI" @s.close end @s.close @response = Kcar::Response.new(@c) status, headers, body = @response.read assert_equal status, "200 OK" assert headers.empty? tmp = [] assert ! body.parser.keepalive? assert_nothing_raised { body.each { |chunk| tmp << chunk.dup } } assert_equal [ "HI" ], tmp _, status = Process.waitpid2(pid) assert status.success? body.close assert @c.closed? end def test_http_keepalive pid = fork do @s << "HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nHI" end @response = Kcar::Response.new(@c) status, headers, body = @response.read assert_equal status, "200 OK" assert_equal({"Content-Length" => "2" }, headers) tmp = [] assert body.parser.keepalive? assert_nothing_raised { body.each { |chunk| tmp << chunk.dup } } assert body.parser.body_eof? assert body.parser.keepalive? assert_equal [ "HI" ], tmp _, status = Process.waitpid2(pid) assert status.success? body.close assert ! @c.closed? pid = fork do @s << "HTTP/1.1 200 OK\r\nContent-Length: 3\r\n" @s << "Connection: close\r\n\r\nBYE" end status, headers, body = @response.read assert_equal status, "200 OK" assert_equal({ "Content-Length" => "3", "Connection" => "close" }, headers) tmp = [] assert ! body.parser.keepalive? assert_nothing_raised { body.each { |chunk| tmp << chunk.dup } } assert_equal [ "BYE" ], tmp _, status = Process.waitpid2(pid) assert status.success? body.close assert @c.closed? end def test_http_keepalive_chunky @response = Kcar::Response.new(@c) pid = fork do @s << "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n" @s << "5\r\nabcde\r\n" @s << "0\r\n\r\nHTTP/1.1 " # partial response end status, headers, body = @response.read assert_equal status, "200 OK" assert_equal({"Transfer-Encoding" => "chunked" }, headers) tmp = [] assert body.parser.keepalive? assert_nothing_raised { body.each { |chunk| tmp << chunk.dup } } assert body.parser.keepalive? assert body.parser.body_eof? assert_equal [ "abcde" ], tmp _, status = Process.waitpid2(pid) assert status.success? body.close assert ! @c.closed? assert_equal "HTTP/1.1 ", @response.buf pid = fork do @s << "200 OK\r\nContent-Length: 3\r\n" @s << "Connection: close\r\n\r\nBYE" end status, headers, body = @response.read assert_equal status, "200 OK" assert_equal({ "Content-Length" => "3", "Connection" => "close" }, headers) tmp = [] assert ! body.parser.keepalive? assert_nothing_raised { body.each { |chunk| tmp << chunk.dup } } assert_equal [ "BYE" ], tmp _, status = Process.waitpid2(pid) assert status.success? body.close assert @c.closed? end def test_http_no_body_keepalive pid = fork { @s << "HTTP/1.1 100 Continue\r\n\r\n" } @response = Kcar::Response.new(@c) status, headers, body = @response.read assert_equal status, "100 Continue" assert_equal({}, headers) tmp = [] assert body.parser.keepalive? assert_nothing_raised { body.each { |chunk| tmp << chunk.dup } } assert tmp.empty? _, status = Process.waitpid2(pid) assert status.success? body.close assert ! @c.closed? pid = fork { @s << "HTTP/1.1 200 OK\r\nConnection: close\r\n\r\nhello" } @s.close status, headers, body = @response.read assert_equal status, "200 OK" assert_equal({'Connection' => 'close'}, headers) tmp = [] assert ! body.parser.keepalive? assert_nothing_raised { body.each { |chunk| tmp << chunk.dup } } assert_equal(%w(hello), tmp) _, status = Process.waitpid2(pid) assert status.success? body.close assert @c.closed? end def test_trailers pid = fork do @s << "HTTP/1.1 200 OK\r\nTrailer: Foo\r\n" @s << "Transfer-Encoding: chunked\r\n\r\n" end @response = Kcar::Response.new(@c) status, headers, body = @response.read assert_equal status, "200 OK" expect = { "Trailer" => "Foo", "Transfer-Encoding" => "chunked", } assert_equal(expect, headers) assert body.parser.keepalive? _, status = Process.waitpid2(pid) assert status.success? tmp = [] pid = fork { @s << "5\r\nhello\r\n0\r\nFoo: bar\r\n\r\n" } assert_nothing_raised { body.each { |chunk| tmp << chunk.dup } } assert_equal %w(hello), tmp expect['Foo'] = 'bar' assert_equal(expect, headers) _, status = Process.waitpid2(pid) assert status.success? body.close assert ! @c.closed? end def test_trailers_pass_through pid = fork do @s << "HTTP/1.1 200 OK\r\nTrailer: Foo\r\n" @s << "Transfer-Encoding: chunked\r\n\r\n" end @response = Kcar::Response.new(@c, {}, false) status, headers, body = @response.read assert_equal status, "200 OK" expect = { "Trailer" => "Foo", "Transfer-Encoding" => "chunked", } assert_equal(expect, headers) assert body.parser.keepalive? _, status = Process.waitpid2(pid) assert status.success? tmp = [] pid = fork { @s << "5\r\nhello\r\n0\r\nFoo: bar\r\n\r\n" } assert_nothing_raised { body.each { |chunk| tmp << chunk.dup } } assert_equal ["5\r\n", "hello\r\n", "0\r\n", "Foo: bar\r\n\r\n"], tmp expect['Foo'] = 'bar' assert_equal(expect, headers) _, status = Process.waitpid2(pid) assert status.success? body.close assert ! @c.closed? end def test_pass_through_one_oh pid = fork do @s << "HTTP/1.0 200 OK\r\n" @s << "Content-Type: text/plain\r\n\r\n" end @response = Kcar::Response.new(@c, {}, false) status, headers, body = @response.read assert_equal status, "200 OK" expect = { "Content-Type" => "text/plain", } assert_equal(expect, headers) assert ! body.parser.keepalive? _, status = Process.waitpid2(pid) assert status.success? tmp = [] pid = fork { @s << "hello" } @s.close assert_nothing_raised { body.each { |chunk| tmp << chunk.dup } } assert_equal %w(hello), tmp assert_equal(expect, headers) _, status = Process.waitpid2(pid) assert status.success? body.close assert @c.closed? end def test_trailers_burpy pid = fork do @s << "HTTP/1.1 200 OK\r\nTrailer: Foo\r\n" @s << "Transfer-Encoding: chunked\r\n\r\n" end @response = Kcar::Response.new(@c) status, headers, body = @response.read assert_equal status, "200 OK" expect = { "Trailer" => "Foo", "Transfer-Encoding" => "chunked", } assert_equal(expect, headers) assert body.parser.keepalive? _, status = Process.waitpid2(pid) assert status.success? tmp = [] pid = fork { @s << "5\r\nhello\r\n0\r\nFoo: bar" } rd, wr = IO.pipe crlf_pid = fork do wr.close @s << rd.sysread(4) end rd.close assert_nothing_raised do first = true body.each do |chunk| tmp << chunk.dup if first first = false wr.syswrite "\r\n\r\n" end end end assert_equal %w(hello), tmp _, status = Process.waitpid2(pid) assert status.success? _, status = Process.waitpid2(crlf_pid) assert status.success? expect['Foo'] = 'bar' assert_equal(expect, headers) body.close assert ! @c.closed? end def test_pass_through_trailers_burpy pid = fork do @s << "HTTP/1.1 200 OK\r\nTrailer: Foo\r\n" @s << "Transfer-Encoding: chunked\r\n\r\n" end @response = Kcar::Response.new(@c, {}, false) status, headers, body = @response.read assert_equal status, "200 OK" expect = { "Trailer" => "Foo", "Transfer-Encoding" => "chunked", } assert_equal(expect, headers) assert body.parser.keepalive? _, status = Process.waitpid2(pid) assert status.success? tmp = [] pid = fork { @s << "5\r\nhello\r\n0\r\nFoo: bar" } rd, wr = IO.pipe crlf_pid = fork do wr.close @s << rd.sysread(4) end rd.close assert_nothing_raised do first = true body.each do |chunk| tmp << chunk.dup if first first = false wr.syswrite "\r\n\r\n" end end end assert_equal ["5\r\n", "hello\r\n", "0\r\n", "Foo: bar\r\n\r\n"], tmp _, status = Process.waitpid2(pid) assert status.success? _, status = Process.waitpid2(crlf_pid) assert status.success? expect['Foo'] = 'bar' assert_equal(expect, headers) body.close assert ! @c.closed? end def test_identity_burpy pid = fork { @s << "HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\n" } @response = Kcar::Response.new(@c) status, headers, body = @response.read assert_equal status, "200 OK" expect = { "Content-Length" => '5' } assert_equal(expect, headers) assert body.parser.keepalive? _, status = Process.waitpid2(pid) assert status.success? tmp = [] pid = fork { @s << "h" } rd, wr = IO.pipe crlf_pid = fork do wr.close @s << rd.sysread(4) end rd.close assert_nothing_raised do first = true body.each do |chunk| tmp << chunk.dup if first first = false wr.syswrite "ello" end end end assert_equal %w(h ello), tmp _, status = Process.waitpid2(pid) assert status.success? _, status = Process.waitpid2(crlf_pid) assert status.success? assert_equal(expect, headers) body.close assert ! @c.closed? end def test_rack_preserve_chunk_hash pid = fork do @s << "HTTP/1.1 200 OK\r\n" @s << "Trailer: Foo\r\n" @s << "Transfer-Encoding: chunked\r\n\r\n" @s << "5\r\nhello\r\n0\r\nFoo: bar\r\n\r\n" end @response = Kcar::Response.new(@c) status, headers, body = @response.rack assert_equal status, "200 OK" expect = { "Trailer" => "Foo", "Transfer-Encoding" => "chunked", } assert_equal expect, headers tmp = [] assert body.parser.keepalive? assert_nothing_raised { body.each { |chunk| tmp << chunk.dup } } assert_equal ["5\r\n", "hello\r\n", "0\r\n", "Foo: bar\r\n\r\n"], tmp expect["Foo"] = "bar" assert_equal expect, headers _, status = Process.waitpid2(pid) assert status.success? body.close assert ! @c.closed? end def test_rack_preserve_chunk_ary pid = fork do @s << "HTTP/1.1 200 OK\r\n" @s << "Trailer: Foo\r\n" @s << "Transfer-Encoding: chunked\r\n\r\n" @s << "5\r\nhello\r\n0\r\nFoo: bar\r\n\r\n" end @response = Kcar::Response.new(@c, []) status, headers, body = @response.rack assert_equal status, "200 OK" expect = [ %w(Trailer Foo), %w(Transfer-Encoding chunked) ] assert_equal expect, headers tmp = [] assert body.parser.keepalive? assert_nothing_raised { body.each { |chunk| tmp << chunk.dup } } assert_equal ["5\r\n", "hello\r\n", "0\r\n", "Foo: bar\r\n\r\n"], tmp expect << %w(Foo bar) assert_equal expect, headers _, status = Process.waitpid2(pid) assert status.success? body.close assert ! @c.closed? end def test_rack_preserve_chunk_no_keepalive pid = fork do @s << "HTTP/1.1 200 OK\r\n" @s << "Connection: close\r\n" @s << "Trailer: Foo\r\n" @s << "Transfer-Encoding: chunked\r\n\r\n" @s << "5\r\nhello\r\n0\r\nFoo: bar\r\n\r\n" end @s.close @response = Kcar::Response.new(@c, []) status, headers, body = @response.rack assert_kind_of Array, headers assert_equal status, "200 OK" tmp = [] assert ! body.parser.keepalive? assert_nothing_raised { body.each { |chunk| tmp << chunk.dup } } assert_equal "5\r\nhello\r\n0\r\nFoo: bar\r\n\r\n", tmp.join _, status = Process.waitpid2(pid) assert status.success? body.close assert @c.closed? end def test_rack_preserve_chunk_no_keepalive_2 s = "HTTP/1.1 200 OK\r\n" s << "Connection: close\r\n" s << "Content-Length: 666\r\n" s << "\r\n" s << "hello" @s.write(s) @response = Kcar::Response.new(@c, []) status, headers, body = @response.rack assert_kind_of Array, headers assert_equal status, "200 OK" tmp = [] assert ! body.parser.keepalive? closer = Thread.new do Thread.pass until tmp[0] @s.close end assert_raises(EOFError) { body.each { |chunk| tmp << chunk.dup } } assert_nil @c.close assert_equal "hello", tmp[0] assert_nil closer.value end end