Failing HTTP request for localhost? - ruby-on-rails

I am using RestClient to get a URL in a Rake task.
I'm getting this error:
the scheme http does not accept registry part: :80 (or bad hostname?)
on the second line here:
def check_url url
RestClient.get('http://localhost:2828'){ |response, request, result, &block|
case response.code
when 200
return true
else
# response.return!(request, result, &block)
return false
end
}
end
What am I missing? RestClient.get with localhost works fine for me in the console.
Any help would be great.

Related

How do you retry an HTTP request to an API if the response code is other than "200 OK"?

I'm making requests to an external API that 9 out of 10 times returns a JSON string that I use to create records in my own App.
That one time though, it will return an "Internal Server Error" (code 500), crashing the rest of my App as it tries to parse a nil JSON String.
How can I retry the external API call if the response.code is other than "200 OK" ?
External API #connector (HTTP Request)
def fetch_client(client_identification)
url = URI("#{BASE_URL}/clients/#{client_identification}")
https = Net::HTTP.new(url.host, url.port)
https.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = "Bearer 1234567890qwertyuiopasdfghjklzxcvbnm"
response = https.request(request)
end
My Adapter that does something with the response (it fails as it tries to parse an empty string)
def get_client(client_identification)
response = #connector.fetch_client(client_identification)
# how to retry if response.code != "200" ?
JSON.parse(response.body) # crash
end
I've tried something like this but my code fails to compile throwing an "Invalid retry" error
retry if response.code != "200"
First you are missing a lot of error handling such as Net::ReadTimeout, Errno::ECONNRESET, Errno::ECONNABORTED, Errno::EPIPE, OpenSSL::SSL::SSLError, Timeout::Error and probably others. For those Net::HTTP has max_retries but you'll need to rescue if not ok after max_retries is reached.
But for the best case scenario, you could call get_client again (recursion) BUT you need to set a retry limit to not get a stack overflow OR if you want to retry only once and don't care about DRY:
def get_client(client_identification)
response = #connector.fetch_client(client_identification)
if response.code != "200"
response = #connector.fetch_client(client_identification)
end
response.code != "200" ? {} : JSON.parse(response.body)
end

How to run cURL commands in Rails

