I learning ruby and playing it with restsclient I have following test the code and I'm expecting that to return 1/false. I can't seem to make it work.
n#lap-jta102:~/tsamcode$ ./get.rb
n#lap-jta102:~/tsamcode$ echo $?
0
#!/usr/bin/env ruby
require 'rest_client'
require 'json'
begin
response = RestClient.get("https://admin:admin#172.16.210.10/isam/host_records/187.0.0.1/hostnames", :content_type => :json, :accept => :json)
return true if response.code == 200
rescue => e
return false unless response != 200
end
$? is not set by return, but by exit. In fact, your return doesn't even do what you think. Try just this:
# one-returner.rb
return 1
$ ruby one-returner.rb
one-returner.rb:1:in `<main>': unexpected return (LocalJumpError)
The reason you're not getting an error in your program is the fact that you blanket-rescue this error when raised by return true (since you have an unrestricted rescue, which is a bad practice for exactly this reason, it can catch a wrong thing and leave you puzzled), and return false never executes (and thus never raises an error) due to unless.
Related
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'm trying to integrate web sockets into my Rails application as per this link: https://github.com/websocket-rails/websocket-rails/wiki/Installation-and-Setup
I use Ruby 2.0 and Rails3.
The contents of the relevant files:
Gemfile:
gem 'websocket-rails
events.rb
WebsocketRails::EventMap.describe do
subscribe :awesomeness_approval, :to => ChatController, :with_method => :awesomeness_approval
end
development.rb
...
config.middleware.delete Rack::Lock
...
chat_controller.rb
class ChatController < WebsocketRails::BaseController
def initialize_session
# perform application setup here
controller_store[:message_count] = 0
end
def awesomeness_approval
if message[:awesomeness] > 5
trigger_success {:m => 'awesome level is sufficient'}
else
trigger_failure {:m => 'awesome level is insufficient'}
end
end
end
chat.js
var dispatcher = new WebSocketRails('localhost:3000/websocket');
dispatcher.on_open = function(data) {
console.log('Connection has been established: ', data);
}
var success = function(response) {
console.log("You are awesome because: "+response.message);
}
var failure = function(response) {
console.log("You are not very awesome because: "+response.message);
}
var message = { awesomeness: 4 }
dispatcher.trigger('awesomeness_approval', message, success, failure);
When I try to start rails server, I get the following error:
w/app/controllers/chat_controller.rb:9: syntax error, unexpected =>, expecting '}' (SyntaxError)
trigger_success {:m => 'awesome level is sufficient'}
...
/app/controllers/chat_controller.rb:11: syntax error, unexpected =>, expecting '}'
trigger_failure {:m => 'awesome level is insufficient'}
However, if I comment out the above lines, I'm able to start the server, and the browser is able to connect to the web socket. The following output is seen on browser console:
"Connection has been established: " Object { connection_id: "f66c4298eb103312e181" } chat.js:3
Please help.
Your error comes from a syntax ambiguity in your method call.
When you write
trigger_success {:m => 'awesome level is sufficient'}
the opening curly brace is interpreted to be the start of a block argument to the method. However, you intend it to be a hash. Interpreted as a block, it would indeed be wrong syntax as a block doesn't allow a simple hash rocket.
You can resolve this ambiguity, you can use parenthesis:
trigger_success({:m => 'awesome level is sufficient'})
That way, it is clear for the ruby parser that you actually mean to pass a single hash argument to the method and is currently parsed.
I have a pair of Puppet custom functions, one of which, namely am_func_cluster, returns a hash of array of currently running instances (reading a file as input) and the second one call that function, iterate over the array and returns the first successful one that listening to port 22. Here is the 2nd function:
module Puppet::Parser::Functions
newfunction(:am_func_head, :type => :rvalue ) do |args|
Puppet::Parser::Functions.function('am_func_cluster')
mCls = function_am_func_cluster(['/opt/running-inst.txt'])
cls = args[0].to_sym if args[0].is_a? String
require 'socket'
require 'timeout'
mCls[cls].each do |dns|
begin
Timeout::timeout(1) { TCPSocket.new(dns, 22)
return (dns if mCls.key?(cls)) || 'undefined'
}
break
rescue SocketError
rescue Timeout::Error
end
end
end
end
upon running, it returns this error:
Error: Could not retrieve catalog from remote server: Error 400 on
SERVER: return can't jump across threads at
/etc/puppet/manifests/nodes.pp:19 on node ip-10-0-9-130.xxx
It works just fine, if I comment out the begin ... end bit in the script. Google didn't favor much in this case, so far. Does anyone one know what that error means or what am I doing wrong. Still don't find myself very efficient understanding the errors returned by Ruby. Any pointer much appreciated. Cheers!!
As it says, you cannot use return there. Use break to escape from the timeout block:
require "timeout"
Timeout.timeout(3){break "foo"}
# => "foo"
and you should not use break outside of it.
As a general tip, it is easy to confuse return, break, and next. If one of them does not work, try another.
I have the following line of code that is giving me an error:
rescue Timeout::Error => e
logs.puts("Rescued a timeout error...#{e}")
email_ids_all.each do |email_delete|
call= "/api/v2/emails/#{email_delete}/"
uri= HTTParty.delete("https://www.surveys.com#{call}",
:basic_auth => auth,
:headers => { 'ContentType' => 'application/x-www-form-urlencoded', 'Content-Length' => "0" }
)
puts "Deleted email #{email_delete}".green
log.puts("Deleted email #{email_delete}")
end
abort #abort entire script after deleting emails
end
The error I am receiving is this:
syntax error, unexpected keyword_rescue, expecting $end
rescue Timeout::Error => e
^
Essentially I am just trying to run an API delete call if the script times out. It doesn't seem to matter what I put in the block for rescue though, I receive the same error. What's wrong with my syntax on the rescue method?
The format for using rescue is as follows:
begin
# Code you want to run that might raise exceptions
rescue YourExceptionClass => e
# Code that runs in the case of YourExceptionClass
rescue ADifferentError => e
# Code that runs in the case of ADifferentError
else
# Code that runs if there was no error
ensure
# Code that always runs at the end regardless of whether or not there was an error
end
Here is a question with lots more information: Begin, Rescue and Ensure in Ruby?.