Bug #5813
closednet/http's EOFError and Keep-Alive
Description
[ruby-dev:39421] describes exceptions thrown by open-uri, and raise a question why net/http raises EOFError.
net/http sometimes raises EOFError.
I recently find it is because of Keep-Alive.
On HTTP/1.1, connections are Keep-Alive and a Keep-Alive connection has a timeout.
If a client of such connection doesn't send anything after some communication,
server closes the connection because of Keep-Alive timeout,
and the client's connection shall raise EOFError (sometimes it may be ECONNRESET).
HTTP/1.1 says a client should retry a request if the request is idempotent.
http://tools.ietf.org/html/rfc2616#section-8.1.4
http://tools.ietf.org/html/draft-ietf-httpbis-p1-messaging-17#section-6.1.5
http://www.studyinghttp.net/connections
So I attached a patch to such retry to net/http.
FYI, this timeout of Keep-Alive, KeepAliveTimeout, is:
Apache in FreeBSD ports or pkgsrc is 5 seconds,
the on in Debian Packages or RPM is 15 seconds.
diff --git a/lib/net/http.rb b/lib/net/http.rb
index 879cfe0..13bd1a7 100644
--- a/lib/net/http.rb
+++ b/lib/net/http.rb
@@ -1332,7 +1332,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 +1349,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? - 
rescue => exceptionraise
D "Conn close because of error #{exception}"
@socket.close if @socket and not @socket.closed?
diff --git a/test/net/http/test_http.rb b/test/net/http/test_http.rb
index 1515854..2e7ab4e 100644
--- a/test/net/http/test_http.rb
+++ b/test/net/http/test_http.rb
@@ -564,3 +564,29 @@ 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' => 0.1,
 - }
 - include TestNetHTTPUtils
 - def test_keep_alive_get
 - start {|http|
 - 
res = http.get('/') - 
assert_kind_of Net::HTTPResponse, res - 
assert_kind_of String, res.body - 
sleep 1 - 
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 50f616f..07e0b9f 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,