I'm using Ruby on Rails 5 and I need to execute the following command in my application:
curl -F 'client_id=126581840734567' -F 'client_secret=678ebe1b3b8081231aab27dff738313' -F 'grant_type=authorization_code' -F 'redirect_uri=https://uri.com/' -F 'code=AQBi4L2Ohy3Q_N3V48OygFm0zb3gEsL985x5TIyDTNDJaLs93BwXiT1tyGYWoCg1HlBDU7ZRjUfLL5HVlzw4G-7YkVEjp6Id2WuqOz0Ylt-k2ADwDC5upH3CGVtHgf2udQhLlfDnQz5NPsnmxjg4bW3PJpW5FaQs8fn1ztgYp-ssfAf6IRt2-sI45ZC8cqqr5K_12y0Nq_Joh0H-tTfVyNLKatIxHPCqRDb3tfqgmxim1Q' https://api.instagram.com/oauth/access_token
so that it returns something like:
{"access_token": "IGQVJYS0k8V6ZACRC10WjYxQWtyMVRZAN8VXamh0RVBZAYi34RkFlOUxXZnTJsbjlEfnFJNmprQThmQ4hTckpFUmJEaXZAnQlNYa25aWURnX3hpO12NV1VMWDNMWmdIT3FicnJfZAVowM3VldlVWZAEViN1ZAidHlyU2VDMUNuMm2V", "user_id": 17231445640157812}
Is there a way to make Rails execute those types of commands? I was trying the following:
uri = URI.parse('https://api.instagram.com/oauth/access_token')
http = Net::HTTP.new(uri.host, uri.port)
request = Net::HTTP::Post.new(uri.request_uri)
request.set_form_data({
"client_id" => "126581840734567",
"client_secret" => "678ebe1b3b8081231aab27dff738313",
"grant_type" => "authorization_code",
"redirect_uri" => "http://nace.network/",
"code" => params[:code]
})
res = Net::HTTP.start(uri.hostname, uri.port) do |http|
http.request(request)
end
but I get the following error:
end of file reached
in this line:
res = Net::HTTP.start(uri.hostname, uri.port) do |http|
http.request(request)
end
You're using HTTPS, so you need to add this to your code:
Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
res = http.request(request)
end
But if you don't need persistent connections, you could also use this:
res = Net::HTTP.post_form(uri,
"client_id" => "126581840734567",
"client_secret" => "678ebe1b3b8081231aab27dff738313",
"grant_type" => "authorization_code",
"redirect_uri" => "http://nace.network/",
"code" => params[:code]
)
Also, you could consider using a library like Faraday, which is a lot easier to deal with.
Edit
This is from TinMan's comment below, sound points.
Using cURL from inside Ruby or Rails is extremely valuable. There is an incredible amount of functionality inside cURL that isn't implemented in Rails or Ruby; Even Ruby's HTTP clients have a hard time replicating it, so cURL is very acceptable depending on the needs of the application. And, depending on the application, because cURL is in compiled C, it could easily outrun pure Ruby clients.
Curl is a means of issuing HTTP (or HTTPs) requests from the command line.
You don't want to use CURL in Rails. You want to issue HTTP requests from within Rails. Using curl is okay, it's one way to issue HTTP requests from with Rails.
We can refine that down further to, you want to issue HTTP requests from Ruby. Narrowing/distilling down to the most basic version of the problem is always good to do.
We knew all this already probably - still worth writing down for us all to benefit from!
Use HTTP in Ruby
We want to use a HTTP Client. There are many but, for this I'm going to use Faraday (a gem) 'cause I like it.
You've made a good start with Ruby's built in NET:HTTP but I prefer Faraday's DSL. It results in more readable and extendable code.
So, here is a class! I barely tested this so, use as a starting point. Make sure you write some unit tests for it.
# This is a Plain Old Ruby Object (PORO)
# It will work in Rails but, isn't Rails specific.
require 'faraday' # This require is needed as it's a PORO.
class InstagramOAuth
attr_reader :code
# The code parameter will likely change frequently, so we provide it
# at run time.
def initialize(code)
#code = code
end
def get_token
connection.get('/oauth/access_token') do |request|
request.params[:code] = code
end
end
private
def connection
#connection ||= Faraday.new(
url: instagram_api_url,
params: params,
ssl: { :ca_path => https_certificate_location }
)
end
def instagram_api_url
#url ||= 'https://api.instagram.com'
end
# You need to find out where these are for your self.
def https_certificate_location
'/usr/lib/ssl/certs'
end
def params
# These params likely won't change to often so we set a write time
# in the class like this.
{
client_id: '126581840734567',
client_secret: '678ebe1b3b8081231aab27dff738313',
grant_type: 'authorization_code',
redirect_uri: 'https://uri.com/'
}
end
end
# How do we use it? Like so
# Your big old authorisation code from your question
code = 'AQBi4L2Ohy3Q_N3V48OygFm0zb3gEsL985x5TIyDTNDJaLs93BwXiT1tyGYWoCg1HlBDU'\
'7ZRjUfLL5HVlzw4G-7YkVEjp6Id2WuqOz0Ylt-k2ADwDC5upH3CGVtHgf2udQhLlfDnQz'\
'5NPsnmxjg4bW3PJpW5FaQs8fn1ztgYp-ssfAf6IRt2-sI45ZC8cqqr5K_12y0Nq_Joh0H'\
'-tTfVyNLKatIxHPCqRDb3tfqgmxim1Q'
# This will return a Faraday::Response object but, what is in it?
response = InstagramOAuth.new(code).get_token
# Now we've got a Hash
response_hash = response.to_hash
puts 'Request made'
puts "Request full URL: #{response_hash[:url]}"
puts "HTTP status code: #{response_hash[:status]}"
puts "HTTP response body: #{response_hash[:body]}"
When I ran the snippet above I got the following. The class works, you just need to tweak the request params until you get what you want. Hopefully the class demonstrates how to send HTTP requests in Ruby/Rails.
Request made
Request full URL: https://api.instagram.com/oauth/access_token?client_id=126581840734567&client_secret=678ebe1b3b8081231aab27dff738313&code=AQBi4L2Ohy3Q_N3V48OygFm0zb3gEsL985x5TIyDTNDJaLs93BwXiT1tyGYWoCg1HlBDU7ZRjUfLL5HVlzw4G-7YkVEjp6Id2WuqOz0Ylt-k2ADwDC5upH3CGVtHgf2udQhLlfDnQz5NPsnmxjg4bW3PJpW5FaQs8fn1ztgYp-ssfAf6IRt2-sI45ZC8cqqr5K_12y0Nq_Joh0H-tTfVyNLKatIxHPCqRDb3tfqgmxim1Q&grant_type=authorization_code&redirect_uri=https%3A%2F%2Furi.com%2F
HTTP status code: 405
HTTP response body:
Additional Reading
. https://lostisland.github.io/faraday/usage/
. https://github.com/lostisland/faraday/wiki/Setting-up-SSL-certificates

