I have make a soap-call with Savon. This works fine and give the
following response:
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://
schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<GetTop10Response xmlns="http://www.kirupafx.com">
<GetTop10Result>
<string>string</string>
<string>string</string>
</GetTop10Result>
</GetTop10Response>
</soap:Body>
</soap:Envelope>
Now I want to take all of the string elements out of the response. But
I can't get it to work.
def query(params=nil)
client = Savon::Client.new do
wsdl.document = "http://www.kirupafx.com/WebService/TopMovies.asmx?wsdl"
end
response = client.request :get_top10
if response.success?
xml = Nokogiri::XML(response.to_xml)
print "Until here oké!"
xml.search('//GetTop10Result').each do |result|
print "How are you Ruby?"
#result[result.at('string').inner_text] = result.at('string').inner_text
end
else
raise "Error!"
end
But he never prints my beautiful "How are you Ruby?" Can somebody help
me? What I'm doing wrong?
You could to this but this isnt the best way to deal with problems like this! You might have experience using Nokogiri and XML but its easier to use .to_hash like this.
def query
client = Savon::Client.new do
wsdl.document = "http://www.kirupafx.com/WebService/TopMovies.asmx?wsdl"
end
response = client.request(:get_top10)
response.to_hash[:get_top10_response][:get_top10_result] if response.success?
false
end
Thanks for both reactions! I figured out. Here is my code:
# Prepare SOAP-request
client = Savon::Client.new do
wsdl.document = "http://www.kirupafx.com/WebService/TopMovies.asmx?wsdl"
end
# Execute SOAP-request
response = client.request :get_top10
if response.success?
names = Array.new(10)
index = 0
hash = response.to_hash[:get_top10_response][:get_top10_result][:string]
hash.each do |value|
names[index] = value
index += 1
end
#result = {
"0"=>{"name"=>"#{names.at(0)}"},
"1"=>{"name"=>"#{names.at(1)}"},
"2"=>{"name"=>"#{names.at(2)}"},
"3"=>{"name"=>"#{names.at(3)}"},
"4"=>{"name"=>"#{names.at(4)}"},
"5"=>{"name"=>"#{names.at(5)}"},
"6"=>{"name"=>"#{names.at(6)}"},
"7"=>{"name"=>"#{names.at(7)}"},
"8"=>{"name"=>"#{names.at(8)}"},
"9"=>{"name"=>"#{names.at(9)}"}
}
else
raise "Error occurred during the request to the top 10 movies!"
end
Related
As a quick background: I've already successfully set up a small application that will connect to Yahoo's API and set my lineups on a daily basis using PUT requests on my team's roster per the Yahoo Developer's Guide.
Specifically, where I'm having problems now:
POST
Using POST, players can be added and/or dropped from a team, or trades
can be proposed. The URI for POSTing to transactions collection is:
http://fantasysports.yahooapis.com/fantasy/v2/league//transactions
The input XML format for a POST request to the transactions API for
replacing one player with another player in a team is:
<fantasy_content>
<transaction>
<type>add/drop</type>
<players>
<player>
<player_key>{player_key}</player_key>
<transaction_data>
<type>add</type>
<destination_team_key>{team_key}</destination_team_key>
</transaction_data>
</player>
<player>
<player_key>{player_key}</player_key>
<transaction_data>
<type>drop</type>
<source_team_key>{team_key}</source_team_key>
</transaction_data>
</player>
</players>
</transaction>
</fantasy_content>
My method for making a request is as follows:
def self.make_signed_request(address, method, user_id, input_file=nil )
# format http method and return false if not accepted API method
method = method.upcase
return false if ( method != "GET" ) && ( method != "POST" ) && ( method != "PUT")
# find user
user = User.find(user_id)
if ( user.yahoo_access_token_expiration.nil? || user.yahoo_access_token_expiration < Time.now )
# expired token, so renew
YahooOAuth.renew_token(user_id)
user.reload
end
# normalize text HMAC-SHA1
digest = OpenSSL::Digest.new('sha1')
nonce = rand(10 ** 30).to_s.rjust(30,'0')
timestamp = Time.now.to_i
text = method + "&" + CGI.escape("#{address}") + "&" + CGI.escape("oauth_consumer_key=#{YAHOO_OAUTH_API[:CLIENT_ID]}&oauth_nonce=#{nonce}&oauth_signature_method=HMAC-SHA1&oauth_timestamp=#{timestamp}&oauth_token=#{CGI.escape(user.yahoo_access_token)}&oauth_version=1.0")
# create key for HMAC-SHA1
key = CGI.escape("#{YAHOO_OAUTH_API[:CLIENT_SECRET]}")+ "&" + CGI.escape("#{user.yahoo_access_token_secret}")
# create HMAC-SHA1 signature for api request
hmac = OpenSSL::HMAC.digest(digest, key, text)
signature_sha1 = CGI.escape(Base64.strict_encode64(hmac))
# make API request
response = nil
if method == "GET"
response = Curl.get("#{address}?oauth_consumer_key=#{YAHOO_OAUTH_API[:CLIENT_ID]}&oauth_nonce=#{nonce}&oauth_signature_method=HMAC-SHA1&oauth_timestamp=#{timestamp}&oauth_token=#{CGI.escape(user.yahoo_access_token)}&oauth_version=1.0&oauth_signature=#{signature_sha1}")
elsif method == "POST"
# return "Not implemented"
response = Curl.post("#{address}?oauth_consumer_key=#{YAHOO_OAUTH_API[:CLIENT_ID]}&oauth_nonce=#{nonce}&oauth_signature_method=HMAC-SHA1&oauth_timestamp=#{timestamp}&oauth_token=#{CGI.escape(user.yahoo_access_token)}&oauth_version=1.0&oauth_signature=#{signature_sha1}", input_file) do |http|
http.headers['Accept'] = 'application/xml'
http.headers['Content-Type'] = 'application/xml'
end
elsif method == "PUT"
response = Curl.put("#{address}?oauth_consumer_key=#{YAHOO_OAUTH_API[:CLIENT_ID]}&oauth_nonce=#{nonce}&oauth_signature_method=HMAC-SHA1&oauth_timestamp=#{timestamp}&oauth_token=#{CGI.escape(user.yahoo_access_token)}&oauth_version=1.0&oauth_signature=#{signature_sha1}", input_file) do |http|
http.headers['Accept'] = 'application/xml'
http.headers['Content-Type'] = 'application/xml'
end
end
# return raw XML result
return response.body
end
When I make my request --
def add_drop(players)
# players are added in [{player_key}, {add/drop}, {faab_bid (or nil if not FAAB)}] form
url = "http://fantasysports.yahooapis.com/fantasy/v2/league/#{self.league.league_key}/transactions"
builder = Nokogiri::XML::Builder.new(:encoding => 'UTF-8') do |xml|
xml.fantasy_content {
xml.transaction {
xml.type "add/drop"
xml.faab_bid players[0][2] unless players[0][2].nil?
xml.players {
players.each do |player|
xml.player {
xml.player_key player[0]
xml.transaction_data {
xml.type player[1] # this will be "add" or "drop"
# adds use "destination_team_key," drops use "source_team_key"
if player[1] == "add"
xml.destination_team_key self.team_key
else
xml.source_team_key self.team_key
end
} # transaction_data
} # player
end
} # players
} # transaction
} # fantasy_content
end # builder
xml_input = builder.to_xml
YahooOAuth.make_signed_request(url, 'put', self.user.id, xml_input)
end
--the XML generated is shown below --
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<fantasy_content>\n <transaction>\n <type>add/drop</type>\n <players>\n <player>\n <player_key>357.p.10171</player_key>\n <transaction_data>\n <type>add</type>\n <destination_team_key>370.l.4801.t.7</destination_team_key>\n </transaction_data>\n </player>\n <player>\n <player_key>357.p.9317</player_key>\n <transaction_data>\n <type>drop</type>\n <source_team_key>370.l.4801.t.7</source_team_key>\n </transaction_data>\n </player>\n </players>\n </transaction>\n</fantasy_content>\n"
-- and the two responses I'll get from Yahoo are:
> <?xml version="1.0" encoding="UTF-8"?> <error xml:lang="en-us"
> yahoo:uri="http://fantasysports.yahooapis.com/fantasy/v2/league/370.l.4801/transactions/?oauth_consumer_key=dj0yJmk9dHBIa0h4ekhTSVBnJmQ9WVdrOVlUZHFkMnhMTXpJbWNHbzlNQS0tJnM9Y29uc3VtZXJzZWNyZXQmeD1lNQ--&oauth_nonce=892057444475304460343340318332&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1492097395&oauth_token=A%3D2.86iTDxtT4nQ.wxTcn33mgnA8dm3AeME87YRjNthVjxiwfhqKr_oWt0HBgbBeS2DC_dNObN72m0ucgi7CsSFaRjpc5IcnZ_bpJNTC3pUXc37bgH8f0S4irpyXLz8s9ONAYYB.LIDT0sOUvdTgk_lPDnlr1GlPhCe7u54Sq.m_vwq1JQlNUzEVrEs3kOW9wiXk17BditA9OGaVE.tuepvpthDRVBhOye8qjb_ic7UZtT.lvccoGEdgvcShHSyg.YYcnShl7ks23G01hAcXrfGveEk0UncWKNmma42cYbg7bzSOY9ZZj3_hvU5rK3SjB1ADPe8bsIEe_Ba9KBhYxlWd9iyyAR_bloL9n0eeL_OQ6PoR4uGJ6VMUDn9n_ovDGvfgAfvtJs15pCcXPhYusuo1S7SJF1O3fLtR8TitmU1qW88x3SenY2U50dlRG9Y73iNUdnyYBpIHLg._pPkms26QhnuxSFfqpXcGleGXOuZ0.TNOE3Cp8VbLEOEIg6QkavgGLKyFetYhSQ879T4rfhfeEoWvwkjsO1BL2Y3n4Hp9cgfU4y3wZvT.b8qhP7QY0UTYtZkyYH.sydFUXUCec.yVGm29S.s.2N96tfr4qWaI0qntRE.X5MVdwfbhz94n9JshmduqurjKRLlMYVWnLZ_Yderm0HDvT7dnowjyUwBx2UxUKJooauQnNU67QQECmh.HZqcm_OBysLABvdtTtaPhnvJ1QViN_UUjslToVPOs1oyxoTNRbL0VL8yxJc&oauth_version=1.0&oauth_signature=UVmcj2S8c5vqkRgAxsdQ3MQZI54%3D"
> xmlns:yahoo="http://www.yahooapis.com/v1/base.rng"
> xmlns="http://www.yahooapis.com/v1/base.rng">
> <description>subresource 'transactions' not supported.</description>
> <detail/> </error>
and
> <?xml version="1.0" encoding="UTF-8"?> <error xml:lang="en-us"
> yahoo:uri="http://fantasysports.yahooapis.com/fantasy/v2/league/370.l.4801/transactions?oauth_consumer_key=dj0yJmk9dHBIa0h4ekhTSVBnJmQ9WVdrOVlUZHFkMnhMTXpJbWNHbzlNQS0tJnM9Y29uc3VtZXJzZWNyZXQmeD1lNQ--&oauth_nonce=503128074504907304111221170641&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1492097198&oauth_token=A%3D2.86iTDxtT4nQ.wxTcn33mgnA8dm3AeME87YRjNthVjxiwfhqKr_oWt0HBgbBeS2DC_dNObN72m0ucgi7CsSFaRjpc5IcnZ_bpJNTC3pUXc37bgH8f0S4irpyXLz8s9ONAYYB.LIDT0sOUvdTgk_lPDnlr1GlPhCe7u54Sq.m_vwq1JQlNUzEVrEs3kOW9wiXk17BditA9OGaVE.tuepvpthDRVBhOye8qjb_ic7UZtT.lvccoGEdgvcShHSyg.YYcnShl7ks23G01hAcXrfGveEk0UncWKNmma42cYbg7bzSOY9ZZj3_hvU5rK3SjB1ADPe8bsIEe_Ba9KBhYxlWd9iyyAR_bloL9n0eeL_OQ6PoR4uGJ6VMUDn9n_ovDGvfgAfvtJs15pCcXPhYusuo1S7SJF1O3fLtR8TitmU1qW88x3SenY2U50dlRG9Y73iNUdnyYBpIHLg._pPkms26QhnuxSFfqpXcGleGXOuZ0.TNOE3Cp8VbLEOEIg6QkavgGLKyFetYhSQ879T4rfhfeEoWvwkjsO1BL2Y3n4Hp9cgfU4y3wZvT.b8qhP7QY0UTYtZkyYH.sydFUXUCec.yVGm29S.s.2N96tfr4qWaI0qntRE.X5MVdwfbhz94n9JshmduqurjKRLlMYVWnLZ_Yderm0HDvT7dnowjyUwBx2UxUKJooauQnNU67QQECmh.HZqcm_OBysLABvdtTtaPhnvJ1QViN_UUjslToVPOs1oyxoTNRbL0VL8yxJc&oauth_version=1.0&oauth_signature=oNaLute5djkIryUEKq0zF6prVFU%3D"
> xmlns:yahoo="http://www.yahooapis.com/v1/base.rng"
> xmlns="http://www.yahooapis.com/v1/base.rng">
> <description>Invalid XML POSTed. Error code 25 at line 3, column 0 of input XML.</description>
> <detail/> </error>
I'm honestly not sure what differentiates between the two responses, as I've gotten both responses with what I'm pretty sure are the same XML inputs and POST parameters.
Does anybody have any insight?
Just ran into the same issue, and this Stack Overflow article was the only relevant link I found on the web. Decided it was time I gave back to this community...
The problem is that one of the sample XMLs on Yahoo's transaction page is wrong. The sample with the { } placeholders and without the <faab_bid> node is correct, but the FAAB example below it has <destination_team_key> instead of <source_team_key> as part of the "drop" transaction. When I made sure to use "source_team_key" instead of "destination_team_key" for the drop set of nodes, the transaction started working.
I'm guessing that Error Code 25 is a generic error indicating that some part of the XML is wrong or unexpected. If this doesn't resolve your issue, try starting with the first add/drop sample XML and filling in values one by one.
Hope this helps.
-Igor
I have to call a webservice but I don't know the format of the response. In any case (xml, json or html) I have to pretty print the response.
For example, if it is a xml I have to indent and show it properly. Same thing if it is a json. I have two problems here:
Detecting the format
Apply a format depending on the type.
I think that (1) is the most challenging problem.
Any help?
As several of the comments have suggested, the http header will contain the content type.
net/http has methods for this: http://ruby-doc.org/stdlib-2.0.0/libdoc/net/http/rdoc/Net/HTTP.html#method-i-head
require 'net/http'
require 'json'
require 'rexml/document'
response = nil
Net::HTTP.start('www.google.com', 80) {|http|
response = http.get('/index.html')
}
header = response['content-type'].split(';').first # => "text/html"
body = response.read_body
then you can conditionally operate:
if header == "text/html"
puts response.read_body
elsif header == "application/json"
puts JSON.pretty_generate(JSON.parse(body))
elsif header == "text/xml"
xml = REXML::Document.new body
out = ""
xml.write(out, 1)
puts out
end
Most of this was pulled form other SO posts:
pretty JSON: How can I "pretty" format my JSON output in Ruby on Rails?
pretty XML: How to beautify xml code in rails application
This is the code that I finally used:
raw_response = response.body
response_html = ''
if response.header['Content-Type'].include? 'application/json'
tokens = CodeRay.scan(raw_response, :json)
response_html = tokens.div
elsif response.header['Content-Type'].include? 'application/xml'
tokens = CodeRay.scan(raw_response, :xml)
response_html = tokens.div
elsif response.header['Content-Type'].include? 'text/html'
tokens = CodeRay.scan(raw_response, :html)
response_html = tokens.div
else
response_html = '<div>' + raw_response + '</div>'
end
It's using the coderay gem.
I am having a hell of a problem here.
I'm using ruby on rails:
ruby 1.8.7 (2011-12-10 patchlevel 356)
rails 2.3.14
I'm trying a simple open with open-uri on the following address:
http://jollymag.net/n/10390-летни-секс-пози-във-водата.html (link is NSFW)
However the resulting file when read produces a weird (broken) string.
This was tested on ruby 1.9.3 and rails 3.2.x too.
require 'open-uri'
url = 'http://jollymag.net/n/10390-летни-секс-пози-във-водата.html'
url = URI.encode(url)
file = open(url)
doc = file.collect.to_s # <- the document is broken
document = Nokogiri::HTML.parse(doc,nil,"utf8")
puts document # <- the document after nokogiri has one line of content
I tried Iconv stuff and others but nothing works. The above code is more or less a minimal isolated case for the exact problem.
I appreciate any help since I'm trying to figure this bug for a couple of days now.
Regards,
Yavor
So the problem was a tricky one for me.
It appears that some servers return only gzip-ed response.
So in order to read you of course have to read it accordingly.
I decided to post my whole crawl code so people might find a more complete solutions to such problems. This is part of a bigger class so it refers a lot of the times to self.
Hope it helps!
SHINSO_HEADERS = {
'Accept' => '*/*',
'Accept-Charset' => 'utf-8, windows-1251;q=0.7, *;q=0.6',
'Accept-Encoding' => 'gzip,deflate',
'Accept-Language' => 'bg-BG, bg;q=0.8, en;q=0.7, *;q=0.6',
'Connection' => 'keep-alive',
'From' => 'support#xenium.bg',
'Referer' => 'http://svejo.net/',
'User-Agent' => 'Mozilla/5.0 (compatible; Shinso/1.0;'
}
def crawl(url_address)
self.errors = Array.new
begin
begin
url_address = URI.parse(url_address)
rescue URI::InvalidURIError
url_address = URI.decode(url_address)
url_address = URI.encode(url_address)
url_address = URI.parse(url_address)
end
url_address.normalize!
stream = ""
timeout(10) { stream = url_address.open(SHINSO_HEADERS) }
if stream.size > 0
url_crawled = URI.parse(stream.base_uri.to_s)
else
self.errors << "Server said status 200 OK but document file is zero bytes."
return
end
rescue Exception => exception
self.errors << exception
return
end
# extract information before html parsing
self.url_posted = url_address.to_s
self.url_parsed = url_crawled.to_s
self.url_host = url_crawled.host
self.status = stream.status
self.content_type = stream.content_type
self.content_encoding = stream.content_encoding
self.charset = stream.charset
if stream.content_encoding.include?('gzip')
document = Zlib::GzipReader.new(stream).read
elsif stream.content_encoding.include?('deflate')
document = Zlib::Deflate.new().deflate(stream).read
#elsif stream.content_encoding.include?('x-gzip') or
#elsif stream.content_encoding.include?('compress')
else
document = stream.read
end
self.charset_guess = CharGuess.guess(document)
if not self.charset_guess.blank? or
not self.charset_guess == 'utf-8' or
not self.charset_guess == 'utf8'
document = Iconv.iconv("UTF-8", self.charset_guess , document).to_s
end
document = Nokogiri::HTML.parse(document,nil,"utf8")
document.xpath('//script').remove
document.xpath('//SCRIPT').remove
for item in document.xpath('//*[translate(#src, "ABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyz")]')
item.set_attribute('src',make_absolute_address(item['src']))
end
document = document.to_s.gsub(/<!--(.|\s)*?-->/,'')
#document = document.to_s.gsub(/\<![ \r\n\t]*(--([^\-]|[\r\n]|-[^\-])*--[ \r\n\t]*)\>/,'')
self.content = Nokogiri::HTML.parse(document,nil,"utf8")
end
I've been looking around a bit for how to get the time off of ebay..
I don't want to use SAVON because... well it didn't work..
So I'm trying to use net/http, just to get the time. (for now)
Here's what I got so far.
def get_ebay_time
require "net/http"
require "uri"
devName = 000000000
appName = 000000000
certName = 000000000
authToken = 0000000000
url = URI.parse("https://api.ebay.com/ws/api.dll")
req = Net::HTTP::Post.new(url.path)
req.add_field("X-EBAY-API-COMPATIBILITY-LEVEL", "759")
req.add_field("X-EBAY-API-DEV-NAME", devName)
req.add_field("X-EBAY-API-APP-NAME", appName)
req.add_field("X-EBAY-API-CERT-NAME", certName)
req.add_field("X-EBAY-API-SITEID", "0")
req.add_field("X-EBAY-API-CALL-NAME", "GeteBayOfficialTime")
req.body = '<?xml version="1.0" encoding="utf-8"?>'+
'<GeteBayOfficialTimeRequest xmlns="urn:ebay:apis:eBLBaseComponents">'+
'<RequesterCredentials>'+
"<eBayAuthToken>#{authToken}</eBayAuthToken>"+
'</RequesterCredentials>'+
'</GeteBayOfficialTimeRequest>?'
http = Net::HTTP.new(url.host, url.port)
res = http.start do |http_runner|
http_runner.request(req)
end
return res.body
end
APIs wrappers are developed to help :)
Please use eBay4r and same on github: up_the_irons/ebay4r
require 'rubygems'
gem 'ebay'
# Put your credentials in this file
load('myCredentials.rb')
# Create new eBay caller object. Omit last argument to use live platform.
eBay = EBay::API.new($authToken, $devId, $appId, $certId, :sandbox => true)
resp = eBay.GeteBayOfficialTime
puts "Hello, World!"
puts "The eBay time is now: #{resp.timestamp}"
it didn't take me as long to find this as I thought.
in the bottom bit, I added SSL handling
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
http.verify_mode = 0
res = http.start do |http_runner|
http_runner.request(req)
end
return res.body
I'm trying to make a request to a web service (fwix), and in my rails app I've created the following initializer, which works... sorta, I have two problems however:
For some reason the values of the parameters need to have +'s as the spaces, is this a standard thing that I can accomplish with ruby? Additionally is this a standard way to form a url? I thought that spaces were %20.
In my code how can I take any of the options sent in and just use them instead of having to state each one like query_items << "api_key=#{options[:api_key]}" if options[:api_key]
The following is my code, the trouble area I'm having are the lines starting with query_items for each parameter in the last method, any ideas would be awesome!
require 'httparty'
module Fwix
class API
include HTTParty
class JSONParser < HTTParty::Parser
def json
JSON.parse(body)
end
end
parser JSONParser
base_uri "http://geoapi.fwix.com"
def self.query(options = {})
begin
query_url = query_url(options)
puts "querying: #{base_uri}#{query_url}"
response = get( query_url )
rescue
raise "Connection to Fwix API failed" if response.nil?
end
end
def self.query_url(input_options = {})
#defaults ||= {
:api_key => "my_api_key",
}
options = #defaults.merge(input_options)
query_url = "/content.json?"
query_items = []
query_items << "api_key=#{options[:api_key]}" if options[:api_key]
query_items << "province=#{options[:province]}" if options[:province]
query_items << "city=#{options[:city]}" if options[:city]
query_items << "address=#{options[:address]}" if options[:address]
query_url += query_items.join('&')
query_url
end
end
end
For 1)
You API provider is expecting '+' because the API is expecting in a CGI formatted string instead of URL formatted string.
require 'cgi'
my_query = "hel lo"
CGI.escape(my_query)
this should give you
"hel+lo"
as you expect
for Question 2) I would do something like
query_items = options.keys.collect { |key| "#{key.to_s}=#{options[key]}" }
def self.query_url(input_options = {})
options = {
:api_key => "my_api_key",
}.merge(input_options)
query_url = "/content.json?"
query_items = []
options.each { |k, v| query_items << "#{k}=#{v.gsub(/\s/, '+')}" }
query_url += query_items.join('&')
end
I'm a developer at Fwix and wanted to help you with your url escaping issue. However, escaping with %20 works for me:
wget 'http://geoapi.fwix.com/content.xml?api_key=mark&province=ca&city=san%20francisco&query=gavin%20newsom'
I was hoping you could provide me with the specific request you're making that you're unable to escape with %20.