How to invoke HTTP POST method over SSL in ruby? - ruby

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

Related

An error has occurred that says {"code":"BadArgument","requestId":"XXXX","message":"JSON format error."} on computer vision api of Azure

require 'net/http'
uri = URI('https://westus.api.cognitive.microsoft.com/vision/v1.0/analyze')
uri.query = URI.encode_www_form({
# Request parameters
'visualFeatures' => 'Adult',
'details' => 'Celebrities',
'language' => 'en'
})
request = Net::HTTP::Post.new(uri.request_uri)
# Request headers
request['Content-Type'] = 'application/json'
# Request headers
request['Ocp-Apim-Subscription-Key'] = 'caa91cccdgywtduwdxxf530'
# Request body
request.body = "http://instudy.jp/wp-content/uploads/2015/03/doraemon-coming.jpg"
response = Net::HTTP.start(uri.host, uri.port, :use_ssl => uri.scheme == 'https') do |http|
http.request(request)
end
puts response.body
I wanted to check the photo whether adult things or not so I wrote the code.
I think there is nothing wrong so I don't know why error's occurred.
environment: ruby 2.3.1
As per the documentaion, the vision api is expecting an image url in 'url' key of request body. Also in your case, you are not passing a valid json as request body. So, replace
request.body = "http://instudy.jp/wp-content/uploads/2015/03/doraemon-coming.jpg"
with
request_body = Hash.new
request_body[:url] = "http://instudy.jp/wp-content/uploads/2015/03/doraemon-coming.jpg"
request.body = request_body.to_json

Sending a http post request in ruby [duplicate]

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

How to add "Content-type" header in HTTP POST

I keep getting a
400 "Bad Request" (Net::HTTPServerException)
error whenever I try to add a content-type header from various methods.
I've seen several different examples and I can't get any to work. My goal is to add a content type of JSON to my request. Without the header, my request doesn't error:
def post_data(notice)
uri = URI('my uri is here')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
req = Net::HTTP::Post.new("#{uri.path}?#{uri.query}")
text = notice
req.add_field('Content-Type', 'application/json')
req.body = "{\"sensu_payload\" = #{payload(text).to_json}}"
response = http.request(req)
verify_response(response)
end
I've also tried this method of adding a header:
request = Net::HTTP::Post.new(#path, initheader = {'Content-Type' =>'application/json'})
Use uri.path instead of #path:
request = Net::HTTP::Post.new(uri.path, initheader = {'Content-Type' =>'application/json'})
Instead of add_field, I think you should use the hash [] form:
req['Content-Type'] = 'application/json'
See the "Setting Headers" example in the documentation and []=.
Also, using:
"#{uri.path}?#{uri.query}"
is never a good idea. String concatenation can't manage the complexities of correctly encoding illegal values in a query, which can break a request. Consider doing something like this instead:
require 'uri'
require 'uri'
foo = URI('http://www.example.com') # => #<URI::HTTP http://www.example.com>
foo.query = URI::encode_www_form({'bar' => 'path/to/file', 'baz' => 'this & that'})
foo.to_s # => "http://www.example.com?bar=path%2Fto%2Ffile&baz=this+%26+that"
Beyond that, I'd recommend using any of the other HTTP-client gems available. They make it much easier to deal with unexpected situations, like redirects and retries than Net::HTTP. It's more like the building block for features that aren't available other ways.

Ruby API (RabbitMQ create user)

Started working on Ruby a week back. Writing an API to connect to RabbitMQ messaging queue. The command line for adding a new user works.
$ curl -i -u guest:guest -H "content-type:application/json" -XPUT -d'{"password":"pwd","tags":"administrator"}' http://localhost:15672/api/users/username
I need to make this Http Put request from Ruby. The following is my code:
def test_add_user
uri = URI.parse('http://localhost:15672/api/users/karthik/')
uri.to_s
http = Net::HTTP.new(uri.host, uri.port)
request = Net::HTTP::Put.new(uri.path)
request.basic_auth 'guest', 'guest'
request['Content-Type'] = 'application/json'
request['Accept'] = 'application/json'
request.set_form_data({'password' => 'secret', 'tags' => 'management'})
http.start do |http|
res = http.request(request)
puts res
end
end
This is the result I get
o.test_add_user
#<Net::HTTPUnsupportedMediaType:0x007fd7fb6fe1d8>
=> nil
Does Media type exception relate with Content-Type?
Only application/json is allowed
Should I use anything like to_json? If yes, where should it be used? Thanks in advance.
Regards
Karthik
Thank you Hector and ptd. Fixed it. Attached the working code for future reference.
def test_add_user
uri = URI.parse('http://localhost:15672/api/users/Test1/')
http = Net::HTTP.new(uri.host, uri.port)
request = Net::HTTP::Put.new(uri.path)
request.basic_auth 'guest', 'guest'
request['Content-Type'] = 'application/json'
request['Accept'] = 'application/json'
request.body = {'password' => 'secret', 'tags' => 'management'}.to_json
http.start do |http|
res = http.request(request)
puts res
end
end
Adds a new user to the RabbitMQ queue

Ruby send JSON request

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

Resources