diff --git a/ChangeLog b/ChangeLog index d78b1f81f9..d3e94c3e67 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,17 @@ +Fri Jan 20 14:31:43 2012 NARUSE, Yui + + * lib/net/http.rb (Net::HTTP#transport_request): retry a idempotent + request automatically. [ruby-dev:45030] [Bug #5790] + [ruby-core:41821] [Bug #5813] + + * lib/net/http.rb (Net::HTTP#keep_alive_timeout=): added to specify + the second to reconnect the TCP connection on Keep-Alive. + The default value is 2 second because current servers uses 2 sec. + http://ftp-admin.blogspot.com/2009/09/keepalivetimeout2.html + + * lib/net/http.rb (Net::HTTP#begin_transport): reconnect TCP + connection on keep-alive timeout. + Thu Jan 19 07:53:09 2012 Tadayoshi Funaba * ext/date/date_strptime.c: moved detector of leftover. diff --git a/lib/net/http.rb b/lib/net/http.rb index 879cfe0045..53093492c2 100644 --- a/lib/net/http.rb +++ b/lib/net/http.rb @@ -578,7 +578,8 @@ module Net #:nodoc: @address = address @port = (port || HTTP.default_port) @curr_http_version = HTTPVersion - @no_keepalive_server = false + @keep_alive_timeout = 2 + @last_communicated = nil @close_on_empty_response = false @socket = nil @started = false @@ -648,6 +649,12 @@ module Net #:nodoc: @continue_timeout = sec end + # Seconds to reuse the connection of the previous request. + # If the idle time is less than this Keep-Alive Timeout, + # Net::HTTP reuses the TCP/IP socket used by the previous communication. + # The default value is 2 seconds. + attr_accessor :keep_alive_timeout + # Returns true if the HTTP session has been started. def started? @started @@ -1332,7 +1339,10 @@ module Net #:nodoc: res end + IDEMPOTENT_METHODS_ = %w/GET HEAD PUT DELETE OPTIONS TRACE/ # :nodoc: + def transport_request(req) + count = 0 begin_transport req res = catch(:response) { req.exec @socket, @curr_http_version, edit_path(req.path) @@ -1346,6 +1356,16 @@ module Net #:nodoc: } end_transport req, res res + rescue EOFError, Errno::ECONNRESET => exception + if count == 0 && IDEMPOTENT_METHODS_.include?(req.method) + count += 1 + @socket.close if @socket and not @socket.closed? + D "Conn close because of error #{exception}, and retry" + retry + end + D "Conn close because of error #{exception}" + @socket.close if @socket and not @socket.closed? + raise rescue => exception D "Conn close because of error #{exception}" @socket.close if @socket and not @socket.closed? @@ -1353,7 +1373,14 @@ module Net #:nodoc: end def begin_transport(req) - connect if @socket.closed? + if @socket.closed? + connect + elsif @last_communicated && @last_communicated + @keep_alive_timeout < Time.now + D 'Conn close because of keep_alive_timeout' + @socket.close + connect + end + if not req.response_body_permitted? and @close_on_empty_response req['connection'] ||= 'close' end @@ -1362,6 +1389,7 @@ module Net #:nodoc: def end_transport(req, res) @curr_http_version = res.http_version + @last_communicated = nil if @socket.closed? D 'Conn socket closed' elsif not res.body and @close_on_empty_response @@ -1369,6 +1397,7 @@ module Net #:nodoc: @socket.close elsif keep_alive?(req, res) D 'Conn keep-alive' + @last_communicated = Time.now else D 'Conn close' @socket.close diff --git a/test/net/http/test_http.rb b/test/net/http/test_http.rb index 15158546f1..b27aeb1cdc 100644 --- a/test/net/http/test_http.rb +++ b/test/net/http/test_http.rb @@ -564,3 +564,47 @@ class TestNetHTTPContinue < Test::Unit::TestCase assert_not_match(/HTTP\/1.1 100 continue/, @debug.string) end end + +class TestNetHTTPKeepAlive < Test::Unit::TestCase + CONFIG = { + 'host' => '127.0.0.1', + 'port' => 10081, + 'proxy_host' => nil, + 'proxy_port' => nil, + 'RequestTimeout' => 1, + } + + include TestNetHTTPUtils + + def test_keep_alive_get_auto_reconnect + start {|http| + http.set_debug_output($stderr) + res = http.get('/') + http.keep_alive_timeout = 1 + assert_kind_of Net::HTTPResponse, res + assert_kind_of String, res.body + sleep 1.5 + assert_nothing_raised { + res = http.get('/') + } + assert_kind_of Net::HTTPResponse, res + assert_kind_of String, res.body + } + end + + def test_keep_alive_get_auto_retry + start {|http| + http.set_debug_output($stderr) + res = http.get('/') + http.keep_alive_timeout = 5 + assert_kind_of Net::HTTPResponse, res + assert_kind_of String, res.body + sleep 1.5 + assert_nothing_raised { + res = http.get('/') + } + assert_kind_of Net::HTTPResponse, res + assert_kind_of String, res.body + } + end +end diff --git a/test/net/http/utils.rb b/test/net/http/utils.rb index 50f616f29f..07e0b9fa2d 100644 --- a/test/net/http/utils.rb +++ b/test/net/http/utils.rb @@ -51,6 +51,7 @@ module TestNetHTTPUtils :ServerType => Thread, } server_config[:OutputBufferSize] = 4 if config('chunked') + server_config[:RequestTimeout] = config('RequestTimeout') if config('RequestTimeout') if defined?(OpenSSL) and config('ssl_enable') server_config.update({ :SSLEnable => true,