Rescuing Net::HTTP exceptions

written about 5 months ago |
3 comments

Working with Net::HTTP can be a pain. It’s got about 40 different ways to do any one task, and about 50 exceptions it can throw.

Just for the love of google, here’s what I’ve got for the “right way” of catching any exception that Net::HTTP can throw at you:


begin
  response = Net::HTTP.post_form(...) # or any Net::HTTP call
rescue Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, EOFError,
       Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError => e
  ...
end

Why not just rescue Exception => e? That’s a bad habit to get into, as it hides any problems in your actual code (like SyntaxErrors, whiny nils, etc). Of course, this would all be much easier if the possible errors had a common ancestor.

The issues I’ve been seeing in dealing with Net::HTTP have made me wonder if it wouldn’t be worth it to write a new HTTP client library. One that was easier to mock out in tests, and didn’t have all these ugly little facets.

Comment below if you know of more exceptions it should catch, or of an easier way to get this done.

Comments

Tim Carey-Smith
said about 5 months ago
Posted by author

One possibly dubious method could be to create a module and include it into all the exceptions you wish to catch.

http://pastie.org/145154 has the explanation :)

said about 5 months ago
Posted by author

For posterity:


require 'net/http'

# The ancestors beforehand
Net::HTTPBadResponse.ancestors
#=> [Net::HTTPBadResponse, StandardError, Exception, 
#    Object, Wirble::Shortcuts, PP::ObjectMixin, Kernel]

# Define the module to use for catching the exceptions
module Net::HTTPBroken; end
#=> nil

# Include the module into the exceptions
[Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, 
 EOFError, Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, 
 Net::ProtocolError].each {|m| m.send(:include, Net::HTTPBroken)}
#=> [Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, 
#    EOFError, Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, 
#    Net::ProtocolError]

# Now the exceptions have the module in their ancestry
Timeout::Error.ancestors
#=> [Timeout::Error, Net::HTTPBroken, Interrupt, SignalException, 
#    Exception, Object, Wirble::Shortcuts, PP::ObjectMixin, Kernel]
Net::HTTPBadResponse.ancestors
#=> [Net::HTTPBadResponse, Net::HTTPBroken, StandardError, 
#    Exception, Object, Wirble::Shortcuts, PP::ObjectMixin, Kernel]

# Lets check whether it works
begin 
  raise Net::HTTPBadResponse, "Got a bad response!" 
rescue Net::HTTPBroken => e
  puts e.message 
end
# Got a bad response!

begin 
  raise Errno::EINVAL, "Invalid!" 
rescue Net::HTTPBroken => e
  puts e.message
end
# Invalid argument - Invalid!

# Win!
said about 3 months ago
Posted by author

Worse still, the timeout code Net::HTTP relies on is broken.

I like your approach to cleaning up the exception handling. And I can only agree that a complete rewrite of Net::HTTP would be wonderful.

Leave a comment...

formatting help...