How to handle exceptions with Ruby Rest-Client - ruby

I recently switched from Ruby's Net:HTTP class to rest-client 1.6.7.
I find it a lot easier to form requests, but unlike Net:HTTP request, when rest-client gets anything other than a 200, the request dies. I've tried putting a breakpoint directly after the RestClient.get, and it never gets hit - so I'm doing something wrong.
def get_member_using_card
resource = "#{#settings_app_uri}api/v1/card/#{self.member_card_num}?token=#{#settings.api_key}"
response = RestClient.get resource
if response.code == 200
card = JSON.parse(response.body)
self.customer_id = card['card']['customer_id']
else
return 0
end
end
Which results in this stacktrace:
RestClient::ResourceNotFound - 404 Resource Not Found:
/Users/tim/.rvm/gems/ruby-1.9.2-p290/gems/rest-client-1.6.7/lib/restclient/abstr
act_response.rb:48:in `return!'
/Users/tim/.rvm/gems/ruby-1.9.2-p290/gems/rest-client-1.6.7/lib/restclient/reque
st.rb:230:in `process_result'
/Users/tim/.rvm/gems/ruby-1.9.2-p290/gems/rest-client-1.6.7/lib/restclient/reque
st.rb:178:in `block in transmit'
/Users/tim/.rvm/rubies/ruby-1.9.2-p290/lib/ruby/1.9.1/net/http.rb:627:in `start'
/Users/tim/.rvm/gems/ruby-1.9.2-p290/gems/rest-client-1.6.7/lib/restclient/reque
st.rb:172:in `transmit'
/Users/tim/.rvm/gems/ruby-1.9.2-p290/gems/rest-client-1.6.7/lib/restclient/reque
st.rb:64:in `execute'
/Users/tim/.rvm/gems/ruby-1.9.2-p290/gems/rest-client-1.6.7/lib/restclient/reque
st.rb:33:in `execute'
/Users/tim/.rvm/gems/ruby-1.9.2-p290/gems/rest-client-1.6.7/lib/restclient.rb:68
:in `get'
Can someone tell me how to properly evaluate the response code and keep this exception from happening...?

