I'm currently using Chef to build out a cookbook that has to fire off a bunch of POST calls to this API and I have to capture the response in a variable to use it in a second HTTP call.
I've tried using the http_request resource from Ruby but I can only fire the call but don't know how to get the response captured:
http_request 'authorize' do
action :post
url '*****************************' headers ({
'Content-Type' => 'application/json'
}) message ({
:Username => "**********",
:Password => "**********"
}).to_json
end
In another attempt, I tried using Chef's http client to fire off a POST call and a get a response:
require "net/https"
require "uri"
require "json"
uri = URI("******************************")
req = Net::HTTP::Post.new(uri)
req.set_form_data("Username" => "********", "Password" => "*********")
res = Net::HTTP.start(uri.hostname, uri.port) do |http|
http.request(req)
end
case res
when Net::HTTPSuccess, Net::HTTPRedirection
# OK
else
res.value
end
But I keep getting this error when I run the chef-client on my node:
EOFError
--------
end of file reached
How can I send off a POST call using Chef/Ruby and capture its response?
You want to use the Chef::HTTP client class, see https://coderanger.net/chef-tips/#4 for an example.
In my case the request url had https so I had to add this line and it was working.
http.use_ssl = true
If you want a more generic approach that can handle both http and https see this answer -
https://stackoverflow.com/a/9227933/6226283
Related
I am trying to use ruby with a website's api. The instructions are to send a GET request with a header. These are the instructions from the website and the example php code they give. I am to calculate a HMAC hash and include it under an apisign header.
$apikey='xxx';
$apisecret='xxx';
$nonce=time();
$uri='https://bittrex.com/api/v1.1/market/getopenorders?apikey='.$apikey.'&nonce='.$nonce;
$sign=hash_hmac('sha512',$uri,$apisecret);
$ch = curl_init($uri);
curl_setopt($ch, CURLOPT_HTTPHEADER, array('apisign:'.$sign));
$execResult = curl_exec($ch);
$obj = json_decode($execResult);
I am simply using an .rb file with ruby installed on windows from command prompt. I am using net/http in the ruby file. How can I send a GET request with a header and print the response?
Using net/http as suggested by the question.
References:
Net::HTTP https://ruby-doc.org/stdlib-2.4.1/libdoc/net/http/rdoc/Net/HTTP.html
Net::HTTP::get https://ruby-doc.org/stdlib-2.4.1/libdoc/net/http/rdoc/Net/HTTP.html#method-c-get
Setting headers: https://ruby-doc.org/stdlib-2.4.1/libdoc/net/http/rdoc/Net/HTTP.html#class-Net::HTTP-label-Setting+Headers
Net::HTTP::Get https://ruby-doc.org/stdlib-2.4.1/libdoc/net/http/rdoc/Net/HTTP/Get.html
Net::HTTPGenericRequest https://ruby-doc.org/stdlib-2.4.1/libdoc/net/http/rdoc/Net/HTTPGenericRequest.html and Net::HTTPHeader https://ruby-doc.org/stdlib-2.4.1/libdoc/net/http/rdoc/Net/HTTPHeader.html (for methods that you can call on Net::HTTP::Get)
So, for example:
require 'net/http'
uri = URI("http://www.ruby-lang.org")
req = Net::HTTP::Get.new(uri)
req['some_header'] = "some_val"
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') { |http|
http.request(req)
}
puts res.body # <!DOCTYPE html> ... </html> => nil
Note: if your response has HTTP result state 301 (Moved permanently), see Ruby Net::HTTP - following 301 redirects
Install httparty gem, it makes requests way easier, then in your script
require 'httparty'
url = 'http://someexample.com'
headers = {
key1: 'value1',
key2: 'value2'
}
response = HTTParty.get(url, headers: headers)
puts response.body
then run your .rb file..
As of Ruby 3.0, Net::HTTP.get_response supports an optional hash for headers:
Net::HTTP.get_response(URI('http://www.example.com/index.html'), { 'Accept' => 'text/html' })
Unfortunately this does not work for Ruby 2 (up to 2.7).
So here's the request using curl:
curl -XPOST -H content-type:application/json -d "{\"credentials\":{\"username\":\"username\",\"key\":\"key\"}}" https://auth.api.rackspacecloud.com/v1.1/auth
I've been trying to make this same request using ruby, but I can't seem to get it to work.
I tried a couple of libraries also, but I can't get it to work.
Here's what I have so far:
uri = URI.parse("https://auth.api.rackspacecloud.com")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
request = Net::HTTP::Post.new("/v1.1/auth")
request.set_form_data({'credentials' => {'username' => 'username', 'key' => 'key'}})
response = http.request(request)
I get a 415 unsupported media type error.
You are close, but not quite there. Try something like this instead:
uri = URI.parse("https://auth.api.rackspacecloud.com")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
request = Net::HTTP::Post.new("/v1.1/auth")
request.add_field('Content-Type', 'application/json')
request.body = {'credentials' => {'username' => 'username', 'key' => 'key'}}.to_json
response = http.request(request)
This will set the Content-Type header as well as post the JSON in the body, rather than in the form data as your code had it. With the sample credentials, it still fails, but I suspect it should work with real data in there.
There's a very good explanation of how to make a JSON POST request with Net::HTTP at this link.
I would recommend using a library like HTTParty. It's well-documented, you can just set up your class like so:
class RackSpaceClient
include HTTParty
base_uri "https://auth.api.rackspacecloud.com/"
format :json
headers 'Accept' => 'application/json'
#methods to do whatever
end
It looks like the main difference between the Ruby code you placed there, and the curl request, is that the curl request is POSTing JSON (content-type application/json) to the endpoint, whereas request.set_form_data is going to send a form in the body of the POST request (content-type application/x-www-form-urlencoded). You have to make sure the content going both ways is of type application/json.
All others are too long here is a ONE LINER:
Net::HTTP.start('auth.api.rackspacecloud.com', :use_ssl => true).post(
'/v1.1/auth', {:credentials => {:username => "username",:key => "key"}}.to_json,
initheader={'Content-Type' => 'application/json'}
)
* to_json needs require 'json'
OR if you want to
NOT verify the hosts
be more readable
ensure the connection is closed once you're done
then:
ssl_opts={:use_ssl => true, :verify_mode => OpenSSL::SSL::VERIFY_NONE}
Net::HTTP.start('auth.api.rackspacecloud.com', ssl_opts) { |secure_connection|
secure_connection.post(
'/v1.1/auth', {:credentials => {:username => "username",:key => "key"}}.to_json,
initheader={'Content-Type' => 'application/json'}
)
}
In case it's tough to remember what params go where:
SSL options are per connection so you specify them while opening the connection.
You can reuse the connection for multiple REST calls to same base url. Think of thread safety of course.
Header is a "request header" and hence specified per request. I.e. in calls to get/post/patch/....
HTTP.start(): Creates a new Net::HTTP object, then additionally opens the TCP connection and HTTP session.
HTTP.new(): Creates a new Net::HTTP object without opening a TCP connection or HTTP session.
Another example:
#!/usr/bin/ruby
require 'net/http'
require 'json'
require 'uri'
full_url = "http://" + options[:artifactory_url] + "/" + "api/build/promote/" + options[:build]
puts "Artifactory url: #{full_url}"
data = {
status: "staged",
comment: "Tested on all target platforms.",
ciUser: "builder",
#timestamp: "ISO8601",
dryRun: false,
targetRepo: "#{options[:target]}",
copy: true,
artifacts: true,
dependencies: false,
failFast: true,
}
uri = URI.parse(full_url)
headers = {'Content-Type' => "application/json", 'Accept-Encoding'=> "gzip,deflate",'Accept' => "application/json" }
http = Net::HTTP.new(uri.host, uri.port)
request = Net::HTTP::Post.new(uri.request_uri, headers)
request.basic_auth(options[:user], options[:password])
request.body = data.to_json
response = http.request(request)
puts response.code
puts response.body
So here's the request using curl:
curl -XPOST -H content-type:application/json -d "{\"credentials\":{\"username\":\"username\",\"key\":\"key\"}}" https://auth.api.rackspacecloud.com/v1.1/auth
I've been trying to make this same request using ruby, but I can't seem to get it to work.
I tried a couple of libraries also, but I can't get it to work.
Here's what I have so far:
uri = URI.parse("https://auth.api.rackspacecloud.com")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
request = Net::HTTP::Post.new("/v1.1/auth")
request.set_form_data({'credentials' => {'username' => 'username', 'key' => 'key'}})
response = http.request(request)
I get a 415 unsupported media type error.
You are close, but not quite there. Try something like this instead:
uri = URI.parse("https://auth.api.rackspacecloud.com")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
request = Net::HTTP::Post.new("/v1.1/auth")
request.add_field('Content-Type', 'application/json')
request.body = {'credentials' => {'username' => 'username', 'key' => 'key'}}.to_json
response = http.request(request)
This will set the Content-Type header as well as post the JSON in the body, rather than in the form data as your code had it. With the sample credentials, it still fails, but I suspect it should work with real data in there.
There's a very good explanation of how to make a JSON POST request with Net::HTTP at this link.
I would recommend using a library like HTTParty. It's well-documented, you can just set up your class like so:
class RackSpaceClient
include HTTParty
base_uri "https://auth.api.rackspacecloud.com/"
format :json
headers 'Accept' => 'application/json'
#methods to do whatever
end
It looks like the main difference between the Ruby code you placed there, and the curl request, is that the curl request is POSTing JSON (content-type application/json) to the endpoint, whereas request.set_form_data is going to send a form in the body of the POST request (content-type application/x-www-form-urlencoded). You have to make sure the content going both ways is of type application/json.
All others are too long here is a ONE LINER:
Net::HTTP.start('auth.api.rackspacecloud.com', :use_ssl => true).post(
'/v1.1/auth', {:credentials => {:username => "username",:key => "key"}}.to_json,
initheader={'Content-Type' => 'application/json'}
)
* to_json needs require 'json'
OR if you want to
NOT verify the hosts
be more readable
ensure the connection is closed once you're done
then:
ssl_opts={:use_ssl => true, :verify_mode => OpenSSL::SSL::VERIFY_NONE}
Net::HTTP.start('auth.api.rackspacecloud.com', ssl_opts) { |secure_connection|
secure_connection.post(
'/v1.1/auth', {:credentials => {:username => "username",:key => "key"}}.to_json,
initheader={'Content-Type' => 'application/json'}
)
}
In case it's tough to remember what params go where:
SSL options are per connection so you specify them while opening the connection.
You can reuse the connection for multiple REST calls to same base url. Think of thread safety of course.
Header is a "request header" and hence specified per request. I.e. in calls to get/post/patch/....
HTTP.start(): Creates a new Net::HTTP object, then additionally opens the TCP connection and HTTP session.
HTTP.new(): Creates a new Net::HTTP object without opening a TCP connection or HTTP session.
Another example:
#!/usr/bin/ruby
require 'net/http'
require 'json'
require 'uri'
full_url = "http://" + options[:artifactory_url] + "/" + "api/build/promote/" + options[:build]
puts "Artifactory url: #{full_url}"
data = {
status: "staged",
comment: "Tested on all target platforms.",
ciUser: "builder",
#timestamp: "ISO8601",
dryRun: false,
targetRepo: "#{options[:target]}",
copy: true,
artifacts: true,
dependencies: false,
failFast: true,
}
uri = URI.parse(full_url)
headers = {'Content-Type' => "application/json", 'Accept-Encoding'=> "gzip,deflate",'Accept' => "application/json" }
http = Net::HTTP.new(uri.host, uri.port)
request = Net::HTTP::Post.new(uri.request_uri, headers)
request.basic_auth(options[:user], options[:password])
request.body = data.to_json
response = http.request(request)
puts response.code
puts response.body
this is my code and I don't know how to debug it because I just get an "internal server error":
I am trying to HTTP POST to an external ASPX:
def upload
uri = 'https://api.postalmethods.com/2009-02-26/PostalWS.asmx' #postalmethods URI
#https://api.postalmethods.com/2009-02-26/PostalWS.asmx?op=UploadFile
#http://www.postalmethods.com/method/2009-02-26/UploadFile
#postalcard = Postalcard.find(:last)
#Username=string&Password=string&MyFileName=string&FileBinaryData=string&FileBinaryData=string&Permissions=string&Description=string&Overwrite=string
filename = #postalcard.postalimage.original_filename
filebinarydata = File.open("#{#postalcard.postalimage.path}",'rb')
body = "Username=me&Password=sekret&MyFileName=#{filename}&FileBinaryData=#{filebinarydata}"
#response = RestClient.post(uri,
body, #body as string
{"Content-Type" => 'application/x-www-form-urlencoded',
"Content-Length" => #postalcard.postalimage.size} # end headers
) #close arguments to Restclient.post
end
Turns on PostalMethods had an error and bug on their HTTP POST.
It only takes SOAP so I would need Savon.
How do I send a JSON request in ruby? I have a JSON object but I dont think I can just do .send. Do I have to have javascript send the form?
Or can I use the net/http class in ruby?
With header - content type = json and body the json object?
uri = URI('https://myapp.com/api/v1/resource')
body = { param1: 'some value', param2: 'some other value' }
headers = { 'Content-Type': 'application/json' }
response = Net::HTTP.post(uri, body.to_json, headers)
require 'net/http'
require 'json'
def create_agent
uri = URI('http://api.nsa.gov:1337/agent')
http = Net::HTTP.new(uri.host, uri.port)
req = Net::HTTP::Post.new(uri.path, 'Content-Type' => 'application/json')
req.body = {name: 'John Doe', role: 'agent'}.to_json
res = http.request(req)
puts "response #{res.body}"
rescue => e
puts "failed #{e}"
end
HTTParty makes this a bit easier I think (and works with nested json etc, which didn't seem to work in other examples I've seen.
require 'httparty'
HTTParty.post("http://localhost:3000/api/v1/users", body: {user: {email: 'user1#example.com', password: 'secret'}}).body
This works on ruby 2.4 HTTPS Post with JSON object and the response body written out.
require 'net/http' #net/https does not have to be required anymore
require 'json'
require 'uri'
uri = URI('https://your.secure-url.com')
Net::HTTP.start(uri.host, uri.port, :use_ssl => uri.scheme == 'https') do |http|
request = Net::HTTP::Post.new(uri, 'Content-Type' => 'application/json')
request.body = {parameter: 'value'}.to_json
response = http.request request # Net::HTTPResponse object
puts "response #{response.body}"
end
real life example, notify Airbrake API about new deployment via NetHttps
require 'uri'
require 'net/https'
require 'json'
class MakeHttpsRequest
def call(url, hash_json)
uri = URI.parse(url)
req = Net::HTTP::Post.new(uri.to_s)
req.body = hash_json.to_json
req['Content-Type'] = 'application/json'
# ... set more request headers
response = https(uri).request(req)
response.body
end
private
def https(uri)
Net::HTTP.new(uri.host, uri.port).tap do |http|
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
end
end
end
project_id = 'yyyyyy'
project_key = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
url = "https://airbrake.io/api/v4/projects/#{project_id}/deploys?key=#{project_key}"
body_hash = {
"environment":"production",
"username":"tomas",
"repository":"https://github.com/equivalent/scrapbook2",
"revision":"live-20160905_0001",
"version":"v2.0"
}
puts MakeHttpsRequest.new.call(url, body_hash)
Notes:
in case you doing authentication via Authorisation header set header req['Authorization'] = "Token xxxxxxxxxxxx" or http://api.rubyonrails.org/classes/ActionController/HttpAuthentication/Token.html
A simple json POST request example for those that need it even simpler than what Tom is linking to:
require 'net/http'
uri = URI.parse("http://www.example.com/search.json")
response = Net::HTTP.post_form(uri, {"search" => "Berlin"})
I like this light weight http request client called `unirest'
gem install unirest
usage:
response = Unirest.post "http://httpbin.org/post",
headers:{ "Accept" => "application/json" },
parameters:{ :age => 23, :foo => "bar" }
response.code # Status code
response.headers # Response headers
response.body # Parsed body
response.raw_body # Unparsed body
It's 2020 - nobody should be using Net::HTTP any more and all answers seem to be saying so, use a more high level gem such as Faraday - Github
That said, what I like to do is a wrapper around the HTTP api call,something that's called like
rv = Transporter::FaradayHttp[url, options]
because this allows me to fake HTTP calls without additional dependencies, ie:
if InfoSig.env?(:test) && !(url.to_s =~ /localhost/)
response_body = FakerForTests[url: url, options: options]
else
conn = Faraday::Connection.new url, connection_options
Where the faker looks something like this
I know there are HTTP mocking/stubbing frameworks, but at least when I researched last time they didn't allow me to validate requests efficiently and they were just for HTTP, not for example for raw TCP exchanges, this system allows me to have a unified framework for all API communication.
Assuming you just want to quick&dirty convert a hash to json, send the json to a remote host to test an API and parse response to ruby this is probably fastest way without involving additional gems:
JSON.load `curl -H 'Content-Type:application/json' -H 'Accept:application/json' -X POST localhost:3000/simple_api -d '#{message.to_json}'`
Hopefully this goes without saying, but don't use this in production.
The net/http api can be tough to use.
require "net/http"
uri = URI.parse(uri)
Net::HTTP.new(uri.host, uri.port).start do |client|
request = Net::HTTP::Post.new(uri.path)
request.body = "{}"
request["Content-Type"] = "application/json"
client.request(request)
end
data = {a: {b: [1, 2]}}.to_json
uri = URI 'https://myapp.com/api/v1/resource'
https = Net::HTTP.new uri.host, uri.port
https.use_ssl = true
https.post2 uri.path, data, 'Content-Type' => 'application/json'
Using my favourite http request library in ruby:
resp = HTTP.timeout(connect: 15, read: 30).accept(:json).get('https://units.d8u.us/money/1/USD/GBP/', json: {iAmOne: 'Hash'}).parse
resp.class
=> Hash