How to deal with timeout in currency conversion service? - ruby-on-rails

I just created this little currency converter for my Rails 4 app:
module Currency
def self.get_exchange_rate(from_curr = "EUR", to_curr = "USD")
if from_curr == to_curr
result = 1
else
begin
amount = 1
url = "http://www.google.com/finance/converter?a=#{amount}&from=#{from_curr}&to=#{to_curr}"
doc = Nokogiri::HTML(open(url))
result_span = doc.css('span.bld').text
result = result_span.tr('^0-9.', '')
rescue => e
puts e
result = 1
end
end
result
end
end
I haven't done this a lot, so my question would be: How can I deal with the (unlikely) event that Google Currency times out or is not available for some reason?
In that case I would like my result to be 1. How can this be achieved?
Thanks for any suggestions.

You can simply rescue any error that Nokogiri might raise (or OpenURI) like so:
require 'nokogiri'
require 'open-uri'
def currency(a)
Nokogiri::HTML(open(a))
rescue => e
puts e
1 # default value when error is raised
end
puts currency('https://www.somedomainthatdoesntexist.com')
That will print the backtrace of the error and then return the number 1
getaddrinfo: nodename nor servname provided, or not known
1

For a custom timeout actions you can use Timeout module
http://ruby-doc.org/stdlib-2.1.2/libdoc/timeout/rdoc/Timeout.html

It'd be worth looking at this answer to see how to get nokogiri to timeout : Adjusting timeouts for Nokogiri connections
The question makes use of the timeout module which is an option but I think it is better to get the connection to time out as per the accepted answer.
All that will mean that you'll get a Timeout::Error exception raised if the call times out which you then need to handle:
begin
doc = ... use nokogiri ...
result_span = doc.css('span.bld').text
result = result_span.tr('^0-9.', '')
rescue Timeout::Error
result = 1
end
result

Related

Rails read file Yomu gem - Psych::SyntaxError Exception - how to rescue

I have Rails application that will give me number of pages from doc, docx and pdf if I upload it.
To get the number of pages, I use Yomu gem in following way in Document model:
def update_information
doc = Yomu.new(self.doc.url)
self.num_of_pages = doc.metadata['xmpTPg:NPages']
self.file_updated_at = doc.metadata['modified'] || doc.metadata['Creation-Date'] || Time.now
self.file_created_at = doc.metadata['Creation-Date'] || Time.now
end
One of my documents is throwing exception:
*** Psych::SyntaxError Exception: (<unknown>): mapping values are not allowed in this context at line 23 column 15
and I can't figure it out how I could rescue it or avoid the problem without breaking the experience (it's a website).
By googling I tried this:
begin
doc = Yomu.new(self.doc.url)
self.num_of_pages = doc.metadata['xmpTPg:NPages']
self.file_updated_at = doc.metadata['modified'] || doc.metadata['Creation-Date'] || Time.now
self.file_created_at = doc.metadata['Creation-Date'] || Time.now
rescue Psych::SyntaxError, StandardError => e
print e
end
But it didn't work and it's still throwing the error and breaking the flow.
Any suggestion how to fix?

rescue from connection reset by peer error and retry

I am hitting an external service which does some password encryption and returns couple of things.
Now if I want to generate 50 passwords we run this function in a loop 50 times
def self.encrypt_password(password)
retries = 2
uri = URI
params = Base64.encode64(password)
uri.query = URI.encode("Source=#{params}")
begin
retries.times.each do
res = Net::HTTP.get_response(uri)
if res.is_a?(Net::HTTPSuccess)
obj = JSON.parse(res.body)
pw = Base64.decode64(obj["Data"])
ps = Base64.decode64(obj["Key"])
pws = Iconv.iconv('ascii', 'utf-16', pws)
return pwe,pws[0]
end
end
rescue
raise "Error generating pws: #{$!}"
end
end
But the problem, i am encountering is that there are occasions when the service just returns the following error in the middle of a loop and exits:
"Connection reset by Peer error"
My question is how do I rescue from that error and retry a few times without breaking the flow of the program?
Or can someone recommend alternate solutions to my problem?
NOTE: I am using ruby on rails 2 and ruby 1.8.x
Ruby has the retry method, that can be used in the rescue clause.
It just runs the current method again, so you can use a counter to limit the number of retries:
def self.encrypt_password(password)
retries = 2
uri = URI
params = Base64.encode64(password)
uri.query = URI.encode("Source=#{params}")
retries.times.each do
res = Net::HTTP.get_response(uri)
if res.is_a?(Net::HTTPSuccess)
obj = JSON.parse(res.body)
pw = Base64.decode64(obj["Data"])
ps = Base64.decode64(obj["Key"])
pws = Iconv.iconv('ascii', 'utf-16', pws)
return pwe,pws[0]
end
end
rescue SomeExceptionType
if retries > 0
retries -= 1
retry
else
raise "Error generating pws: #{$!}"
end
end
end

How to check for specific rescue clause for error handling in Rails 3.x?

