Bug #5790
closednet/http の EOFError と Keep-Alive
Description
[ruby-dev:39421] がずっと心に残っていたので、思い立って調べてみたので、
(正確には自分が高頻度で踏むようになったので調べてみた)
その調査結果と対策案を提案します。
まず、投げられる原因ですが、根本的な原因は Keep-Alive のタイムアウトです。
HTTP/1.1 ではデフォルトで持続的接続を行うので、複数回のリクエストに渡って
一つの socket が使い回されます。
しかし、リクエスト同士で時間が開いていると、サーバー側でタイムアウトする
可能性があります。この時にクライアント側の read(2) が 0 を返す、
つまり EOFError となることがあります。
HTTP/1.1 は、冪等なメソッドの場合には確認なしにリトライすべきと言っているので、
そのようにするパッチを添付します。
冪等でないメソッドの場合にどうするべきかは悩ましいところです。
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
なお、この Keep-Alive における Timeout は、
Apache の場合、FreeBSD ports や pkgsrc では 5 秒、
Debian Packages や RPM では 15 秒でした。
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,
Updated by Anonymous almost 13 years ago
(2011/12/22 18:49), Yui NARUSE wrote:
HTTP/1.1 は、冪等なメソッドの場合には確認なしにリトライすべきと言っているので、
そのようにするパッチを添付します。
冪等でないメソッドの場合にどうするべきかは悩ましいところです。
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
細かいところは見ていないのですが、基本的にその方向でよいと思います。が、
この件については、ruby-coreにも投げて、Eric Hodelのコメントをもらったほ
うがよいと思います。その理由は、つい最近Mechanize 2.1(というより、本質
的にはその下層にあるnet-http-persistence)に同じ問題への対策が入ったため
です。net/http側で面倒を見るべき、と彼に言ったのですが、彼は違う意見だっ
たようなので、念のため。
Updated by naruse (Yui NARUSE) almost 13 years ago
- Status changed from Open to Closed
- % Done changed from 0 to 100
This issue was solved with changeset r34341.
Yui, thank you for reporting this issue.
Your contribution to Ruby is greatly appreciated.
May Ruby be with you.
-
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.