I am attempting to learn Ruby/Faraday. I need to POST XML to a RESTful web service and am confused on how to do this.
I have a string containing the XML as follows:
require "faraday"
require "faraday_middleware"
mystring = %&<xml><auth><user userid='username' pwd='password'/></auth></xml>&
How do I post the XML to a URL and receive the result? I am trying to do something like:
conn = Faraday.new(:url=>'http://url')
conn.post '/logon' {mystring}
I get the message:
SyntaxError: (irb):11: syntax error, unexpected '{', expecting $end
conn.post '/logon' {mystring}
Edit 1
I have gotten the POST request to work. My code is provided below.
require "faraday"
require "faraday_middleware"
myString = %&<xml><auth><user userid='username' pwd='password'/></auth></xml>&
myUrl = %&url&
conn = Faraday.new(:url => myUrl) do |builder|
builder.response :logger #logging stuff
builder.use Faraday::Adapter::NetHttp #default adapter for Net::HTTP
end
res = conn.post do |request|
request.url myUrl
request.body = myString
end
puts res.body
According to the documentation:
conn = Faraday.new(:url=>'http://url')
conn.post '/logon', mystring
There are two errors in your code. The first one is that you are missing a comma between the URL and the variable causing { mystring } to be interpreted as a block.
The second error is that mystring already holds a string and the following code does not make sense in Ruby:
{ "string" }
Thus conn.post '/logon', mystring is wrong. So the final result is:
conn = Faraday.new(:url=>'http://url')
conn.post '/logon', mystring
or:
conn = Faraday.new(:url=>'http://url')
conn.post '/logon', { :key => mystring }
if you want to submit a key/value POST body. But this is not your case, because you are already posting an XML body.
Related
I'm trying to set up a web hook, following this GitHub tutorial
require 'sinatra'
require 'json'
require 'net/http'
require 'pp'
set :port, 31415
# Descarga las diferencias hechas para un push
post '/' do
push = JSON.parse(request.body.read)
piezas = push["compare"].split("/")
api_url = "/repos/#{piezas[3]}/#{piezas[4]}/compare/#{piezas[6]}"
diff = Net::HTTP.get(URI("https://api.github.com#{api_url}"))
puts diff.class
pp(JSON.parse(diff))
end
diff.class prints:
String
And, as a matter of fact, the last sentence works correctly, printing via pp the structure. However, after printing, it yields the error
[2018-10-25 20:00:23] ERROR TypeError: no implicit conversion of Array into String
It's not referencing any line in the script, but would it be possible that the error would be in the first JSON.parse? Could it be that request.body.read would be an array?
Update I couldn't golf it down to any of the JSON.parse separately. Downloading the hook payload works OK, downloading the JSON from the GitHub API works without a glithc. Somehow it's using them together what does not work.
It's possible the library is treating the response like text. Try adding an Accept header. This worked for me:
request["Accept"] = "application/json"
example:
uri = URI.parse("https://api.github.com")
req = Net::HTTP::Get.new(URI("https://api.github.com/repos/JJ/microservices-broker/compare/d5d39c5db99d...bbbf695d1bf2"))
req["Accept"] = 'application/json'
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
response = http.request(req)
json = JSON.parse(response.body)
json['url']
# or
json = JSON.parse(response.body, symbolize_names: true)
json[:url]
(EDIT:) Also, Using Net::HTTP is really painful. Please checkout these libraries:
https://github.com/lostisland/faraday
https://github.com/octokit/octokit.rb
I have making a post request in Ruby to a slack endpoint and its failing, here is my request, not sure what I'm missing:
#!/usr/bin/env ruby
#Notification Script Test
def send_slack_message
slack_rooms = [ '#test_channel_notify' ]
slack_token_file = (File.join(ENV['HOME'], '.slack_api_token'))
slack_api_token = (File.open(#slack_token_file).readlines)[0].chomp
msg = 'This is a test message send'
slack_url = "https://slack.com/api/chat.postMessage"
%x{curl -k -X POST -d"token=#{slack_api_token}\&channel=#{slack_rooms}\&text=#{msg}" '#{slack_url}'}
end
send_slack_message
I am getting the following error, not sure what I'm missing:
./cap2.rb:7:in `initialize': no implicit conversion of nil into String (TypeError)
from ./cap2.rb:7:in `open'
from ./cap2.rb:7:in `send_slack_message'
from ./cap2.rb:13:in `<main>'
I am a ruby novice so I may be missing everything would love some help!
The error says that you can't give nil to File.open. Make sure #slack_token_file exists and is not nil.
The slack API wants to receive the payload in this format: 'payload={"json": "data"}'
Using Net::HTTP you can make a POST request like this:
require 'net/http'
require 'uri'
def payload(message, channel)
{ channel: channel, username: 'your-username', text: message, icon_emoji: ':robot_face:' }
end
msg = 'This is a test message send'
body = payload(msg, '#test_channel_notify').to_json
url = URI("https://slack.com/api/chat.postMessage")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
request = Net::HTTP::Post.new(url)
request.body = "payload=#{body}"
http.request(request)
While your original problem has been solved, I will provide an alternative solution which follows the more common practice of storing secret credentials as environment variables, not as files (which can, for example, more easily exfiltrated by accident, committed to source control, pasted to a presentation, etc).
Set SLACK_TOKEN in your environment, and then use:
#!/usr/bin/env ruby
#Notification Script Test
def send_slack_message
slack_rooms = [ '#test_channel_notify' ]
slack_api_token = ENV['SLACK_TOKEN']
msg = 'This is a test message send'
slack_url = "https://slack.com/api/chat.postMessage"
%x{curl -k -X POST -d"token=#{slack_api_token}\&channel=#{slack_rooms}\&text=#{msg}" '#{slack_url}'}
end
send_slack_message
The JSON object I'm parsing is at http://api.4chan.org/3/catalog.json
Here is my Ruby code:
['open-uri','nokogiri','json'].each{|g| require g}
json_test = File.open('json_test.JSON','r').read
board_cat_body = Nokogiri::HTML(open('http://api.4chan.org/3/catalog.json'))
puts JSON.parse(board_cat_body)
Result (it's very long so I took a part of it out):
C:/Ruby193/lib/ruby/1.9.1/json/common.rb:148:in `parse': 387: unexpected token at '{"no":248019,"sticky":1,"closed":1,"now":"12\/19....
However, if I copy and paste the contents of http://api.4chan.org/3/catalog.json into a local JSON file and parse from that local JSON file, there is no problem.
Does anyone know what I'm doing wrong?
Remove the Nokogiri call. JSON isn't HTML.
['open-uri','json'].each{|g| require g}
json = JSON.parse(open('http://api.4chan.org/3/catalog.json').read)
puts json.inspect
The document you get in board_cat_body is not a JSON doc, it's HTML, as you can see if you print it. So, I propose to download the document this way:
require 'net/http'
require 'json'
url = URI.parse('http://api.4chan.org/3/catalog.json')
req = Net::HTTP::Get.new(url.path)
res = Net::HTTP.start(url.host, url.port) { |http| http.request(req) }
and parse it:
puts JSON.parse(res.body)
How can I send HTTP GET request with parameters via ruby?
I have tried a lot of examples but all of those failed.
I know this post is old but for the sake of those brought here by google, there is an easier way to encode your parameters in a URL safe manner. I'm not sure why I haven't seen this elsewhere as the method is documented on the Net::HTTP page. I have seen the method described by Arsen7 as the accepted answer on several other questions also.
Mentioned in the Net::HTTP documentation is URI.encode_www_form(params):
# Lets say we have a path and params that look like this:
path = "/search"
params = {q: => "answer"}
# Example 1: Replacing the #path_with_params method from Arsen7
def path_with_params(path, params)
encoded_params = URI.encode_www_form(params)
[path, encoded_params].join("?")
end
# Example 2: A shortcut for the entire example by Arsen7
uri = URI.parse("http://localhost.com" + path)
uri.query = URI.encode_www_form(params)
response = Net::HTTP.get_response(uri)
Which example you choose is very much dependent on your use case. In my current project I am using a method similar to the one recommended by Arsen7 along with the simpler #path_with_params method and without the block format.
# Simplified example implementation without response
# decoding or error handling.
require "net/http"
require "uri"
class Connection
VERB_MAP = {
:get => Net::HTTP::Get,
:post => Net::HTTP::Post,
:put => Net::HTTP::Put,
:delete => Net::HTTP::Delete
}
API_ENDPOINT = "http://dev.random.com"
attr_reader :http
def initialize(endpoint = API_ENDPOINT)
uri = URI.parse(endpoint)
#http = Net::HTTP.new(uri.host, uri.port)
end
def request(method, path, params)
case method
when :get
full_path = path_with_params(path, params)
request = VERB_MAP[method].new(full_path)
else
request = VERB_MAP[method].new(path)
request.set_form_data(params)
end
http.request(request)
end
private
def path_with_params(path, params)
encoded_params = URI.encode_www_form(params)
[path, encoded_params].join("?")
end
end
con = Connection.new
con.request(:post, "/account", {:email => "test#test.com"})
=> #<Net::HTTPCreated 201 Created readbody=true>
I assume that you understand the examples on the Net::HTTP documentation page but you do not know how to pass parameters to the GET request.
You just append the parameters to the requested address, in exactly the same way you type such address in the browser:
require 'net/http'
res = Net::HTTP.start('localhost', 3000) do |http|
http.get('/users?id=1')
end
puts res.body
If you need some generic way to build the parameters string from a hash, you may create a helper like this:
require 'cgi'
def path_with_params(page, params)
return page if params.empty?
page + "?" + params.map {|k,v| CGI.escape(k.to_s)+'='+CGI.escape(v.to_s) }.join("&")
end
path_with_params("/users", :id => 1, :name => "John&Sons")
# => "/users?name=John%26Sons&id=1"
I am having trouble parsing a SOAP response.
Here is my code:
require 'rubygems'
require 'savon'
client = Savon::Client.new "http://1.2.3.4/xyz/abcd/fsds.wsdl"
res = client.query_unpaid_assesments do |soap,wsse|
soap.namespaces["xmlns:SOAP-ENV"] = "http://schemas.xmlsoap.org/soap/envelope/"
soap.namespaces["xmlns:xsi"] = "http://www.w3.org/2001/XMLSchema-instance"
soap.namespaces["xmlns:xsd"] = "http://www.w3.org/2001/XMLSchema"
wsse.username="xyz"
wsse.password="123"
soap.body = {:orderNumber => 111222333 }
end
response = Savon::Response#to_hash
hres = response.to_hash
all_data = hres[:response][:asses_data][:date][:amount][:assesReference][:year][:cusOffCode][:serie][:number][:date][:time]
Here is the error that I am having:
undefined method to_hash for Savon::Response:Class (NoMethodError)
"res" is giving me xml response that I would like to have in hash.
I read previous related questions and they recommending to use response.to_hash , which I did and is throwing the error specified above.
How can i get rid of this error and have my response into hash.
thanks for ur help
I forgot to post the body of the xml response that I would like to parse:
<soapenv:Body>
<response>
<ns203:assesData xmlns:ns203="http://asdfsd.sdfsd.zbc.org">
<ns203:date>2010-09-01</ns203:date>
<ns203:amount>34400</ns203:amount>
<ns203:asesReference>
<ns203:year>2010</ns203:year>
<ns203:cusOffCode>098</ns203:customsOfficeCode>
<ns203:serie>F</ns203:serie>
<ns203:number>524332</ns203:number>
<ns203:date>2010-11-11</ns203:date>
<ns203:time>10:11:103</ns203:time>
</ns203:assesReference>
</ns203:assesData>
</response>
</soapenv:Body>
I believe you need to be trying to #to_hash res itself, the returned Savon::Response object, instead of the Savon::Response class.
So hres = res.to_hash should work.
An example I found (at the end of here: http://blog.nofail.de/2010/01/savon-vs-handsoap-calling-a-service/) should give you the idea.
class SavonBankCode
def self.zip_code(bank_code)
client = Savon::Client.new Shootout.endpoints[:bank_code][:uri]
response = client.get_bank { |soap| soap.body = { "wsdl:blz" => bank_code } }
response.to_hash[:get_bank_response][:details][:plz]
end
end
An alternative would be to parse the result with Nokogiri or similar, meaning you could do something like this:
require 'nokogiri'
response = res.xpath("//ns203:assesData", "ns203" => "http://asdfsd.sdfsd.zbc.org")
date = response.xpath("ns203:date", "ns203" => "http://asdfsd.sdfsd.zbc.org")
amount = response.xpath("ns203:amount", "ns203" => "http://asdfsd.sdfsd.zbc.org")
number = response.xpath("ns203:asesReference/ns203:number", "ns203" => "http://asdfsd.sdfsd.zbc.org")
etc. etc. Ugly as sin of course, but hey it is an (untested or refined) alternative ;)
Good luck!
You can also try response.body.
It returns a hash