Rescue and this external API call - ruby-on-rails

I am using the following code to query maxmind for geolocation of a user's IP address. I want to make sure I am prepared for any errors/timeouts from maxmind's servers. Should I implement some type of rescue? If so, what is recommended?
uri = URI("https://geoip.maxmind.com/geoip/v2.1/city/#{request.remote_ip}?pretty")
Net::HTTP.start(uri.host, uri.port,
:use_ssl => uri.scheme == 'https',
:verify_mode => OpenSSL::SSL::VERIFY_NONE) do |http|
request = Net::HTTP::Get.new uri.request_uri
request.basic_auth 'USER_ID', 'KEY'
response = http.request request # Net::HTTPResponse object
if response.kind_of? Net::HTTPSuccess
location_hash = JSON.parse(response.body)
end
end
end

To rescue all exceptions:
begin
#your code
rescue Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, EOFError,
Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError => e
# do something with exception
end
You can also rescue single errors putting different rescues (use comma to rescue more than one at once):
begin
# your code
rescue Timeout::Error => e
rescue Errno::EINVAL => e
...
end

Related

ruby code to download a file from url with basic authentication

I am new to ruby and I am learning it.
I am looking to download a file from one url(eg: https://myurl.com/123/1.zip), with basic authentication. I tried to execute the following ruby script, from windows command prompt..
require 'net/http'
uri = URI('https://myurl.com/123/1.zip')
Net::HTTP.start(uri.host, uri.port,
:use_ssl => uri.scheme == 'https',
:verify_mode => OpenSSL::SSL::VERIFY_NONE) do |http|
request = Net::HTTP::Get.new uri.request_uri
request.basic_auth 'john#test.com', 'John123'
response = http.request request # Net::HTTPResponse object
puts response
puts response.body
end
When I executed the script, I see no errors but the file isn't downloaded. Could you please kindly correct my code
You can try this:
require 'open-uri'
File.open('/path/your.file', "wb") do |file|
file.write open('https://myurl.com/123/1.zip', :http_basic_authentication => ['john#test.com', 'John123']).read
end
You were almost there. Just make use of ruby's send_data method
require 'net/http'
uri = URI('https://myurl.com/123/1.zip')
Net::HTTP.start(uri.host, uri.port,
:use_ssl => uri.scheme == 'https',
:verify_mode => OpenSSL::SSL::VERIFY_NONE) do |http|
request = Net::HTTP::Get.new uri.request_uri
request.basic_auth 'john#test.com', 'John123'
http.request(request) do |response|
send_data(response.body, filename: 'set_filename.pdf')
end
end

How to i get ruby to return json response to be used in rails controller

I'm working with an Api, so far so good i am able to send requests and receive response, but i want the ruby code to send the json response and message that can be accessible from my rails controller so that i can get simple_form to render the error or success messages and also handle redirection from the rails controller, i also want to save transaction details from the response in a model, here's my lib code
class SimplePay
require 'net/https'
require 'uri'
require 'json'
def self.pay(api_key, token, amount, amount_currency, status)
uri = URI.parse('https://checkout.simplepay.ng/v2/payments/card/charge/')
header = {'Content-Type': 'text/json'}
data = {
'token': token,
'amount': amount,
'amount_currency': amount_currency
}
http = Net::HTTP.new(uri.host, uri.port)
request = Net::HTTP::Post.new(uri.request_uri, header)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
request = Net::HTTP::Post.new(uri.request_uri, header)
request.basic_auth(api_key, '')
request.set_form_data(data)
response = http.request(request)
res = JSON.parse(response.read_body)
response_code = res['response_code']
if response.kind_of? Net::HTTPSuccess
if response_code.eql? 20000
puts 'success'
else
puts 'Failed'
end
elsif status.eql? true
puts 'Success But Not Charged'
elsif status.eql? false
raise error 'Failed To Charge Card'
elsif response.kind_of? Net::HTTPBadRequest
raise error 'Failed 400'
elsif response.kind_of? Net::HTTPError
raise error 'Failed 404'
else
raise error 'Unknown Error'
end
end
end
How do i go about achieving this?
I would say send a Hash to the controller and then when you are sending it just doing a .to_json will make it into a json

Ruby on Rails 3: Streaming data and catching exception

I'm streaming data using the following approach:
self.response_body = Enumerator.new do |y|
10_000_000.times do |i|
y << "This is line #{i}\n"
end
end
I'm trying to catch any exception generated inside Enumerator and present something nicer to the user. Right now, the app is presenting an ugly error page from Torquebox. e.g.
.
I tried rescue and redirect_to and many other ways to catch the exception (including add a middleware class for handling exceptions). Any help would be appreciated!.
(The app is made under jruby v1.7.19 and torquebox v3.1.1)
I have partial solution for this issue:
Check the external file headers. The app is sending a header request to the external server.
1.1 If the status is 200 (success), then the app starts the retrieve process as before.
1.2 If the status is any other value, then an error page is displayed with the appropriate error message.
And the code looks like this:
uri = URI.parse(link)
net_http = Net::HTTP.new(uri.host, uri.port)
net_http.open_timeout = options[:timeout]
net_http.read_timeout = options[:timeout]
net_http.use_ssl = (uri.scheme == 'https')
if net_http.use_ssl?
net_http.cert = OpenSSL::X509::Certificate.new(File.read(options[:cert]))
net_http.key = OpenSSL::PKey::RSA.new(File.read(options[:cert]))
net_http.verify_mode = OpenSSL::SSL::VERIFY_NONE
end
begin
request = Net::HTTP::Get.new(uri.request_uri)
res = net_http.request(request)
if res.code.to_i == 200
self.response.headers['Pragma'] = 'no-cache'
self.response.headers['Cache-Control'] = 'private,max-age=0,must-revalidate,no-store'
self.response.headers['Last-Modified'] = Time.now.ctime.to_s
self.response.headers['Accept-Ranges'] = 'bytes'
self.response.headers['Content-Type'] = type
self.response.headers['Content-Disposition'] = "attachment; filename=\"#{save_as}\""
self.response.headers['Content-Transfer-Encoding'] = 'binary'
self.response.headers['Content-Description'] = 'File Transfer'
self.response.headers['Content-Length'] = "#{filesize}"
self.response_body = FileStreamer.new(link, options)
else
raise "The requested file is not available at the moment"
end
rescue SocketError => se
raise 'It seems the URL is not correct'
rescue Errno::ECONNREFUSED => cr
raise 'It seems the server refused the connection request'
rescue Errno::EHOSTUNREACH => hu
raise 'It seems the server cannot be reached'
rescue Errno::ENETUNREACH => nu
raise 'It seems the network cannot be reached'
rescue Timeout::Error => te
raise 'Request timed out while connecting to the server'
rescue StandardError => e
raise e.message
end
The FileStreamer is a rack middleware which response to each method.
def each
begin
#net_http.start do |http|
http.request_get(#uri.request_uri(),
{'User-Agent' => 'FileStreamer'}) do |response|
case response
when Net::HTTPSuccess then
response.read_body do |segment|
yield segment
end
when Net::HTTPRedirection then
raise 'Redirection not allowed'
else
raise "Error trying to retrieve a file:\n" \
" URL: #{#uri.to_s}\n" \
" Error: #{response.message.to_s}"
end
end
end
rescue SocketError => se
raise 'It seems the URL is not correct'
rescue Errno::ECONNREFUSED => cr
raise 'It seems the server refused the connection request'
rescue Errno::EHOSTUNREACH => hu
raise 'It seems the server cannot be reached'
rescue Errno::ENETUNREACH => nu
raise 'It seems the network cannot be reached'
rescue Timeout::Error => te
raise 'Request timed out while connecting to the server'
rescue StandardError => e
raise e.message
end
end
Keep in mind this solution is not including errors happening in the middle of the process, I guess if an error happens in the middle, then the app will display the ugly error page.

Ruby websocket client for websocket-rails gem

I am developing a rails webpage which need to use websocket functionality to communicate with an external ruby client. In order to do this, I am using the websocket-rails gem in the rails server, definning the client_connected client_disconnected event and a specific action to received the messages from the client (new_message).
On the client side I have tried to use different ruby gems like faye-websocket-ruby and websocket-client-simple but I always obtain errors when I try to send a message. On the server I can't find the way to process these messages. Both gems has a send method with only accepts a string (not being able to specify the name of the event)
The code that I have been using is the following:
Server side
app/controllers/chat_controller.rb
class ChatController < WebsocketRails::BaseController
def new_message
puts ')'*40
end
def client_connected
puts '-'*40
end
def client_disconnected
puts '&'*40
end
end
config/events.rb
WebsocketRails::EventMap.describe do
subscribe :client_connected, :to => ChatController, :with_method => :client_connected
subscribe :message, :to => ChatController, :with_method => :new_message
subscribe :client_disconnected, :to => ChatController, :with_method => :client_disconnected
end
config/initializers/websocket_rails.rb
WebsocketRails.setup do |config|
config.log_path = "#{Rails.root}/log/websocket_rails.log"
config.log_internal_events = true
config.synchronize = false
end
Client side
websocket-client-simple
require 'rubygems'
require 'websocket-client-simple'
ws = WebSocket::Client::Simple.connect 'ws://localhost:3000/websocket'
ws.on :message do |msg|
puts msg.data
end
ws.on :new_message do
hash = { channel: 'example' }
ws.send hash
end
ws.on :close do |e|
p e
exit 1
end
ws.on :error do |e|
p e
end
hash = { channel: 'Example', message: 'Example' }
ws.send 'new_message', hash
loop do
ws.send STDIN.gets.strip
end
faye-websocket
require 'faye/websocket'
require 'eventmachine'
EM.run {
ws = Faye::WebSocket::Client.new('ws://localhost:3000/websocket')
ws.on :open do |event|
p [:open]
end
ws.on :message do |event|
p [:message, event.data]
end
ws.on :close do |event|
p [:close, event.code, event.reason]
ws = nil
end
ws.send( 'Example Text' )
}
Thanks in advance. Regards.
PD: Let me know if you need more code.
Finally I have found the solution. The problem was that the message needs to be constructed with a certain format in order to be understood by websocket-rails.
Example: ws.send( '["new_message",{"data":"Example message"}]' )
where new_message is the event which websocket-rails is listening to.

How do I capture a rsolr Sunspot exception raised on a different thread from a model callback?

Even though I can capture the exception raised from #post.save and log the error and print the threads
begin
if #post.save
rescue Exception => ex
# rescue Errno::ECONNREFUSED => ex
Thread.list.each {|t| p t}
Rails.logger.error "ERROR: Could not save blog post! Is the Solr server up and running? Exception: #{ex}"
it still errors out on the web page and doesn't show any of my code in the stack trace. The solr Sunspot model callback is running on a separate thread.
rsolr (1.0.9) lib/rsolr/connection.rb:19:in `rescue in execute'
rsolr (1.0.9) lib/rsolr/connection.rb:14:in `execute'
...
sunspot_rails (2.1.0) lib/sunspot/rails/solr_instrumentation.rb:15:in `send_and_receive_with_as_instrumentation'
(eval):2:in `post'
rsolr (1.0.9) lib/rsolr/client.rb:67:in `update'
...
/usr/local/rvm/rubies/ruby-2.0.0-p247/lib/ruby/2.0.0/webrick/httpserver.rb:138:in `service'
/usr/local/rvm/rubies/ruby-2.0.0-p247/lib/ruby/2.0.0/webrick/httpserver.rb:94:in `run'
/usr/local/rvm/rubies/ruby-2.0.0-p247/lib/ruby/2.0.0/webrick/server.rb:295:in `block in start_thread'
Notice block in start_thread?
So how can I capture this exception and display an error to the user? I don't think the whole app should crash just because Solr isn't running.
I found this, http://makandracards.com/makandra/5273-using-solr-with-sunspot, but it only describes how to capture search exceptions, not index/update exceptions.
Wow. After what seems like 20 hours... create lib/rsolr/connection.rb:
require 'net/http'
require 'net/https'
# The default/Net::Http adapter for RSolr.
class RSolr::Connection
# using the request_context hash,
# send a request,
# then return the standard rsolr response hash {:status, :body, :headers}
def execute client, request_context
h = http request_context[:uri], request_context[:proxy], request_context[:read_timeout], request_context[:open_timeout]
request = setup_raw_request request_context
request.body = request_context[:data] if request_context[:method] == :post and request_context[:data]
begin
response = h.request request
charset = response.type_params["charset"]
{:status => response.code.to_i, :headers => response.to_hash, :body => force_charset(response.body, charset)}
rescue Errno::ECONNREFUSED => e
Rails.logger.error "ERROR: #execute: Could not connect to Solr: #{e.message}"
# How to display an error message to the user?
# ActionController::Base.flash.now.alert "Could not connect to search indexer."
# Maybe http://stackoverflow.com/questions/393395/how-to-call-expire-fragment-from-rails-observer-model/608700#608700 ?
return nil
# raise(Errno::ECONNREFUSED.new(request_context.inspect))
# catch the undefined closed? exception -- this is a confirmed ruby bug
rescue NoMethodError
Rails.logger.error "ERROR: #execute: NoMethodError: Could not connect to Solr: #{e.message}"
return nil
# $!.message == "undefined method `closed?' for nil:NilClass" ?
# raise(Errno::ECONNREFUSED.new) :
# raise($!)
end
end
protected
# This returns a singleton of a Net::HTTP or Net::HTTP.Proxy request object.
def http uri, proxy = nil, read_timeout = nil, open_timeout = nil
#http ||= (
http = if proxy
proxy_user, proxy_pass = proxy.userinfo.split(/:/) if proxy.userinfo
Net::HTTP.Proxy(proxy.host, proxy.port, proxy_user, proxy_pass).new uri.host, uri.port
else
Net::HTTP.new uri.host, uri.port
end
http.use_ssl = uri.port == 443 || uri.instance_of?(URI::HTTPS)
http.read_timeout = read_timeout if read_timeout
http.open_timeout = open_timeout if open_timeout
http
)
end
#
def setup_raw_request request_context
http_method = case request_context[:method]
when :get
Net::HTTP::Get
when :post
Net::HTTP::Post
when :head
Net::HTTP::Head
else
raise "Only :get, :post and :head http method types are allowed."
end
headers = request_context[:headers] || {}
raw_request = http_method.new request_context[:uri].request_uri
raw_request.initialize_http_header headers
raw_request.basic_auth(request_context[:uri].user, request_context[:uri].password) if request_context[:uri].user && request_context[:uri].password
raw_request
end
private
def force_charset body, charset
return body unless charset and body.respond_to?(:force_encoding)
body.force_encoding(charset)
end
end

Resources