[ruby-trunk - Bug #5790][Open] net/http の EOFError と Keep-Alive

Issue #5790 has been reported by Yui NARUSE.


Bug #5790: net/http の EOFError と Keep-Alive
https://bugs.ruby-lang.org/issues/5790

Author: Yui NARUSE
Status: Open
Priority: Normal
Assignee:
Category:
Target version: 2.0.0
ruby -v: ruby 2.0.0dev (2011-12-21 trunk 34086) [x86_64-freebsd9.0]

[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?
    
  •  raise
    
    rescue => exception
    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,

$B$A$g$C$H(Bredmine$B$N$F$9$H$F$9$H(B

(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 H.のコメントをもらったほ
うがよいと思います。その理由は、つい最近Mechanize 2.1(というより、本質
的にはその下層にあるnet-http-persistence)に同じ問題への対策が入ったため
です。net/http側で面倒を見るべき、と彼に言ったのですが、彼は違う意見だっ
たようなので、念のため。

This forum is not affiliated to the Ruby language, Ruby on Rails framework, nor any Ruby applications discussed here.

| Privacy Policy | Terms of Service | Remote Ruby Jobs