【问题标题】:How to tell a connect timeout error from a read timeout error in Ruby's Net::HTTP如何从 Ruby 的 Net::HTTP 中的读取超时错误中区分连接超时错误
【发布时间】:2010-04-12 21:22:08
【问题描述】:

我的问题与How to rescue timeout issues (Ruby, Rails)有关。

这是从超时中拯救的常用方法:

def action
  # Post using Net::HTTP
rescue Timeout::Error => e
  # Do something
end

我想确定异常是在尝试连接到主机时引发的,还是在尝试从主机读取时引发的。这可能吗?

【问题讨论】:

    标签: ruby


    【解决方案1】:

    这是解决方案(在 Ben 修复之后):

    require "net/http"
    http = Net::HTTP.new("example.com")
    http.open_timeout = 2
    http.read_timeout = 3
    begin
      http.start
      begin
        http.request_get("/whatever?") do |res|
          res.read_body
        end
      rescue Timeout::Error
        puts "Timeout due to reading"
      end
    rescue Timeout::Error
      puts "Timeout due to connecting"
    end
    

    【讨论】:

    • 马克,我真的希望这是真的,但这对我不起作用。我认为这是因为http.request_get 创建并使用了一个不继承超时变量的Net::HTTP 新实例。
    • 我收回它,它不会创建Net:HTTP 的新实例。尽管如此,无论我将打开和读取超时设置为什么,它似乎都会超时大约 30 秒。
    • open_timeout 对我有用,在 ruby​​ 1.8.7 和 1.9.2 dev 上。测试 read_timeout 比较困难,可能是每个块读取,你应该检查代码。不过,如果您将两者都设置为 0.1,它应该会很快超时,不是吗?无论如何,我的回答仍然是区分是什么原因导致超时的有效方法!
    • 想通了。我正在使用没有 DNS 条目的“blah.com”进行测试。由于某种原因,由于解析失败,上面的代码没有捕获超时。您的代码只需稍作修改即可完美运行。您必须将request_get 放在内部救援块中,并在外部救援块中添加对http.start 的调用,如下所示:gist.github.com/364868 更新您的答案,我会接受它。非常感谢马克!
    • 啊,很好,很抱歉没有完全正确。我用你的修改更新了答案。
    【解决方案2】:

    如果您无法升级到 ruby​​ 2.x,Marc-André Lafortune 的解决方案仍然是最好的。

    从 2.x 开始,Timeout::Error 的子类将根据触发的超时产生:

    • Net::OpenTimeout
    • Net::ReadTimeout

    但是,read_timeout 行为在 2.x 上很奇怪,因为它似乎是您设置的值的两倍。 This article 解释了原因。

    这是两个超时的测试(在 1.8.7、1.9.3、2.1.2、2.2.4 上测试)。

    编辑:open_timeout 测试适用于 Mac,但在 Linux 上,客户端收到“连接被拒绝”错误。

    require "net/http"
    require "socket"
    
    SERVER_HOST = '127.0.0.1'
    SERVER_PORT = 9999
    
    def main
      puts 'with_nonlistening_server'
      with_nonlistening_server do
        make_request
      end
      
      puts
      puts 'with_listening_server'
      with_listening_server do
        make_request
      end
    end
    
    def with_listening_server
      # This automatically starts listening
      serv = TCPServer.new(SERVER_HOST, SERVER_PORT)
      begin
        yield
      ensure
        serv.close
      end
    end
    
    def with_nonlistening_server
      raw_serv = Socket.new Socket::AF_INET, Socket::SOCK_STREAM, 0
      addr     = Socket.pack_sockaddr_in SERVER_PORT, SERVER_HOST
    
      # Bind, but don't listen
      raw_serv.bind addr
      begin
        yield
      ensure
        raw_serv.close
      end
    end
    
    def make_request
      http = Net::HTTP.new(SERVER_HOST, SERVER_PORT)
      http.open_timeout = 1
      http.read_timeout = 1  # seems to be doubled on ruby 2.x
      start_tm = Time.now
      begin
        http.start
        begin
          http.get('/')
        rescue Timeout::Error => err
          puts "Read timeout: #{err.inspect}"
        end
      rescue Timeout::Error => err
        puts "Open timeout: #{err.inspect}"
      end
      end_tm = Time.now
      puts "Duration (sec): #{end_tm - start_tm}"
    end
    
    if __FILE__ == $PROGRAM_NAME
      main
    end
    

    1.9.3 上的示例输出:

    with_nonlistening_server
    Open timeout: #<Timeout::Error: execution expired>
    Duration (sec): 1.002477
    
    with_listening_server
    Read timeout: #<Timeout::Error: Timeout::Error>
    Duration (sec): 1.00599
    

    2.1.2 上的示例输出:

    with_nonlistening_server
    Open timeout: #<Net::OpenTimeout: execution expired>
    Duration (sec): 1.005923
    
    with_listening_server
    Read timeout: #<Net::ReadTimeout: Net::ReadTimeout>
    Duration (sec): 2.009582
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-08-07
      • 2018-07-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-09-28
      • 1970-01-01
      相关资源
      最近更新 更多