I have multiple bruises today, trying to learn two things at once... the API for Postmark and Rails HTTP requests.
Goal: Use Postmark add-on for Heroku to send production email.
I am trying to combine this article on HTTP requests...
http://docs.ruby-lang.org/en/2.0.0/Net/HTTP.html
... with this API reference for Postmark...
http://developer.postmarkapp.com/developer-send-api.html
Unfortunately, the examples from Postmark are done in curl and I have not succeeded in translating them into a HTTP request. I suspect the problem centers around the headers -- the parts of the transmission other than the body.
The rescue clause seen in the code below traps the error 'connection reset by peer'. At this point I don't know if I am even close to the right format for the headers that provide Postmark authentication.
I have the proper server token (in the config entry) and the From email has been given the required Postmark signature.
def send_production_email(email_address, subject, email_body)
# Use API to interact with Heroku add-on Postmark
# http://developer.postmarkapp.com/developer-send-api.html
uri = URI('https://api.postmarkapp.com/email')
# Form the request
req = Net::HTTP::Post.new(uri)
# Set request headers -- SUSPECT THIS IS WRONG
req['Accept'] = 'application/json'
req['Content-Type'] = 'application/json'
req['X-Postmark-Server-Token'] = Rails.application.config.postmark_token
rbody ={
'From' => 'Support <michael#mydomain.com>',
'To' => email_address,
'Subject' => subject,
'HtmlBody' => wrap_html(email_body),
'TextBody' => email_body
}.to_json
req.body = rbody
# Send the request, waiting for the response
begin
response = Net::HTTP.new(uri.host, uri.port).start {|http| http.request(req) }
rescue Exception => e
logthis("http request error: #{e.message}")
return
end
# ...parsing section omitted since I do not get that far...
end
A second attempt was formatted this way, but results in the same peer reset error:
rbody ={
'From' => 'Support <michael#disambiguator.com>', # TODO: replace email when domain is live
'To' => email_address,
'Subject' => subject,
'HtmlBody' => wrap_html(email_body),
'TextBody' => email_body
}.to_json
uri = URI('https://api.postmarkapp.com/email')
http = Net::HTTP.new(uri.host, uri.port)
# http.use_ssl = true
request = Net::HTTP::Post.new(uri.path, {'Content-Type' => 'application/json', 'Accept' => 'application/json', 'X-Postmark-Server-Token' => Rails.application.config.postmark_token})
request.body = rbody
# Send the request, waiting for the response
begin
response = http.request(request)
rescue Exception => e
logthis("http request error: #{e.message}")
return
end
I am grateful for any guidance!
I’m a Wildbit’s employee and the maintainer of the official Postmark Ruby gem.
The "connection reset by peer" error is the result of you trying to send an unencrypted HTTP request to an endpoint expecting secure communication via HTTPS. So, if you change this line:
Net::HTTP.new(uri.host, uri.port).start {|http| http.request(req) }
to:
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
response = http.start { |http| http.request(req) }
then you should be able to receive a response from the API. I see that you have this line in the second example, but it is commented. Since you’re doing this as an exercise, I’d like to add that when using net/http you don’t usually have to work with the underlying classes like Net::HTTP::Post. It’s generally simpler to use the higher level API provided by instances of the Net::HTTP class. Here is an example of how your method could be simplified by using it:
def send_production_email(email_address, subject, email_body)
uri = URI('https://api.postmarkapp.com/email')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
headers = {'Accept' => 'application/json',
'Content-Type' => 'application/json',
'X-Postmark-Server-Token' => Rails.application.config.postmark_token}
payload = {'From' => 'tema#wildbit.com',
'To' => email_address,
'Subject' => subject,
'HtmlBody' => email_body,
'TextBody' => email_body}
http.post(uri.request_uri, payload.to_json, headers)
rescue => e
puts "http request error: #{e.message}"
end
And, if you’re interested in how net/http is used in the official Postmark Ruby gem, check out the HttpClient class’ source.
Related
I am trying to call external API for my project and I have some troubles while using Net::HTTP in my rails lib . Here is my code
class ApiCall
def self.do_api_request(api_token, body)
require 'net/http'
require 'uri'
uri = URI.parse('https://sample.com')
header = {'Token' => api_token, 'Content-Type' => 'application/json', 'Accept' => 'application/json'}
request = Net::HTTP::Post.new(uri.request_uri, header)
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = (uri.scheme == "https")
request.body = body
http.request(request)
end
end
This is how I use it (assume I knew the api_token and body):
body = {'id' => 2, 'age'=> 23};
ApiCall.do_api_request(api_token, body)
This way, it throws an error back:
NoMethodError: undefined method `bytesize' for Hash
Then after check online, seems like the body is hash instead of string, so I did this
body = URI.encode_www_form(body) and after rerun, it gives me :
400 bad request
I have no ideas how to put both header and body into a rails Net::HTTP method
Solution:
I know where the problem is. request body supposed to be string
so body = "{'id' : 2, 'age' : 23}" , I used body.to_json
I will suggest you to use HTTParty for calling an api. This is real simple to use. Following are the examples-
HTTParty.get("https://api.bigcommerce.com/stores/"+#store.store_hash+"/v3/catalog/categories", :headers => #your_header_data)
This will return the response. Also for post request,
HTTParty.post("https://api.bigcommerce.com/stores/"+#store.store_hash+"/v3/catalog/products", :headers => #auth, :body => product_json)
So you can pass body to in body param here.
Back story
I've spent a few days on the Ruby gem for wheniwork, and it's been a nightmare. To make a long story short, the gem had a dependency of an older version of activesupport and I basically forked their repo and made a compatible version for my ruby project. I was able to create a new gem and attempted to use it, but I was getting the following json message:
{"error"=>"User login required for this resource.", "code"=>1000}
The username, password, and api key were set. What I basically need to do is access our account on wheniwork, and retrieve data.
This is an example using the curl command
curl https://api.wheniwork.com/2/login \
--data '{"username":"user#example.com","password":"*******"}' \
-H "W-Key: iworksoharditsnotfunny"
Essentially, I would get some kind of return object from wheniwork that would contain the token. I'd use this token for future requests to the wheniwork site.
To (Ruby)
A new attempt to translate this to Ruby
require 'net/http'
data = {'credentials' => {'username' => 'XXXXX#XXXXX.com','password' => 'XXXXX', 'W-Key' => '111111111XXXXX'}}
uri = URI.parse("https://api.wheniwork.com/2/login/")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
request = Net::HTTP::Post.new(uri.path, initheader = {'Content-Type' =>'application/json'})
request.add_field('Content-Type', 'application/json')
request.body = data.to_json
response = http.request(request)
Result:
#<Net::HTTPUnauthorized 401 Unauthorized readbody=true>
If you have any idea or clue (or basically anything that would help me here), I would appreciate it beyond words. Thanks!
Got it!! The major issue(s) were that the api key and (username+password) combo are two separate pieces.
I was so close the entire time, but here it is:
data = {'username' => 'XXXX#XXXXXXX.com','password' => 'XXXXX'}
uri = URI.parse("https://api.wheniwork.com/2/login/")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
request = Net::HTTP::Post.new(uri.path)
request.add_field('W-Key', 'XXXXXXXXXXXXX')
request.body = data.to_json
response = http.request(request)
puts JSON.parse(response.body)
I am trying to perform an HTTP authorization using Ruby on Rails. Here is what I'm trying:
res = http.post(uri.request_uri,
:Authorization => cobSessionToken,
"coBrandSessionCredential=loginToken=#{cobSessionToken}&userLogin=#{login}&userPassword=#{password}")
render :json => {"isValid" => true, "Body" => JSON.parse(res.body)}
This doesn't seem to work. How can I perform an authorization?
how about something like this?
url = URI.parse('https://my.url.com/path')
req = Net::HTTP::Post.new(url.path)
req.basic_auth 'user', 'pass'
req.use_ssl = true
req.form_data({'key1' => 'val1', 'key2' => 'val2'})
resp = Net::HTTP.new(url.host, url.port).start {|http| http.request(req) }
puts resp
I would recommend using something like postman (its a free google program you can get at the google store) to make sure the error is not on the server side. Use Net:http it comes with ruby so you do not need to install it but you have to require it.
Require it by:
require "net/http"
require "uri"
Use this cheatsheet I think you need basic_auth.rb You will see how to form the request.
It has been a while since I have used Rails. I currently have a curl request as follows
curl -X GET -H 'Authorization: Element TOKEN, User TOKEN' 'https://api.cloud-elements.com/elements/api-v2/hubs/marketing/ping'
All I am looking to do is to be able to run this request from inside of a rails controller, but my lack of understanding when it comes to HTTP requests is preventing me from figuring it out to how best handle this. Thanks in advance.
Use this method for HTTP requests:
def api_request(type , url, body=nil, header =nil )
require "net/http"
uri = URI.parse(url)
case type
when :post
request = Net::HTTP::Post.new(uri)
request.body = body
when :get
request = Net::HTTP::Get.new(uri)
when :put
request = Net::HTTP::Put.new(uri)
request.body = body
when :delete
request = Net::HTTP::Delete.new(uri)
end
request.initialize_http_header(header)
#request.content_type = 'application/json'
response = Net::HTTP.start(uri.host, uri.port, :use_ssl => uri.scheme == 'https') {|http| http.request request}
end
Your example will be:
api_request(:get, "https://api.cloud-elements.com/elements/api-v2/hubs/marketing/ping",nil, {"Authorization" => "Element TOKEN, User TOKEN" })
It would be something like the following. Note that the connection will be blocking, so it can tie up your server depending on how quickly the remote host returns the HTTP response and how many of these requests you are making.
require 'net/http'
# Let Ruby form a canonical URI from our URL
ping_uri = URI('https://api.cloud-elements.com/elements/api-v2/hubs/marketing/ping')
# Pass the basic configuration to Net::HTTP
# Note, this is not asynchronous. Ruby will wait until the HTTP connection
# has closed before moving forward
Net::HTTP.start(ping_uri.host, ping_uri.port, :use_ssl => true) do |http|
# Build the request using the URI as a Net::HTTP::Get object
request = Net::HTTP::Get.new(ping_uri)
# Add the Authorization header
request['Authorization'] = "Element #{ELEMENT_TOKEN}, User #{user.token}"
# Actually send the request
response = http.request(request)
# Ruby will automatically close the connection once we exit the block
end
Once the block exits, you can use the response object as necessary. The response object is always a subclass (or subclass of a subclass) of Net::HTTPResponse and you can use response.is_a? Net::HTTPSuccess to check for a 2xx response. The actual body of the response will be in response.body as a String.
Is there a way to check for an HTTPS status code in ruby? I know that there are ways to do this in HTTP using require 'net/http', but I'm looking for HTTPS. Maybe there is a different library that I need to use?
You can do this in net/http:
require "net/https"
require "uri"
uri = URI.parse("https://www.secure.com/")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Get.new(uri.request_uri)
res = http.request(request)
res.code #=> "200"
Refs:
Net::HTTP cheat sheet
How to Cure Net::HTTP’s Risky Default HTTPS Behavior
You can use any wrapper around Net::HTTP(S) to get much easier behavior.
I use Faraday here ( https://github.com/lostisland/faraday ) but HTTParty has almost the same functionality ( https://github.com/jnunemaker/httparty )
require 'faraday'
res = Faraday.get("https://www.example.com/")
res.status # => 200
res = Faraday.get("http://www.example.com/")
res.status # => 200
(as a bonus you get options for parsing responses, raising state exceptions, logging requests....
connection = Faraday.new("https://www.example.com/") do |conn|
# url-encode the body if given as a hash
conn.request :url_encoded
# add an authorization header
conn.request :oauth2, 'TOKEN'
# use JSON to convert the response into a hash
conn.response :json, :content_type => /\bjson$/
# ...
conn.adapter Faraday.default_adapter
end
connection.get("/")
# GET https://www.example.com/some/path?query=string
connection.get("/some/path", :query => "string")
# POST, PUT, DELETE, PATCH....
connection.post("/some/other/path", :these => "fields", :will => "be converted to a request string in the body"}
# add any number of headers. in this example "Accept-Language: en-US"
connection.get("/some/path", nil, :accept_language => "en-US")
require 'uri'
require 'net/http'
res = Net::HTTP.get_response(URI('http://www.example.com/index.html'))
puts res.code # -> '200'
Slightly more readable way:
response.kind_of?(Net::HTTPOK)