Need to rescue 401 status code using RestClient

I am using a Rails gem to send requests to an api using RestClient. I need to rescue a 401 error code. I saw the following on RestClient documentation:
> RestClient.get('http://my-rest-service.com/resource'){ |response,
> request, result, &block| case response.code when 200
> p "It worked !"
> response when 423
> raise SomeCustomExceptionIfYouWant else
> response.return!(request, result, &block) end }
I have attempted to implement a similar case statement:
case response.code
when 200
JSON.parse(response.body)
when 401
raise AuthenicationError, "Unauthorized"
else
raise RestClient::ExceptionWithResponse
end
It captures the 200 case fine but ignores the 401 case and goes straight to the else. Any suggestions on raising an exception for 401 on a response that's coming back through RestClient?
I can't tell you and why I'm sure the rest-client repo can tell you :) ... but using RestClient::Request.new then executing the api call with a block works for me.
I think it probably has to do with the fact that the RestClient has built in exceptions.
request = RestClient::Request.new(
method: :get,
url: 'https://my-rest-service.com/resource.json')
response = request.execute {|response| response}
case response.code
when 200
puts "Good"
when 401
puts "Bad"
raise Exception
end
It captures the 200 case fine but ignores the 401 case and goes straight to the else.
I rather suspect it does not go to the else, actually; you'd still get a RestClient::ExceptionWithResponse raised even if you took out the else clause completely, because that's what RestClient.get does when it gets an error response such as in the 400 or 500 range. From the README:
for result codes between 200 and 207, a RestClient::Response will be returned
for result codes 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::ExceptionWithResponse holding the Response will be raised; a specific exception class will be thrown for known error codes
call .response on the exception to get the server's response
You are using wrong HTTP code. Unauthorized is actually 401, not 410.
If you catch the exception from Request.execute in a rescue block, note that you can also get the response body form the exception, example:
def request(method, url, params = {})
resp = RestClient::Request.execute(
method: method,
url: url,
timeout: 30,
accept: :json,
payload: params.to_json,
headers: {
content_type: :json,
}
)
JSON.parse(resp.body)
rescue => e
{ error: e.message, body: JSON.parse(e.response.body) } # <-------------
end

Ruby: failed to make a successful GET request

I am trying to send a http.get request to different websites. Here is the code I am using:
def makePing
begin
url = URI.parse(#URI)
req = Net::HTTP::Get.new(url.to_s)
res = Net::HTTP.start(url.host, url.port) {|http|
http.read_timeout = #request_timeout_limit
http.request(req)
}
# debugger
rescue Exception => echo
puts "Error is: Failed to open TCP connection to #{#URI}"
end
end
It returns the result of 200 for 'http://www.example.com'
but
for http://www.google.com or http://www.facebook.com
it returns
<Net::HTTPNotFound 404 Not Found readbody=true>
1-I am wondering why it happens like this?
2-How can I get the body of the response?
3- I expect that, the request get expired exactly after #request_timeout_limit, and it stop trying, but it is not working in this way?

How to solve error with using NET::HTTP

I want to post some data using standard ruby class NET::HTTP.
I have controller from examples
def request
require "net/http"
require "uri"
uri = URI.parse("http://google.com/")
# Shortcut
response = Net::HTTP.get_response(uri)
# Will print response.body
Net::HTTP.get_print(uri)
# Full
http = Net::HTTP.new(uri.host, uri.port)
response = http.request(Net::HTTP::Get.new(uri.request_uri))
end
My application gives error -
undefined method `content_mime_type' for #<Net::HTTPMovedPermanently 301 Moved Permanently readbody=true>
Why this is happening ?
Problem might be that in the last line of your code, there are two requests happening. The code translates to:
response = http.request(<result>) where the <some result> part is the return value from the call Net::HTTP::Get.new(uri.request_uri)
I think you were trying to do this instead:
http.request(uri.request_uri)

Resources