diff --git a/ChangeLog b/ChangeLog index 891ee6bf02..34c7f15f02 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,25 @@ +Thu May 18 22:37:20 2006 GOTOU Yuuzou + + * lib/webrick/config.rb (WEBrick::Config::HTTP): add new parameters, + :InputBufferSize and :OutputBufferSize. + + * lib/webrick/utils.rb (WEBrick::Utils.timeout): add new timeout + method. this implementation is expected to be compatible with + timeout.rb and faster than timeout.rb. + + * lib/webrick/httprequest.rb (WEBrick::HTTPRequest#_read_data): + Timeout.timeout is replaced by WEBrick::Utils.timeout. + + * lib/webrick/httprequest.rb: WEBrick::HTTPRequest::BUFSIZE is + replaced by config[:InputBufferSize]. + + * lib/webrick/httpresposne.rb: WEBrick::HTTPResponse::BUFSIZE is + replaced by config[:OutputBufferSize]. + + * lib/webrick/server.rb: get rid of unnecessary require. + + * test/webrick/test_utils.rb: test for WEBrick::Utils.timeout. + Thu May 18 00:42:12 2006 nobuyoshi nakada * ext/extmk.rb, lib/mkmf.rb: use BUILD_FILE_SEPARATOR in Makefiles. diff --git a/lib/webrick/config.rb b/lib/webrick/config.rb index 19d0c7ab14..10db6e0944 100644 --- a/lib/webrick/config.rb +++ b/lib/webrick/config.rb @@ -48,6 +48,8 @@ module WEBrick :DocumentRootOptions => { :FancyIndexing => true }, :RequestCallback => nil, :ServerAlias => nil, + :InputBufferSize => 65536, # input buffer size in reading request body + :OutputBufferSize => 65536, # output buffer size in sending File or IO # for HTTPProxyServer :ProxyAuthProc => nil, diff --git a/lib/webrick/httprequest.rb b/lib/webrick/httprequest.rb index ad9a983ef2..33dafa93b1 100644 --- a/lib/webrick/httprequest.rb +++ b/lib/webrick/httprequest.rb @@ -8,19 +8,15 @@ # # $IPR: httprequest.rb,v 1.64 2003/07/13 17:18:22 gotoyuzo Exp $ -require 'timeout' require 'uri' - require 'webrick/httpversion' require 'webrick/httpstatus' require 'webrick/httputils' require 'webrick/cookie' module WEBrick - class HTTPRequest BODY_CONTAINABLE_METHODS = [ "POST", "PUT" ] - BUFSIZE = 1024*4 # Request line attr_reader :request_line @@ -44,6 +40,7 @@ module WEBrick def initialize(config) @config = config + @buffer_size = @config[:InputBufferSize] @logger = config[:Logger] @request_line = @request_method = @@ -278,7 +275,7 @@ module WEBrick elsif self['content-length'] || @remaining_size @remaining_size ||= self['content-length'].to_i while @remaining_size > 0 - sz = BUFSIZE < @remaining_size ? BUFSIZE : @remaining_size + sz = [@buffer_size, @remaining_size].min break unless buf = read_data(socket, sz) @remaining_size -= buf.size block.call(buf) @@ -321,7 +318,7 @@ module WEBrick def _read_data(io, method, arg) begin - timeout(@config[:RequestTimeout]){ + WEBrick::Utils.timeout(@config[:RequestTimeout]){ return io.__send__(method, arg) } rescue Errno::ECONNRESET diff --git a/lib/webrick/httpresponse.rb b/lib/webrick/httpresponse.rb index d0f232d1e1..b3f1abef88 100644 --- a/lib/webrick/httpresponse.rb +++ b/lib/webrick/httpresponse.rb @@ -16,8 +16,6 @@ require 'webrick/httpstatus' module WEBrick class HTTPResponse - BUFSIZE = 1024*4 - attr_reader :http_version, :status, :header attr_reader :cookies attr_accessor :reason_phrase @@ -30,6 +28,7 @@ module WEBrick def initialize(config) @config = config + @buffer_size = config[:OutputBufferSize] @logger = config[:Logger] @header = Hash.new @status = HTTPStatus::RC_OK @@ -258,7 +257,7 @@ module WEBrick if @request_method == "HEAD" # do nothing elsif chunked? - while buf = @body.read(BUFSIZE) + while buf = @body.read(@buffer_size) next if buf.empty? data = "" data << format("%x", buf.size) << CRLF @@ -282,7 +281,7 @@ module WEBrick # do nothing elsif chunked? remain = body ? @body.size : 0 - while buf = @body[@sent_size, BUFSIZE] + while buf = @body[@sent_size, @buffer_size] break if buf.empty? data = "" data << format("%x", buf.size) << CRLF @@ -301,18 +300,18 @@ module WEBrick def _send_file(output, input, offset, size) while offset > 0 - sz = BUFSIZE < offset ? BUFSIZE : offset + sz = @buffer_size < size ? @buffer_size : size buf = input.read(sz) offset -= buf.size end if size == 0 - while buf = input.read(BUFSIZE) + while buf = input.read(@buffer_size) _write_data(output, buf) end else while size > 0 - sz = BUFSIZE < size ? BUFSIZE : size + sz = @buffer_size < size ? @buffer_size : size buf = input.read(sz) _write_data(output, buf) size -= buf.size diff --git a/lib/webrick/server.rb b/lib/webrick/server.rb index 5e57966255..3adbded5fa 100644 --- a/lib/webrick/server.rb +++ b/lib/webrick/server.rb @@ -10,7 +10,6 @@ require 'thread' require 'socket' -require 'timeout' require 'webrick/config' require 'webrick/log' diff --git a/lib/webrick/utils.rb b/lib/webrick/utils.rb index cf9da6f2ce..4a447aaf85 100644 --- a/lib/webrick/utils.rb +++ b/lib/webrick/utils.rb @@ -96,5 +96,80 @@ module WEBrick end module_function :random_string + ########### + + require "thread" + require "timeout" + require "singleton" + + class TimeoutHandler + include Singleton + TimeoutMutex = Mutex.new + + def TimeoutHandler.register(seconds, exception) + TimeoutMutex.synchronize{ + instance.register(Thread.current, Time.now + seconds, exception) + } + end + + def TimeoutHandler.cancel(id) + TimeoutMutex.synchronize{ + instance.cancel(Thread.current, id) + } + end + + def initialize + @timeout_info = Hash.new + Thread.start{ + while true + now = Time.now + @timeout_info.each{|thread, ary| + ary.each{|info| + time, exception = *info + interrupt(thread, info.object_id, exception) if time < now + } + } + sleep 0.5 + end + } + end + + def interrupt(thread, id, exception) + TimeoutMutex.synchronize{ + if cancel(thread, id) && thread.alive? + thread.raise(exception, "execution timeout") + end + } + end + + def register(thread, time, exception) + @timeout_info[thread] ||= Array.new + @timeout_info[thread] << [time, exception] + return @timeout_info[thread].last.object_id + end + + def cancel(thread, id) + if ary = @timeout_info[thread] + ary.delete_if{|info| info.object_id == id } + if ary.empty? + @timeout_info.delete(thread) + end + return true + end + return false + end + end + + def timeout(seconds, exception=Timeout::Error) + return yield if seconds.nil? or seconds.zero? + raise ThreadError, "timeout within critical session" if Thread.critical + id = TimeoutHandler.register(seconds, exception) + begin + yield(seconds) + ensure + TimeoutHandler.cancel(id) + end + end + module_function :timeout end end diff --git a/test/webrick/test_utils.rb b/test/webrick/test_utils.rb new file mode 100644 index 0000000000..ace4632547 --- /dev/null +++ b/test/webrick/test_utils.rb @@ -0,0 +1,64 @@ +require "test/unit" +require "webrick/utils" + +class TestWEBrickUtils < Test::Unit::TestCase + def assert_expired(flag, m) + if m == WEBrick::Utils + handler = WEBrick::Utils::TimeoutHandler.instance + assert_equal(flag, handler.instance_eval{ @timeout_info.empty? }) + end + end + + def do_test_timeout(m) + ex = Class.new(StandardError) + + assert_equal(:foo, m.timeout(10){ :foo }) + assert_expired(true, m) + + i = 0 + assert_raises(Timeout::Error){ + m.timeout(2){ + assert_raises(Timeout::Error){ m.timeout(1){ i += 1; sleep } } + assert_expired(false, m) + i += 1 + sleep + } + } + assert_equal(2, i) + assert_expired(true, m) + + assert_raises(Timeout::Error){ m.timeout(0.1){ sleep } } + assert_expired(true, m) + + assert_raises(ex){ m.timeout(0.1, ex){ sleep } } + assert_expired(true, m) + + i = 0 + assert_raises(ex){ + m.timeout(10){ + m.timeout(1, ex){ i += 1; sleep } + } + sleep + } + assert_equal(1, i) + assert_expired(true, m) + + i = 0 + assert_raises(Timeout::Error){ + m.timeout(1){ + m.timeout(10, ex){ i += 1; sleep } + } + sleep + } + assert_equal(1, i) + assert_expired(true, m) + end + + def test_webrick_timeout + do_test_timeout(WEBrick::Utils) + end + + #def test_timeout + # do_test_timeout(Timeout) + #end +end