See heading Exceptions on http://rubydoc.info/gems/rest-client/
for results code between 200 and 207 a RestClient::Response will be returned
for results code 301, 302 or 307 the redirection will be followed if the request is a get or a head
for result code 303 the redirection will be followed and the request transformed into a get
for other cases a RestClient::Exception holding the Response will be raised, a specific exception class will be thrown for know error codes
RestClient.get 'http://example.com/resource'
➔ RestClient::ResourceNotFound: RestClient::ResourceNotFound`
begin
RestClient.get 'http://example.com/resource'
rescue => e
e.response
end
➔ 404 Resource Not Found | text/html 282 bytes

Also in the same documentation #wich pointed to, you can pass a block to RestClient.get such that it will not throw an exception on non-200 response codes:
# Don't raise exceptions but return the response
RestClient.get('http://example.com/resource'){|response, request, result| response }
See the "Result Handling" section from the documentation.

rescue RestClient::ExceptionWithResponse => err

There are several errors that could happen, specific exception types like Errno::EHOSTUNREACH or the more generic ExceptionWithResponse. Check the readme for more info.

I believe the best way to handle exceptions of an API client is to get the original error message thrown by the API endpoint. Here is an example code to handle that with RestClient
require 'json'
def get_call
begin
standard_response = {body: nil, success: false, message: ''}
response = RestClient.get('https://example.com/api/v1/xx', headers={'Authorization' => 'AbcDef xxx'})
standard_response[:success] = true
standard_response[:body] = JSON.parse(response.body)
rescue RestClient::ExceptionWithResponse => e
http_body = JSON.parse(e.http_body) # This is the original response from the API endpoint. e.g. {'message': 'Reason for the failure'}
meaningful_error_message = http_body['message'].nil? ? e.message : http_body['message'] # if {'message': 'error message'} is the format of your API
standard_response[:message] = meaningful_error_message
end
standard_response
end

Beautiful way to handle the exceptions in rest client.
For more info do check rest-client#response-callbacks-error-handling
RestClient.get('http://example.com/resource') { |response, request, result, &block|
case response.code
when 200
p "It worked !"
response
when 423
raise SomeCustomExceptionIfYouWant
else
response.return!(&block)
end
}

Related

How to view the X-Shopify-Shop-Api-Call-Limit

I want to view this specific header: X_SHOPIFY_SHOP_API_CALL_LIMIT
When I do this:
CreateShopifyClientService.call(shop)
begin
response = ShopifyAPI::InventoryLevel.find(:all, params: { inventory_item_ids: product.inventory_item_id})
byebug
rescue Exception => e
byebug
end
I've tried just about everything I can think of, including looking thru the tests on the gem and cannot find the mysterious header.
Here are some of my futile attempts:
(byebug) ap response.header
*** NoMethodError Exception: undefined method "header' for #<ShopifyAPI::PaginatedCollection:0x000055b39213c9c8>
(byebug) ap response.headers
*** NoMethodError Exception: undefined method "headers' for #<ShopifyAPI::PaginatedCollection:0x000055b39213c9c8>
I just did this in my terminal and it worked a peach...
shop = Shop.first
session = ShopifyAPI::Auth::Session.new(
shop: shop.shopify_domain,
access_token: shop.shopify_token
)
client = ShopifyAPI::Clients::Rest::Admin.new(
session: session
)
response = client.get(path: "products")
p response.headers["x-shopify-shop-api-call-limit"]
["1/40"]
=> ["1/40"]
So it is not too terribly hard to access the response headers. I see you're getting the response of the API call and not the HTTP response itself, so it seems you need to approach it differently for those old API calls. Maybe try updating to the more modern API and see if that helps you out.

How to determine http status code in range error handler

What I'd like to do is:
error 400..510 do
{:'400' => 'Bad Request', :'401' => ...}[<http-status-code>.to_s.to_sym]
end
where <http-status-code> is some expression, evaluated to error code to be returned. One possible way the handler is triggered:
get '/test' do
401
end
Is this achievable?
You can send a Net::HTTP request. The output of that request will be a Net::HTTPResponse object. For example, from the Ruby Net::HTTP documentation at http://ruby-doc.org/stdlib-2.0.0/libdoc/net/http/rdoc/Net/HTTP.html:
uri = URI('http://example.com/index.html')
res = Net::HTTP.get_response(uri)
res.code

How do I get a redirected status code using NET:HTTP?

Similar to "getting the status code of a HTTP redirected page", but with NET::HTTP instead of curb I am making a GET request to a page that that will redirect:
response = Net::HTTP.get_response(URI.parse("http://www.wikipedia.org/wiki/URL_redirection"))
puts response.code #{
puts response['location']
=> 301
en.wikipedia.org/wiki/URL_redirection
The problem is that I want to know the status code of the redirected page. In this case it is 200, but in my app I want to check if it is 200 or something else.
The solution I've seen is to just call get_response(response['location']), but that won't work in my application because the way the redirect is designed makes it so that the redirect can only be followed once. Since the first GET consumes that one redirect, I can't then follow it again.
Is there some way to get the last status code that is a result of a GET?
EDIT: Further clarification of the situation:
The application that I'm sending GET to has a single sign-on authentication mechanism where, if I want to access 'myapp/mypage', I have to first send a post:
postResponse = Net::HTTP.post_form(URI.parse("http://myapp.com/trusted"), {"username" => #username})
Then make the GET request to:
'http://myapp.com/trusted/#{postResponse.body}/mypage
*The postResponse.body is a 'ticket' which can be redeemed once.
That GET verifies that the ticket is valid and then redirects to:
myapp.com/mypage
So whether that ticket is valid or not, I get a 301.
I want to check the status code of the final get to myapp.com/mypage.
If I manually try to follow the redirect, whether it's a HEAD request or a GET, the original redirect will have already consumed the ticket, so I will get an error that the ticket is expired even if the original redirect was a 200.
The Net::HTTP documentation has example code showing how to deal with redirects. Have you tried it? It should make it easy to get inside the redirect mechanism and grab statuses for later.
Here's their example:
Following Redirection
Each Net::HTTPResponse object belongs to a class for its response code.
For example, all 2XX responses are instances of a Net::HTTPSuccess subclass, a 3XX response is an instance of a Net::HTTPRedirection subclass and a 200 response is an instance of the Net::HTTPOK class. For details of response classes, see the section “HTTP Response Classes” below.
Using a case statement you can handle various types of responses properly:
def fetch(uri_str, limit = 10)
# You should choose a better exception.
raise ArgumentError, 'too many HTTP redirects' if limit == 0
response = Net::HTTP.get_response(URI(uri_str))
case response
when Net::HTTPSuccess then
response
when Net::HTTPRedirection then
location = response['location']
warn "redirected to #{location}"
fetch(location, limit - 1)
else
response.value
end
end
print fetch('http://www.ruby-lang.org')
A minor change like this should help:
require 'net/http'
RESPONSES = []
def fetch(uri_str, limit = 10)
# You should choose a better exception.
raise ArgumentError, 'too many HTTP redirects' if limit == 0
response = Net::HTTP.get_response(URI(uri_str))
RESPONSES << response
case response
when Net::HTTPSuccess then
response
when Net::HTTPRedirection then
location = response['location']
warn "redirected to #{location}"
fetch(location, limit - 1)
else
response.value
end
end
print fetch('http://jigsaw.w3.org/HTTP/300/302.html')
puts RESPONSES.join("\n") # =>
I see this when I run it:
redirected to http://jigsaw.w3.org/HTTP/300/Overview.html
#<Net::HTTPOK:0x007f9e82a1e050>#<Net::HTTPFound:0x007f9e82a2daa0>
#<Net::HTTPOK:0x007f9e82a1e050>
If it's enough just to make an HTTP HEAD request without 'consuming' your URL (this would be the usual expectation for a HEAD request), you can do it like this:
2.0.0-p195 :143 > result = Net::HTTP.start('www.google.com') { |http| http.head '/' }
=> #<Net::HTTPFound 302 Found readbody=true>
So in your example you'd do this:
...
result = Net::HTTP.start(response.uri.host) { |http| http.head response.uri.path }
If you want to preserve a history of response codes, you could try this. This retains the last 5 response codes from calls to get_response and exposes them through a Net::HTTP.history method.
module Net
class << HTTP
alias_method :_get_response, :get_response
def get_response *args, &block
resp = _get_response *args, &block
#history = (#history || []).push(resp.code).last 5
resp
end
def history
#history || []
end
end
end
(I don't entirely get the usage scenario, so adapt to your needs)

uncaught exception with net/http request wrapper in ruby

I have a script that calls the facebook test api to automate test account creation. At seemingly random interval, from either after 50 requests to 6000 requests, I get an exception that isn't caught. I'm at a loss for how to figure out what the error is, so I'll start with the relevant code here.
I'm not using the URI library because facebook keys have the pipe character that breaks URI.parse for ruby 1.8.7.
require 'rubygems'
require 'net/https'
require 'json'
http = Net::HTTP.new(domain, 443)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
def request_wrapper(http, request)
retry_count = 5
begin
return http.request(request)
rescue Exception => e
retry_count--
if retry_count < 0
raise e
end
retry
end
for i in 0..500 do
request = Net::HTTP::Get.new('https://' + domain + path)
response = request_wrapper(http, request)
end
The code will work for some time, but always inevitably reports the following:
in rescue in request_wrapper': undefined method-#' for nil:NilClass
(NoMethodError)
Anyone ever seen this undefined method '-#' before? Again, this happens very intermittently, but it's been a real thorn in my side. It always point to the line in the code where I am calling the request wrapper.
Thanks for taking a look.
The problem is with the line retry_count--. This line gets evaluated only when a failed HTTP request raises an Exception, which explains why it occurs intermittently.
Ruby does not have a unary decrement (--) or increment (++) operator. Matz has outlined the philosophical reasons behind this here. Also see this thread and this one for more information.
Instead, retry_count -= 1 should do the job.

Faraday (Ruby) Timeout Errors

I'm attempting to put a small payload generated in route A (Sinatra app) to Route B using Faraday. So the code basically looks like:
post "/routeA" do
foo.save
foo_id = foo.id
conn = Faraday.new(:url => "http://localhost:3001/routeB" ) do |builder|
builder.request :url_encoded
builder.response :logger
builder.adapter :net_http
end
resp = conn.put do |req|
req.url '/routeB'
req.headers['Content-Type'] = 'application/json'
req.body = {:id => foo_id }.to_json
req.options = {
#:timeout => 5, # see below, these aren't the problem
#:open_timeout => 2
}
end
# never gets here b/c Timeout error always thrown
STDERR.puts resp.body
end
put "/routeB" do
# for test purposes just log output
STDERR.puts request.body.read.to_s.inspect
status 202
body '{"Ok"}'
end
Problem is that it always throws a timeout error (I've run without the timeout options, and with the ones shown above -> same results). However, the logs show the request is going through:
I, [2012-03-24T16:56:13.241329 #17673] INFO -- : put http://localhost:3001/routeB
D, [2012-03-24T16:56:13.241427 #17673] DEBUG -- request: Content-Type: "application/json"
#<Faraday::Error::TimeoutError>
DEBUG - POST (60.7987ms) /routeA - 500 Internal Server Error
"{\"id\":7}"
DEBUG - PUT (0.0117ms) /routeB - 202 Accepted
Not sure how to get past the timeout error? Any insight would be appreciated. Thanks.
The problem is that the application cannot respond to another request until it's finished with the current one. That is, when you make a PUT request on the /routeB, the application got that, and it's waiting for the current request (/routeA) to finish. But the request won't finish because it's waiting to get the response from the /routeB. I think this is what causes the timeout error.

Resources