开发者

How to tell a connect timeout error from a read timeout error in Ruby's Net::HTTP

开发者 https://www.devze.com 2022-12-26 19:43 出处:网络
My question is related to How to rescue timeout issues (Ruby, Rails). Here\'s the common way to rescue from a timeout:

My question is related to How to rescue timeout issues (Ruby, Rails).

Here's the common way to rescue from a timeout:

def action
  # Post using Net::HTTP
rescue Ti开发者_开发问答meout::Error => e
  # Do something
end

I'd like to determine if the exception was raised while trying to connect to the host, or if it was raised while trying to read from the host. Is this possible?


Here's the solution (after Ben's fix):

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


Marc-André Lafortune's solution is still the best if you can't upgrade to ruby 2.x.

Starting from 2.x, a subclass of Timeout::Error will be raised depending on which timeout was triggered:

  • Net::OpenTimeout
  • Net::ReadTimeout

However, the read_timeout behavior is strange on 2.x, because it seems to double the value you set. This article explains why.

Here's a test for both timeouts (tested on 1.8.7, 1.9.3, 2.1.2, 2.2.4).

EDIT: The open_timeout test works on Mac, but on Linux, the client gets a "connection refused" error.

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

Example output on 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

Example output on 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
0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号