What’s the best way to rescue exceptions from Net::HTTP?
Exceptions thrown are described in Ruby’s socket.c, like Errno::ETIMEDOUT, Errno::ECONNRESET, and Errno::ECONNREFUSED. The base class to all of these is SystemCallError, but it feels weird to write code like the following because SystemCallError seems so far removed from making an HTTP call:
begin
response = Net::HTTP.get_response(uri)
response.code == "200"
rescue SystemCallError
false
end
Is it just me? Is there a better way to handle this beyond fixing Net::HTTP to handle the Errno exceptions that would likely pop up and encapsulate them in a parent HttpRequestException?
I agree it is an absolute pain to handle all the potential exceptions. Look at this to see an example:
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.
What I've done, and seen most people do, is move away from Net::HTTP and move to 3rd party HTTP libraries such as:
httparty and faraday
I experienced the same problem, and after a lot of research, I realized the best way to to handle all exceptions Net::HTTP methods would throw is to rescue from StandardError.
As pointed by Mike Lewis's answer, Tammer Saleh blog post proposes rescuing from a lot exceptions, but it is still flaw. There are some exceptions he does not rescue from, like Errno::EHOSTUNREACH, Errno::ECONNREFUSED, and possible some socket exceptions.
So, as I found out in tenderlove's translation of an old ruby-dev thread, the best solution is rescuing from StandardError, unfortunately:
begin
response = Net::HTTP.get_response(uri)
rescue StandardError
false
end
It is awful, but if you want your system does not break because of these other exceptions, use this approach.
Another approach is to aggregate all these exceptions in a constant, and then re-use this constant, e.g.:
ALL_NET_HTTP_ERRORS = [
Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, EOFError,
Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError
]
begin
your_http_logic()
rescue *ALL_NET_HTTP_ERRORS
…
end
It is far more maintainable and cleaner.
However, word of warning. I've copied the possible exceptions list from the aforementioned Tammer Saleh's blog post, and I know that his list is incomplete. For instance, Net::HTTP.get(URI("wow")) raises Errno::ECONNREFUSED which is not listed. Also, I wouldn't be surprised if the list should be modified for different Ruby versions.
For this reason, I recommend sticking to rescue StandardError in most cases. In order to avoid catching too much, move as much as possible outside the begin-rescue-end block, preferably leave only a call to one of the Net::HTTP methods.
Your intuition on this is right, for the most robust solution, I'd probably rescue each one individually (or in small groups) and take the appropriate action, like trying the connection again, or abandoning the request all together. I like to avoid using a very high-level/generic rescue because it might catch exceptions that I'm not prepared for or didn't expect.
Related
Sorry if it's an easy question, I am pretty new to Ruby.
When users sign up, or login I would like to keep statistics with Redis
def create
#user = User.new(user_params)
if #user.save
$redis.hincrby("2016MMDD", "new_users", 1)
render json: #user
end
end
If for any reason $redis.hincrby fails, is there a way to continue the execution of the code and render the user anyway?
Sure, just wrap the risky code in a begin/rescue/end block that captures the issue and continues execution.
http://rubylearning.com/satishtalim/ruby_exceptions.html. - see handling exceptions.
You might want to research what exceptions to look for, say if redis has gone away, rather than the base Exception catch
I see myself handling similar exceptions in a rather similar fashion repeatedly and would like to use aspects to keep this error handling code outside of the core business logic. A quick search online pulled up a couple of ruby gems (aquarium, aspector, etc) but I don't see a whole lot of downloads for those gems in rubygems. Given that, I want to believe there are probably other nicer ways to deal with this in Ruby.
get '/products/:id' do
begin
product = find_product params[:id]
rescue Mongoid::Errors::DocumentNotFound
status 404
end
end
get '/users/:id' do
begin
user = find_user params[:id]
rescue Mongoid::Errors::DocumentNotFound
status 404
end
end
In the above example, there are 2 Sinatra routes that look for a requested object by ID in MongoDB and throw a 404 if the object were not to be found. Clearly, the code is repetitive and I am looking to find a Ruby way to make it DRY.
You can see answer in this guide.
You code example:
error Mongoid::Errors::DocumentNotFound do
status 404
end
Assuming I have a WebCrawler class. There are several errors it can encounter. How should I propagate the errors upward?
Using exceptions:
class WebCrawler
class UrlBadFormatError < StandardError; end
class PageNotFoundError < StandardError; end
class UnauthorizedError < StandardError; end
def crawl(url)
if(! url =~ /some_format/)
raise UrlBadFormatError
response = get(url)
if(response.code == 404)
raise PageNotFoundError
if(response.code == 403)
raise UnauthorizedError
...
end
end
or constants:
class WebCrawler
URL_BAD_FORMAT = 1
PAGE_NOT_FOUND = 2
UNAUTHORZIED = 3
def crawl(url)
if(! url =~ /some_format/)
return URL_BAD_FORMAT
response = get(url)
if(response.code == 404)
return PAGE_NOT_FOUND
if(response.code == 403)
return UNAUTHORZIED
...
end
end
or symbols:
class WebCrawler
def crawl(url)
if(! url =~ /some_format/)
return :url_bad_format
response = get(url)
if(response.code == 404)
return :page_not_found
if(response.code == 403)
return :unauthorized
...
end
end
which is best? or it depends(on what?)
For something which indicates programmer error, such as the wrong type of argument passed to a method, definitely throw an exception. The exception will crash the program, drawing the programmer's attention to the fact that they are using your class incorrectly, so they can fix the problem. In this case, returning an error code wouldn't make sense, because the program will have to include code to check the return value, but after the program is debugged, such errors shouldn't ever happen.
In your WebCrawler class, is it expected that crawl will receive a bad URL as an argument sometimes? I think the answer is probably no. So raising an exception would be appropriate when a bad URL is passed.
When an exception is raised, the flow of execution suddenly "jumps" to the innermost handler. This can be a useful way to structure code when the exception is not expected to happen most of the time, because you can write the "main flow" of your method as simple, straight-line code without including a lot of details about what will happen when some rare error condition occurs. Those details can be separated from the "main flow" code, and put in an exception handler. When an error condition is expected to happen under normal conditions, though, it can be better to put the error handling code inline with the "main flow", to make it clearer what is going on. If the control flow of your program "jumps around" (as is the case when exceptions are used for normal flow control), that means the reader also has to "jump around" in the program text as they are figuring out how it works.
For the other two, I think it is expected that at least sometimes, the HTTP request will return an error code. To determine whether an exception or special return value is the best way to indicate such a condition, I would think about how often those conditions are going to happen under normal usage. Think also about how the client code will read either way. If you use exceptions, they will have to write something like:
urls.map do |url|
begin
crawl(url)
rescue PageNotFoundError
""
rescue UnauthorizedError
""
end
end
(By the way, I think this code example shows something: it might be a good idea if both of your custom exceptions inherit from a common superclass, so you can catch both of them with a single rescue clause if desired.) Or if you use error codes, it would look something like:
urls.map do |url|
response = crawl(url)
if [:page_not_found, :unauthorized].include? response
""
else
response
end
end
Which do you think reads better? It's really up to you. The one thing which you do not want to do is use integer constants for errors. Why use integers? When you print them in a debug trace, you'll have to go look at the list of constants to see what each one means. And using symbols is just as efficient computationally.
Why wouldn't you throw exceptions? They can encapsulate additional information besides just the type, are trivially rescued, and if you're using an IDE, are first-class citizens.
If it's an exception then by all means raises an exception! All three of those cases are, in my opinion, exceptions. While some may argue that 4xx status codes aren't exception-worthy since you may expect them to happen, they are still client errors.
You may also read about Ruby's throw/catch, which offer exception-like behavior for cases where "don't use exceptions for control flow" applies (though I don't think that's the case here).
You should raise errors. If you encounter a malformed URL, or if the page isn't found, or if you weren't authorized to access the page, it means you cannot continue crawling. Raising an error or exception returns from the method and lets the caller deal with the unusual situation.
It should also include information about the error, such as error codes, the URL which resulted in an error and any other relevant information. It can help in deciding how best to handle the error and can later be formatted into a helpful message for the user.
What you should not do, ever, is return numeric error codes. Ruby is not C. Just use symbols instead.
I am against the use of exceptions upon encountering 403s, 404s, malformed urls and similar common occurences on the web. Exceptions are meant for "internal" errors. In the World Wild Web, bad URLs are entirely unexceptional. There should be a method(s) for handling each different URL disease. I would personally return special values as symbols, or some "SpecialCase" objects recording what happened. There is also underused catch...throw statement.
What’s the best way to rescue exceptions from Net::HTTP?
Exceptions thrown are described in Ruby’s socket.c, like Errno::ETIMEDOUT, Errno::ECONNRESET, and Errno::ECONNREFUSED. The base class to all of these is SystemCallError, but it feels weird to write code like the following because SystemCallError seems so far removed from making an HTTP call:
begin
response = Net::HTTP.get_response(uri)
response.code == "200"
rescue SystemCallError
false
end
Is it just me? Is there a better way to handle this beyond fixing Net::HTTP to handle the Errno exceptions that would likely pop up and encapsulate them in a parent HttpRequestException?
I agree it is an absolute pain to handle all the potential exceptions. Look at this to see an example:
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.
What I've done, and seen most people do, is move away from Net::HTTP and move to 3rd party HTTP libraries such as:
httparty and faraday
I experienced the same problem, and after a lot of research, I realized the best way to to handle all exceptions Net::HTTP methods would throw is to rescue from StandardError.
As pointed by Mike Lewis's answer, Tammer Saleh blog post proposes rescuing from a lot exceptions, but it is still flaw. There are some exceptions he does not rescue from, like Errno::EHOSTUNREACH, Errno::ECONNREFUSED, and possible some socket exceptions.
So, as I found out in tenderlove's translation of an old ruby-dev thread, the best solution is rescuing from StandardError, unfortunately:
begin
response = Net::HTTP.get_response(uri)
rescue StandardError
false
end
It is awful, but if you want your system does not break because of these other exceptions, use this approach.
Another approach is to aggregate all these exceptions in a constant, and then re-use this constant, e.g.:
ALL_NET_HTTP_ERRORS = [
Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, EOFError,
Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError
]
begin
your_http_logic()
rescue *ALL_NET_HTTP_ERRORS
…
end
It is far more maintainable and cleaner.
However, word of warning. I've copied the possible exceptions list from the aforementioned Tammer Saleh's blog post, and I know that his list is incomplete. For instance, Net::HTTP.get(URI("wow")) raises Errno::ECONNREFUSED which is not listed. Also, I wouldn't be surprised if the list should be modified for different Ruby versions.
For this reason, I recommend sticking to rescue StandardError in most cases. In order to avoid catching too much, move as much as possible outside the begin-rescue-end block, preferably leave only a call to one of the Net::HTTP methods.
Your intuition on this is right, for the most robust solution, I'd probably rescue each one individually (or in small groups) and take the appropriate action, like trying the connection again, or abandoning the request all together. I like to avoid using a very high-level/generic rescue because it might catch exceptions that I'm not prepared for or didn't expect.
I have a statement that looks something like this
if not #user.verified? or #user.credit_card.expired?
# do stuff
end
The problem is the case where the #user does not yet have a credit card because expired? will raise an exception because of the nil value.
Is there a succinct way to catch the error inline without having to resort to extra if statements?
To avoid this exception you can use Rails 2.3/3 built-in method: try
if not #user.verified? or #user.credit_card.try(:expired?)
# do stuff
end
The andand gem allows for a guarded method invocation if and only if the object is not null.
Your code snippet would look like this:
if not #user.verified? or #user.credit_card.andand.expired?
# do stuff
end
You can install it with gem install andand and you're good to go.
Website: http://andand.rubyforge.org/
The code
#user.credit_card.expired?
Would be called a violation of the Law of Demeter. That law states you're allowed to call any method you like on #user, but not on #user.credit_card.