summaryrefslogtreecommitdiff
path: root/test/webrick/test_httpserver.rb
diff options
context:
space:
mode:
Diffstat (limited to 'test/webrick/test_httpserver.rb')
-rw-r--r--test/webrick/test_httpserver.rb335
1 files changed, 309 insertions, 26 deletions
diff --git a/test/webrick/test_httpserver.rb b/test/webrick/test_httpserver.rb
index e0c2ae33ad..2e5d44940c 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_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,22 +236,27 @@ 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|
+ 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, log.call)
- assert_equal(stopped, 0, log.call)
- assert_equal(accepted, 0, log.call)
+ Thread.pass while server.status != :Running
+ sleep 1 if defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled? # server.status behaves unexpectedly with --jit-wait
+ 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("/")
@@ -257,4 +275,269 @@ class TestWEBrickHTTPServer < Test::Unit::TestCase
assert_equal(started, 1)
assert_equal(stopped, 1)
end
+
+ class CustomRequest < ::WEBrick::HTTPRequest; end
+ class CustomResponse < ::WEBrick::HTTPResponse; end
+ class CustomServer < ::WEBrick::HTTPServer
+ def create_request(config)
+ CustomRequest.new(config)
+ end
+
+ def create_response(config)
+ CustomResponse.new(config)
+ end
+ end
+
+ def test_custom_server_request_and_response
+ config = { :ServerName => "localhost" }
+ TestWEBrick.start_server(CustomServer, config){|server, addr, port, log|
+ server.mount_proc("/", lambda {|req, res|
+ assert_kind_of(CustomRequest, req)
+ assert_kind_of(CustomResponse, res)
+ res.body = "via custom response"
+ })
+ Thread.pass while server.status != :Running
+
+ Net::HTTP.start(addr, port) do |http|
+ req = Net::HTTP::Get.new("/")
+ http.request(req){|res|
+ assert_equal("via custom response", res.body)
+ }
+ server.shutdown
+ end
+ }
+ 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_include log_ary[0], exp
+ ensure
+ s&.shutdown
+ th&.join
+ end
+
+ def test_gigantic_request_header
+ log_tester = lambda {|log, access_log|
+ assert_equal 1, log.size
+ assert_include log[0], '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_include log[0], '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