diff options
Diffstat (limited to 'test/webrick/test_httpserver.rb')
| -rw-r--r-- | test/webrick/test_httpserver.rb | 322 |
1 files changed, 286 insertions, 36 deletions
diff --git a/test/webrick/test_httpserver.rb b/test/webrick/test_httpserver.rb index eb685f916d..024c0c510f 100644 --- a/test/webrick/test_httpserver.rb +++ b/test/webrick/test_httpserver.rb @@ -1,12 +1,25 @@ +# frozen_string_literal: false require "test/unit" require "net/http" require "webrick" -require File.join(File.dirname(__FILE__), "utils.rb") +require_relative "utils" class TestWEBrickHTTPServer < Test::Unit::TestCase + empty_log = Object.new + def empty_log.<<(str) + assert_equal('', str) + self + end + NoLog = WEBrick::Log.new(empty_log, WEBrick::BasicLog::WARN) + + def teardown + WEBrick::Utils::TimeoutHandler.terminate + super + end + def test_mount httpd = WEBrick::HTTPServer.new( - :Logger => WEBrick::Log.new(TestWEBrick::NullWriter), + :Logger => NoLog, :DoNotListen=>true ) httpd.mount("/", :Root) @@ -17,50 +30,50 @@ class TestWEBrickHTTPServer < Test::Unit::TestCase serv, opts, script_name, path_info = httpd.search_servlet("/") assert_equal(:Root, serv) assert_equal([], opts) - assert_equal(script_name, "") - assert_equal(path_info, "/") + assert_equal("", script_name) + assert_equal("/", path_info) serv, opts, script_name, path_info = httpd.search_servlet("/sub") assert_equal(:Root, serv) assert_equal([], opts) - assert_equal(script_name, "") - assert_equal(path_info, "/sub") + assert_equal("", script_name) + assert_equal("/sub", path_info) serv, opts, script_name, path_info = httpd.search_servlet("/sub/") assert_equal(:Root, serv) assert_equal([], opts) - assert_equal(script_name, "") - assert_equal(path_info, "/sub/") + assert_equal("", script_name) + assert_equal("/sub/", path_info) serv, opts, script_name, path_info = httpd.search_servlet("/foo") assert_equal(:Foo, serv) assert_equal([], opts) - assert_equal(script_name, "/foo") - assert_equal(path_info, "") + assert_equal("/foo", script_name) + assert_equal("", path_info) serv, opts, script_name, path_info = httpd.search_servlet("/foo/") assert_equal(:Foo, serv) assert_equal([], opts) - assert_equal(script_name, "/foo") - assert_equal(path_info, "/") + assert_equal("/foo", script_name) + assert_equal("/", path_info) serv, opts, script_name, path_info = httpd.search_servlet("/foo/sub") assert_equal(:Foo, serv) assert_equal([], opts) - assert_equal(script_name, "/foo") - assert_equal(path_info, "/sub") + assert_equal("/foo", script_name) + assert_equal("/sub", path_info) serv, opts, script_name, path_info = httpd.search_servlet("/foo/bar") assert_equal(:Bar, serv) assert_equal([:bar1], opts) - assert_equal(script_name, "/foo/bar") - assert_equal(path_info, "") + assert_equal("/foo/bar", script_name) + assert_equal("", path_info) serv, opts, script_name, path_info = httpd.search_servlet("/foo/bar/baz") assert_equal(:Baz, serv) assert_equal([:baz1, :baz2], opts) - assert_equal(script_name, "/foo/bar/baz") - assert_equal(path_info, "") + assert_equal("/foo/bar/baz", script_name) + assert_equal("", path_info) end class Req @@ -75,7 +88,7 @@ class TestWEBrickHTTPServer < Test::Unit::TestCase def httpd(addr, port, host, ali) config ={ - :Logger => WEBrick::Log.new(TestWEBrick::NullWriter), + :Logger => NoLog, :DoNotListen => true, :BindAddress => addr, :Port => port, @@ -115,10 +128,10 @@ class TestWEBrickHTTPServer < Test::Unit::TestCase host3 = httpd(addr1, port1, name2, alias1), host4 = httpd(addr1, port2, name1, nil), host5 = httpd(addr1, port2, name2, alias1), - host6 = httpd(addr1, port2, name3, alias2), + httpd(addr1, port2, name3, alias2), host7 = httpd(addr2, nil, name1, nil), host8 = httpd(addr2, nil, name2, alias1), - host9 = httpd(addr2, nil, name3, alias2), + httpd(addr2, nil, name3, alias2), host10 = httpd(local, nil, nil, nil), host11 = httpd(nil, port3, nil, nil), ].sort_by{ rand } @@ -223,38 +236,275 @@ class TestWEBrickHTTPServer < Test::Unit::TestCase :StopCallback => Proc.new{ stopped += 1 }, :RequestCallback => Proc.new{|req, res| requested0 += 1 }, } - TestWEBrick.start_httpserver(config){|server, addr, port| + log_tester = lambda {|log, access_log| + assert(log.find {|s| %r{ERROR `/' not found\.} =~ s }) + assert_equal([], log.reject {|s| %r{ERROR `/' not found\.} =~ s }) + } + TestWEBrick.start_httpserver(config, log_tester){|server, addr, port, log| vhost_config = { :ServerName => "myhostname", :BindAddress => addr, :Port => port, :DoNotListen => true, - :Logger => WEBrick::Log.new(TestWEBrick::NullWriter), + :Logger => NoLog, :AccessLog => [], :RequestCallback => Proc.new{|req, res| requested1 += 1 }, } server.virtual_host(WEBrick::HTTPServer.new(vhost_config)) - true while server.status != :Running - assert_equal(started, 1) - assert_equal(stopped, 0) - assert_equal(accepted, 0) + Thread.pass while server.status != :Running + assert_equal(1, started, log.call) + assert_equal(0, stopped, log.call) + assert_equal(0, accepted, log.call) http = Net::HTTP.new(addr, port) req = Net::HTTP::Get.new("/") req["Host"] = "myhostname:#{port}" - http.request(req){|res| assert_equal("404", res.code)} - http.request(req){|res| assert_equal("404", res.code)} - http.request(req){|res| assert_equal("404", res.code)} + http.request(req){|res| assert_equal("404", res.code, log.call)} + http.request(req){|res| assert_equal("404", res.code, log.call)} + http.request(req){|res| assert_equal("404", res.code, log.call)} req["Host"] = "localhost:#{port}" - http.request(req){|res| assert_equal("404", res.code)} - http.request(req){|res| assert_equal("404", res.code)} - http.request(req){|res| assert_equal("404", res.code)} - assert_equal(6, accepted) - assert_equal(3, requested0) - assert_equal(3, requested1) + http.request(req){|res| assert_equal("404", res.code, log.call)} + http.request(req){|res| assert_equal("404", res.code, log.call)} + http.request(req){|res| assert_equal("404", res.code, log.call)} + assert_equal(6, accepted, log.call) + assert_equal(3, requested0, log.call) + assert_equal(3, requested1, log.call) } assert_equal(started, 1) assert_equal(stopped, 1) end + + # This class is needed by test_response_io_with_chunked_set method + class EventManagerForChunkedResponseTest + def initialize + @listeners = [] + end + def add_listener( &block ) + @listeners << block + end + def raise_str_event( str ) + @listeners.each{ |e| e.call( :str, str ) } + end + def raise_close_event() + @listeners.each{ |e| e.call( :cls ) } + end + end + def test_response_io_with_chunked_set + evt_man = EventManagerForChunkedResponseTest.new + t = Thread.new do + begin + config = { + :ServerName => "localhost" + } + TestWEBrick.start_httpserver(config) do |server, addr, port, log| + body_strs = [ 'aaaaaa', 'bb', 'cccc' ] + server.mount_proc( "/", ->( req, res ){ + # Test for setting chunked... + res.chunked = true + r,w = IO.pipe + evt_man.add_listener do |type,str| + type == :cls ? ( w.close ) : ( w << str ) + end + res.body = r + } ) + Thread.pass while server.status != :Running + http = Net::HTTP.new(addr, port) + req = Net::HTTP::Get.new("/") + http.request(req) do |res| + i = 0 + evt_man.raise_str_event( body_strs[i] ) + res.read_body do |s| + assert_equal( body_strs[i], s ) + i += 1 + if i < body_strs.length + evt_man.raise_str_event( body_strs[i] ) + else + evt_man.raise_close_event() + end + end + assert_equal( body_strs.length, i ) + end + end + rescue => err + flunk( 'exception raised in thread: ' + err.to_s ) + end + end + if t.join( 3 ).nil? + evt_man.raise_close_event() + flunk( 'timeout' ) + if t.join( 1 ).nil? + Thread.kill t + end + end + end + + def test_response_io_without_chunked_set + config = { + :ServerName => "localhost" + } + log_tester = lambda {|log, access_log| + assert_equal(1, log.length) + assert_match(/WARN Could not determine content-length of response body./, log[0]) + } + TestWEBrick.start_httpserver(config, log_tester){|server, addr, port, log| + server.mount_proc("/", lambda { |req, res| + r,w = IO.pipe + # Test for not setting chunked... + # res.chunked = true + res.body = r + w << "foo" + w.close + }) + Thread.pass while server.status != :Running + http = Net::HTTP.new(addr, port) + req = Net::HTTP::Get.new("/") + req['Connection'] = 'Keep-Alive' + begin + Timeout.timeout(2) do + http.request(req){|res| assert_equal("foo", res.body) } + end + rescue Timeout::Error + flunk('corrupted response') + end + } + end + + def test_request_handler_callback_is_deprecated + requested = 0 + config = { + :ServerName => "localhost", + :RequestHandler => Proc.new{|req, res| requested += 1 }, + } + log_tester = lambda {|log, access_log| + assert_equal(2, log.length) + assert_match(/WARN :RequestHandler is deprecated, please use :RequestCallback/, log[0]) + assert_match(%r{ERROR `/' not found\.}, log[1]) + } + TestWEBrick.start_httpserver(config, log_tester){|server, addr, port, log| + Thread.pass while server.status != :Running + + http = Net::HTTP.new(addr, port) + req = Net::HTTP::Get.new("/") + req["Host"] = "localhost:#{port}" + http.request(req){|res| assert_equal("404", res.code, log.call)} + assert_match(%r{:RequestHandler is deprecated, please use :RequestCallback$}, log.call, log.call) + } + assert_equal(1, requested) + end + + def test_shutdown_with_busy_keepalive_connection + requested = 0 + config = { + :ServerName => "localhost", + } + TestWEBrick.start_httpserver(config){|server, addr, port, log| + server.mount_proc("/", lambda {|req, res| res.body = "heffalump" }) + Thread.pass while server.status != :Running + + Net::HTTP.start(addr, port) do |http| + req = Net::HTTP::Get.new("/") + http.request(req){|res| assert_equal('Keep-Alive', res['Connection'], log.call) } + server.shutdown + begin + 10.times {|n| http.request(req); requested += 1 } + rescue + # Errno::ECONNREFUSED or similar + end + end + } + assert_equal(0, requested, "Server responded to #{requested} requests after shutdown") + end + + def test_cntrl_in_path + log_ary = [] + access_log_ary = [] + config = { + :Port => 0, + :BindAddress => '127.0.0.1', + :Logger => WEBrick::Log.new(log_ary, WEBrick::BasicLog::WARN), + :AccessLog => [[access_log_ary, '']], + } + s = WEBrick::HTTPServer.new(config) + s.mount('/foo', WEBrick::HTTPServlet::FileHandler, __FILE__) + th = Thread.new { s.start } + addr = s.listeners[0].addr + + http = Net::HTTP.new(addr[3], addr[1]) + req = Net::HTTP::Get.new('/notexist%0a/foo') + http.request(req) { |res| assert_equal('404', res.code) } + exp = %Q(ERROR `/notexist\\n/foo' not found.\n) + assert_equal 1, log_ary.size + assert log_ary[0].include?(exp) + ensure + s&.shutdown + th&.join + end + + def test_gigantic_request_header + log_tester = lambda {|log, access_log| + assert_equal 1, log.size + assert log[0].include?('ERROR headers too large') + } + TestWEBrick.start_httpserver({}, log_tester){|server, addr, port, log| + server.mount('/', WEBrick::HTTPServlet::FileHandler, __FILE__) + TCPSocket.open(addr, port) do |c| + c.write("GET / HTTP/1.0\r\n") + junk = -"X-Junk: #{' ' * 1024}\r\n" + assert_raise(Errno::ECONNRESET, Errno::EPIPE) do + loop { c.write(junk) } + end + end + } + end + + def test_eof_in_chunk + log_tester = lambda do |log, access_log| + assert_equal 1, log.size + assert log[0].include?('ERROR bad chunk data size') + end + TestWEBrick.start_httpserver({}, log_tester){|server, addr, port, log| + server.mount_proc('/', ->(req, res) { res.body = req.body }) + TCPSocket.open(addr, port) do |c| + c.write("POST / HTTP/1.1\r\nHost: example.com\r\n" \ + "Transfer-Encoding: chunked\r\n\r\n5\r\na") + c.shutdown(Socket::SHUT_WR) # trigger EOF in server + res = c.read + assert_match %r{\AHTTP/1\.1 400 }, res + end + } + end + + def test_big_chunks + nr_out = 3 + buf = 'big' # 3 bytes is bigger than 2! + config = { :InputBufferSize => 2 }.freeze + total = 0 + all = '' + TestWEBrick.start_httpserver(config){|server, addr, port, log| + server.mount_proc('/', ->(req, res) { + err = [] + ret = req.body do |chunk| + n = chunk.bytesize + n > config[:InputBufferSize] and err << "#{n} > :InputBufferSize" + total += n + all << chunk + end + ret.nil? or err << 'req.body should return nil' + (buf * nr_out) == all or err << 'input body does not match expected' + res.header['connection'] = 'close' + res.body = err.join("\n") + }) + TCPSocket.open(addr, port) do |c| + c.write("POST / HTTP/1.1\r\nHost: example.com\r\n" \ + "Transfer-Encoding: chunked\r\n\r\n") + chunk = "#{buf.bytesize.to_s(16)}\r\n#{buf}\r\n" + nr_out.times { c.write(chunk) } + c.write("0\r\n\r\n") + head, body = c.read.split("\r\n\r\n") + assert_match %r{\AHTTP/1\.1 200 OK}, head + assert_nil body + end + } + end end |
