uri = URI("http://#{url}")
res = Net::HTTP.get_response(uri) ### Code here
I read the path URLs from the txt file.
When a url socket error or timeout error is given the program closes.
To prevent it, I want to check that it does not give a url socket error.
uri = URI("http://#{url}")
if (uri== worked ) ### What should I use here?
res = Net::HTTP.get_response(uri)
end
Instead of an if statement, you should use an exception handler for this. Assuming the errors you're expecting are SocketError and Errno::ETIMEDOUT, that would look something like this:
begin
# code that might raise the error
rescue SocketError => e
# run this code in the event of a socket error
rescue Errno::ETIMEDOUT => e
# run this code in the event of a timeout
ensure
# run this code unless an error is raised that isn't accounted for
end
Related
I have the following code to help me catch redirect errors when processing URLs.
begin
page = Nokogiri::HTML(uri.open(redirect: false))
rescue OpenURI::HTTPRedirect => redirect
uri = redirect.uri
puts "retry #{tries}: #{uri}"
retry if (tries-=1) > 0
raise
It works well for URL redirect errors, but it doesn't catch any other types of errors. For example, a URL threw a 416 error, which couldn't be handled by the above code.
I can use
rescue StandardError => e
to catch the 416 error and skip the URL I was processing, but how do I catch both errors at the same time so that when there is a redirect error, the code knows where to redirect, and when there are other types of errors, the code knows when to skip?
Just need to put in another rescue. You can put in as many as you need.
begin
page = Nokogiri::HTML(uri.open(redirect: false))
rescue OpenURI::HTTPRedirect => redirect
uri = redirect.uri
puts "retry #{tries}: #{uri}"
retry if (tries-=1) > 0
raise
rescue OtherError => e
# do something
I’m using Rails 4.2.3 and Nokogiri to get data from a web site. I want to perform an action when I don’t get any response from the server, so I have:
begin
content = open(url).read
if content.lstrip[0] == '<'
doc = Nokogiri::HTML(content)
else
begin
json = JSON.parse(content)
rescue JSON::ParserError => e
content
end
end
rescue Net::OpenTimeout => e
attempts = attempts + 1
if attempts <= max_attempts
sleep(3)
retry
end
end
Note that this is different than getting a 500 from the server. I only want to retry when I get no response at all, either because I get no TCP connection or because the server fails to respond (or some other reason that causes me not to get any response). Is there a more generic way to take account of this situation other than how I have it? I feel like there are a lot of other exception types I’m not thinking of.
This is generic sample how you can define timeout durations for HTTP connection, and perform several retries in case of any error while fetching content (edited)
require 'open-uri'
require 'nokogiri'
url = "http://localhost:3000/r503"
openuri_params = {
# set timeout durations for HTTP connection
# default values for open_timeout and read_timeout is 60 seconds
:open_timeout => 1,
:read_timeout => 1,
}
attempt_count = 0
max_attempts = 3
begin
attempt_count += 1
puts "attempt ##{attempt_count}"
content = open(url, openuri_params).read
rescue OpenURI::HTTPError => e
# it's 404, etc. (do nothing)
rescue SocketError, Net::ReadTimeout => e
# server can't be reached or doesn't send any respones
puts "error: #{e}"
sleep 3
retry if attempt_count < max_attempts
else
# connection was successful,
# content is fetched,
# so here we can parse content with Nokogiri,
# or call a helper method, etc.
doc = Nokogiri::HTML(content)
p doc
end
When it comes to rescuing exceptions, you should aim to have a clear understanding of:
Which lines in your system can raise exceptions
What is going on under the hood when those lines of code run
What specific exceptions could be raised by the underlying code
In your code, the line that's fetching the content is also the one that could see network errors:
content = open(url).read
If you go to the documentation for the OpenURI module you'll see that it uses Net::HTTP & friends to get the content of arbitrary URIs.
Figuring out what Net::HTTP can raise is actually very complicated but, thankfully, others have already done this work for you. Thoughtbot's suspenders project has lists of common network errors that you can use. Notice that some of those errors have to do with different network conditions than what you had in mind, like the connection being reset. I think it's worth rescuing those as well, but feel free to trim the list down to your specific needs.
So here's what your code should look like (skipping the Nokogiri and JSON parts to simplify things a bit):
require 'net/http'
require 'open-uri'
HTTP_ERRORS = [
EOFError,
Errno::ECONNRESET,
Errno::EINVAL,
Net::HTTPBadResponse,
Net::HTTPHeaderSyntaxError,
Net::ProtocolError,
Timeout::Error,
]
MAX_RETRIES = 3
attempts = 0
begin
content = open(url).read
rescue *HTTP_ERRORS => e
if attempts < MAX_RETRIES
attempts += 1
sleep(2)
retry
else
raise e
end
end
I would think about using a Timeout that raises an exception after a short period:
MAX_RESPONSE_TIME = 2 # seconds
begin
content = nil # needs to be defined before the following block
Timeout.timeout(MAX_RESPONSE_TIME) do
content = open(url).read
end
# parsing `content`
rescue Timeout::Error => e
attempts += 1
if attempts <= max_attempts
sleep(3)
retry
end
end
In my Sinatra project, I'd like to be able to halt with both an error code and an error message:
halt 403, "Message!"
I want this, in turn, to be rendered in an error page template (using ERB). For example:
error 403 do
erb :"errors/error", :locals => {:message => env['sinatra.error'].message}
end
However, apparently env['sinatra.error'].message (aka the readme and every single website says I should do it) does not expose the message I've provided. (This code, when run, returns the undefined method `message' for nil:NilClass error.)
I've searched for 4-5 hours and experimented with everything and I can't figure out where the message is exposed for me to render via ERB! Does anyone know where it is?
(It seems like the only alternative I can think of is writing this instead of the halt code above, every time I would like to halt:
halt 403, erb(:"errors/error", :locals => {m: "Message!"})
This code works. But this is a messy solution since it involves hardcoding the location of the error ERB file.)
(If you were wondering, this problem is not related to the show_exceptions configuration flag because both set :show_exceptions, false and set :show_exceptions, :after_handler make no difference.)
Why doesn't it work − use the source!
Lets look at the Sinatra source code to see why this problem doesn't work. The main Sinatra file (lib/sinatra/base.rb) is just 2043 lines long, and pretty readable code!
All halt does is:
def halt(*response)
response = response.first if response.length == 1
throw :halt, response
end
And exceptions are caught with:
# Dispatch a request with error handling.
def dispatch!
invoke do
static! if settings.static? && (request.get? || request.head?)
filter! :before
route!
end
rescue ::Exception => boom
invoke { handle_exception!(boom) }
[..]
end
def handle_exception!(boom)
#env['sinatra.error'] = boom
[..]
end
But for some reason this code is never run (as tested with basic "printf-debugging"). This is because in invoke the block is run like:
# Run the block with 'throw :halt' support and apply result to the response.
def invoke
res = catch(:halt) { yield }
res = [res] if Fixnum === res or String === res
if Array === res and Fixnum === res.first
res = res.dup
status(res.shift)
body(res.pop)
headers(*res)
elsif res.respond_to? :each
body res
end
nil # avoid double setting the same response tuple twice
end
Notice the catch(:halt) here. The if Array === res and Fixnum === res.first part is what halt sets and how the response body and status code are set.
The error 403 { .. } block is run in call!:
invoke { error_block!(response.status) } unless #env['sinatra.error']
So now we understand why this doesn't work, we can look for solutions ;-)
So can I use halt some way?
Not as far as I can see. If you look at the body of the invoke method, you'll see that the body is always set when using halt. You don't want this, since you want to override the response body.
Solution
Use a "real" exception and not the halt "pseudo-exception". Sinatra doesn't seem to come with pre-defined exceptions, but the handle_exception! does look at http_status to set the correct HTTP status:
if boom.respond_to? :http_status
status(boom.http_status)
elsif settings.use_code? and boom.respond_to? :code and boom.code.between? 400, 599
status(boom.code)
else
status(500)
end
So you could use something like this:
require 'sinatra'
class PermissionDenied < StandardError
def http_status; 403 end
end
get '/error' do
#halt 403, 'My special message to you!'
raise PermissionDenied, 'My special message to you!'
end
error 403 do
'Error message -> ' + #env['sinatra.error'].message
end
Which works as expected (the output is Error message -> My special message to you!). You can return an ERB template here.
In Sinatra v2.0.7+, messages passed to halt are stored in the body of the response. So a halt with an error code and an error message (eg: halt 403, "Message!") can be caught and rendered in an error page template with:
error 403 do
erb :"errors/error", locals: { message: body[0] }
end
I am working on a project where I have implemented a TCP client server for a device communication. In order to send a command from the server to the client, I am building a command that the device understands and sending to it but the response is not what should be returned
while 1
Thread.start(#otd.accept) do |client|
loop do
command_to_send ="<R-2,3,4>"
client.puts command_to_send
puts "Command #{command_to_send}sent"
#sleep 2
response = **client.gets** # here it halts and never puts the the next statement.
puts "Reponse #{response}"
end # end of nested loop
client.close
end #END OF THREAD.
end #end of while loop
Can someone tell me what I am missing?
Do not use gets as it expects '\n' to be a delimiter of the message.
Instead use: recv here is a method that could help you:
def read(timeout = 2, buffer = 1024)
message = ''
begin
Timeout::timeout(timeout) do
buffer = client.recv(buffer)
message += buffer
end
rescue Timeout::Error
puts "Received nothing from client: #{client.__id__}"
message = ''
rescue Exception => e
raise "Client failed to read for reason - #{e.message}"
end
message
end
You do not have to use sleep anymore as recv like gets is blocking. But the timeout makes sure you are not stuck waiting for a response not existing.
In ruby, I'm not quite sure how to handle whether objects are nil or not.
For example I have the following:
begin
sp = SerialPort.new(#serial_device, #serial_bps, #serial_par, #serial_bits, SerialPort::NONE)
tcp = TCPSocket.new(#host, #port)
if (sp)
sp.print(command)
sp.close
elsif
tcp.print(command)
tcp.close
end
say siri_output
rescue
pp $!
puts "Sorry, I encountered an error: #{$!}"
ensure
request_completed
end
The problem is that the first object returns an error relating to:
No route to host - connect(2)
Which is correct, because TCP isn't connected duh. So I'd like it to use the next object instead.
Is there a way to do this without using certain Exceptions, I was wondering if there's a better way of doing what I'm after any how.
The problem is not the checks, you are doing that right. Anything that's not nil or false is true in ruby. It's that when you get an exception on the row that starts with "sp = .." the execution jumps to the resque block. You should restructure the code like this (I've removed the ensure clause because I do not know what it does). A good thing to do it's to rescue every specific type of exception in it's own row. by class name ex. NoConnectivityException => e (or what the class of the exception would be).
begin
sp = SerialPort.new(#serial_device, #serial_bps, #serial_par, #serial_bits, SerialPort::NONE)
sp.print(command)
sp.close
say siri_output
rescue Exception => e
puts "Sorry, I encountered an error: #{e.inspect}"
puts "trying TCP"
begin
tcp = TCPSocket.new(#host, #port)
tcp.print(command)
tcp.close
say siri_output
rescue Exception => e
puts "Sorry, I encountered an error: #{e.inspect}"
end
end
For quick and sloppy programming you can do another thing, but it's not recommended and generally a pain to debug, as any error results in nil and is silenced.
sp = SerialPort.new(#serial_device, #serial_bps, #serial_par, #serial_bits, SerialPort::NONE) rescue nil
tcp = TCPSocket.new(#host, #port) rescue nil
This way you'd end up with either a SerialPort object or nil object in the sp variable, and the same for sp.