I have the following code:
begin
site = RedirectFollower.new(url).resolve
rescue => e
puts e.to_s
return false
end
Which throws errors like:
the scheme http does not accept registry part: www.officedepot.com;
the scheme http does not accept registry part: ww2.google.com/something;
Operation timed out - connect(2)
How can I add in another rescue for all errors that are like the scheme http does not accept registry part?
Since I want to do something other than just printing the error and returning false in that case.
That depends.
I see the three exception descriptions are different. Are the Exception types different as well?
If So you could write your code like this:
begin
site = RedirectFollower.new(url).resolve
rescue ExceptionType1 => e
#do something with exception that throws 'scheme http does not...'
else
#do something with other exceptions
end
If the exception types are the same then you'll still have a single rescue block but will decide what to do based on a regular expression. Perhaps something like:
begin
site = RedirectFollower.new(url).resolve
rescue Exception => e
if e.message =~ /the scheme http does not accept registry part/
#do something with it
end
end
Does this help?
Check what is exception class in case of 'the scheme http does not accept registry part' ( you can do this by puts e.class). I assume that this will be other than 'Operation timed out - connect(2)'
then:
begin
.
rescue YourExceptionClass => e
.
rescue => e
.
end
Important Note: Rescue with wildcard, defaults to StandardError. It will not rescue every error.
For example, SignalException: SIGTERMwill not be rescued with rescue => error. You will have to specifically use rescue SignalException => e.

code to ping websites works sometimes

I'm testing out a piece of code to ping a bunch of websites I own on a regular basis, to make sure they're up.
I'm using rails and so far I have this hideous test action that I'm using to try it out (see below).
The problem though, is that sometimes it works, and other times it won't ... sometimes it runs through the code just fine, other times, it seems to completely ignore the begin/rescue block ...
a. I need help figuring out what the problem is
b. And refactoring this to make it look respectable.
Your help is much appreciated.
edit 1: Here is the updated code, sorry it took so long, pastie.org was down since yesterday http://pastie.org/927201
Its still doing the same thing ... skipping the begin block (because it only updates up_check_time) ... however if one of the sites times out, it actually updates everything (check_msg, code etc) correctly ... confusing, yeah?
require 'net/http'
require 'uri'
def ping
#sites = NewsSource.all
#sites.each do |site|
if site.uri and !site.uri.empty?
uri = URI.parse(site.uri)
response = nil
path = uri.path.blank? ? '/' : uri.path
path = uri.query.blank? ? path : "#{path}?#{uri.query}"
begin
Net::HTTP.start(uri.host, uri.port) {|http|
http.open_timeout = 30
http.read_timeout = 30
response = http.head(path)
}
if response.code.eql?('200') or response.code.eql?('301') or response.code.eql?('302')
site.up = true
else
site.up = false
end
site.up_check_msg = response.message
site.up_check_code = response.code
rescue Errno::EBADF
rescue Timeout::Error
site.up = false
site.up_check_msg = 'timeout'
site.up_check_code = '408'
end
site.up_check_time = 0.seconds.ago
site.save
end
end
end
You currently have an empty rescue block for Errno::EBADF so if that exception is raised then you will not be setting site.up to false.
Also, a couple of other minor improvements:
Instead of if site.uri and !site.uri.empty? you can use:
next if site.uri.nil? or site.uri.empty?
to skip that iteration of the each loop and avoid indenting the code by an additional level.
And:
if response.code.eql?('200') or response.code.eql?('301') or response.code.eql?('302')
site.up = true
else
site.up = false
end
can be written more concisely:
site.up = ['200', '301', '302'].include? response.code
If you tidy up the code with some of these tips then it might help narrow down the problem.
Here's a snippet from one of my programs, maybe it helps:
urls.each_with_index do |url, idx|
print "Processing URL #%04d: " % (idx+1)
uri = URI.parse(url)
response = nil
begin
Net::HTTP.start(uri.host, uri.port) do |http|
response = http.head(uri.path.size > 0 ? uri.path : "/")
end
rescue => e
puts "#{e.message} - #{url}"
next
end
# handle redirects
if response.is_a?(Net::HTTPRedirection)
new_uri = URI.parse(response['location'])
puts "URI redirects to #{new_uri}"
next
end
puts case response.code
when '200' then ...
when '404' then ...
else ...
end
end
The only thing that I can think of is that you are getting some other exception in your begin block. Since you are only explicitly rescuing Errno::EBADF, Timeout::Error it would appear that your begin and rescue got skipped. You might be able to verify this by getting rid of Errno::EBADF, Timeout::Error and just having a plain rescue, then put the following in your rescue block
logger.info(">>Exception was: "+$!)
Then look in your logs to see what exceptions you are getting.
If you are monitoring your servers why not use Nagios? it's free and also has some Ruby support, Here and Here.
EDIT:
Ruby GEM: http://hobodave.com/2010/01/10/simple-nagios-probes-in-ruby/

read_timeout in Nokogiri?

I'm fetching some weather data from an online xml doc using Nokogiri, and I would like to set up a timeout for graceful recovery in case the source can't be reached...
My google searches show several possible methods for open-uri and Net::HTTP, but none specific to Nokogiri. My attempts to use those methods are failing (not too surprisingly):
begin
currentloc = ("http://api.wunderground.com/auto/wui/geo/WXCurrentObXML/index.xml?query=" + #destination.weatherloc)
currentloc.read_timeout = 10 #
doc = Nokogiri::XML(open(currentloc))
rescue Timeout::Error
return "Current weather for this location not available: request timed out."
end
returns "NoMethodError", and:
begin
currentloc = ("http://api.wunderground.com/auto/wui/geo/WXCurrentObXML/index.xml?query=" + #destination.weatherloc)
doc = Nokogiri::XML(open(currentloc), :read_timeout => 10)
rescue Timeout::Error
return "Current weather for this location not available: request timed out."
end
returns "TypeError can't convert Hash into String"
Does Nokogiri support this kind of method (and if so... how?), or should I be looking at some other solution?
Thanks.
You can use the timeout module :
require 'open-uri'
require 'nokogiri'
require 'timeout'
begin
timeout(10) do
result = Nokogiri::XML(open(currentloc))
end
rescue Timeout::Error
return "Current weahter..."
end

Resources