Using Savon and nokogiri to make a Soap Request - ruby-on-rails

A sample request that I am trying to make looks like this:
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<GetList xmlns="http://tempuri.org/">
<listRequest i:type="b:NpsListRequest" xmlns:a="http://schemas.datacontract.org/2004/07/Services.List" xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns:b="http://schemas.datacontract.org/2004/07/Services.List.Strategies">
<a:id>1</a:id>
</listRequest>
</GetList>
</s:Body>
</s:Envelope>
I can replicate that exactly, using Nokogiri, like so:
builder = Nokogiri::XML::Builder.new do |xml|
xml['s'].Envelope('xmlns:s' => "http://schemas.xmlsoap.org/soap/envelope/") {
xml['s'].Body {
xml.GetList('xmlns' => "http://tempuri.org/") {
xml.listRequest('i:type' => "b:NpsListRequest", 'xmlns:a' => "http://schemas.datacontract.org/2004/07/Services.List", 'xmlns:i' => "http://www.w3.org/2001/XMLSchema-instance", 'xmlns:b' => "http://schemas.datacontract.org/2004/07/Services.List.Strategies") {
xml['a'].id(1)
}
}
}
}
end
builder.to_xml
Then, I build my soap_client using Savon:
def soap_client
soap_client = Savon.client(
wsdl: "http://1.1.1.1/Api.svc?wsdl", #PLE
headers: {"Authorization" => "Basic"},
basic_auth: ['user', 'password'],
env_namespace: :soapenv,
namespace_identifier: :ser,
ssl_verify_mode: :none,
log: true,
:pretty_print_xml => true
)
end
Now, I need to make a :get_list call that looks like the above sample request.
soap_client.operations returns this:
[:get_list] So I know I'm authenticated and the client is working correctly.
I can't figure out how to actually make the :get_list call.
I would like something simple where I don't even have to use Nokogiri to build my own xml, like this:
soap_client.call :get_list, message: {id => 1}
But that doesn't seem to be a feasible solution. Any thoughts on how I can pass the XML I'm building using Nokogiri through to the :get_list call for my soap_client? I am open to other suggestions as well.

Related

How to solve this error " (pre:svcFault) Service Fault"?

I am trying to call a SOAP api using Savon gem. I am getting the following error: "(pre:svcFault) Service Fault"
I created both the header and message for the request.
Here is the request sent from SoapUI: SoapUI request.
i am getting a true response from SoapUI.
My code is shown below:
class SoapApi
require 'savon'
def self.initialize
header = {
"ebmCID" => "9366498d-bc79-4fad-be2b-fa1a0e84241a",
"ebmMID" => "9366498d-bc79-4fad-be2b-fa1a0e84241a",
"ebmRTID" => "9366498d-bc79-4fad-be2b-fa1a0e84241a",
"ebmSID" => "FMobile-FCUBS",
"ebmTimestamp" => "2019-06-10T12:27:46.1623586Z",
}
message = {
customerId: '00653473'
}
client = Savon.client(
:wsdl => "https://192.168.176.103:8012/tevs/pp.pm.evs.Customer_1.2?wsdl",
:ssl_verify_mode => :none
)
response = client.call(
:get_account_list,
:soap_header => header,
:message => message
)
return response
end
end
And here i am calling the above method:
#index.html.erb
<%=
SoapApi.initialize
puts #response
%>
Where you able to create a valid call using SoapUI (https://www.soapui.org.)? Try this first and make it work.
Next create a call from a plain ruby script - without Rails - which sends the same functional XML as you did in SoapUI before.
Third embed this code into your RoR application.
You can put the following in your client definition for better logging:
client = Savon.client(
:wsdl => "https://192.168.176.103:8012/tevs/pp.pm.evs.Customer_1.2?wsdl",
:ssl_verify_mode => :none,
log: true,
log_level: :debug,
pretty_print_xml: true
)
Compare the output with your working SoapUI example.

invalid SOAP request needs

I'm using savon to make requests against a SOAP service.
Regardless, I'm having trouble making Savon client call.
below mentioned are my ruby code
client = Savon.client(
wsdl: "https://<URL>",
soap_header: { 'Content-Type:' => "text/xml"},
log: true,
pretty_print_xml: true
)
client.call(:get_active_employees_info,:xmlns =>"http://tempuri.org/") do
message("AppID" => "*******","Username" => "*****","Password" => "******")
end
which return
Savon::HTTPError: HTTP error (400)
but same snippets run on Postman
<soap12:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://www.w3.org/2003/05/soap-envelope">
<soap12:Body>
<GetActiveEmployeesInfo xmlns="http://tempuri.org/">
<AppID>**********</AppID>
<Username>********</Username>
<Password>**********</Password>
</GetActiveEmployeesInfo>
</soap12:Body>
</soap12:Envelope>
I think that the problem is related to the request body that you are building.
Since you have a working example, I would start by checking the request's body build by savon. To do this, use the following code:
ops = client.operation(:get_active_employees_info)
puts ops.build(message: {"AppID" => "*******","Username" => "*****","Password" => "******"}).pretty
Then, compare generated message with the one from Postman. I hope it helps.

How to organize the syntax in this SOAP request packet sent with gem Savon in a ruby script?

I am trying to send a SOAP request to a resource on the web. It is expecting to receive this:
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Body>
<DiscoverParameterValues xmlns="http://comscore.com/">
<parameterId>loc</parameterId>
<query xmlns="http://comscore.com/ReportQuery">
<Parameter KeyId="geo" Value="124" />
</query>
</DiscoverParameterValues>
</soap:Body>
</soap:Envelope>
This is the Ruby code that Im using to send a request that will result in an XML similar to the one above.
require 'savon'
#Connect to the Comscore API
client = Savon.client(
wsdl:"https://api.comscore.com/KeyMeasures.asmx?WSDL",
basic_auth: ["username", "pw" ],
log: true, :pretty_print_xml=>true)
And this is the XML thats generated:
<env:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tns="http://comscore.com/"
xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:ins0="http://comscore.com/Report"
xmlns:ins1="http://comscore.com/ReportQuery"
xmlns:ins2="http://comscore.com/ReportModel"
xmlns:ins3="http://comscore.com/FetchMedia"
xmlns:ins4="http://comscore.com/Media/Response">
<env:Body>
<tns:DiscoverParameterValues>
<tns:parameterId>loc</tns:parameterId>
==> <tns:query>
<tns:Parameter>
<tns:attributes>
<tns:KeyId>geo</tns:KeyId>
<tns:Value>124</tns:Value>
</tns:attributes>
</tns:Parameter>
==> </tns:query>
</tns:DiscoverParameterValues>
</env:Body>
</env:Envelope>
As you can see there is a difference in how the XML from my code and the XML thats required looks. Ive marked it with "=>"
Here is the gem documentation page that Im using to structure my query:
http://savonrb.com/version2/locals.html
Ive tried playing around with this but I dont understand how to get the "attributes" to work right.
Any suggestions?
UPDATE:
After reading this post, I changed my ruby syntax to the following:
attributes = { "KeyId"=>"geo", "Value" =>"124"}
parameter = {
:parameterId=>"loc",
:query=>{ "Parameter" => { attributes: attributes}}
}
response = client.call(:discover_parameter_values,
message:{:parameterId=>"loc",
:query =>{ "Parameter"=> "", :attributes! =>
{ "Parameter" =>
{ "KeyId" => "geo" , "Value"=>"124" }}},
},
:attributes => {"xmlns" => "http://comscore.com/" })
My XML changed to this:
<env:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tns="http://comscore.com/"
xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:ins0="http://comscore.com/Report"
xmlns:ins1="http://comscore.com/ReportQuery"
xmlns:ins2="http://comscore.com/ReportModel"
xmlns:ins3="http://comscore.com/FetchMedia"
xmlns:ins4="http://comscore.com/Media/Response">
<env:Body>
<tns:DiscoverParameterValues xmlns="http://comscore.com/">
<tns:parameterId>loc</tns:parameterId>
<tns:query>
<tns:Parameter KeyId="geo" Value="124"/>
</tns:query>
</tns:DiscoverParameterValues>
</env:Body>
</env:Envelope>
So everything works EXCEPT - I cannot set "attributes" on the "query" tag.
I would like to set this as an attribute on the query: "xmlns=http://comscore.com/ReportQuery"
How do I do that?
Question: Does it work?
Savon uses the namespace qualifiert in a different way than your first example.
In the following snippet the tags within have an implicit namespace as specified in the attribute xmlns.
<DiscoverParameterValues xmlns="http://comscore.com/">
<parameterId>loc</parameterId>
<query xmlns="http://comscore.com/ReportQuery">
<Parameter KeyId="geo" Value="124" />
</query>
</DiscoverParameterValues>
Savon uses a different syntax. It defines all needed namespace in the envelope and uses the prefixes per tag later. Different syntax for the same expression.
SoapUI uses often the former variant, Savon the latter.
ParameterIdcomes in both cases from the namespace http://comscore.com/.
UPDATE response:
You can go different ways:
Is usually cheat: I just write fully qualified keys, eg.
...
:query => { "Parameter" => "", :attributes! =>
{ "Parameter" =>
{ "ins1:KeyId" => "geo", "ins1:Value" => "124" }}},
...
If you absolutely want to put the key as attribute on "query" then you can define it like this:
response = client.call(:discover_parameter_values,
message:{:parameterId=>"loc",
:query =>{ "Parameter"=> "", :attributes! =>
{ "Parameter" =>
{ "KeyId" => "geo" , "Value"=>"124" }}},
:attributes! => {'tns:query' =>
{ "xmlns" => "http://comscore.com/ReportQuery" }
},
:attributes => {"xmlns" => "http://comscore.com/" })

Enforcing XML structure order in Savon

I'm currently trying to build a provisioning service of a kind for a local instance of Open-Xchange, using Rails 2.3.8, Savon and OX's SOAP API.
Via the console, I can issue the following commands that will work;
>> client = Savon::client("http://192.168.2.195/servlet/axis2/services/OXContextService?wsdl")
=> #<Savon::Client:0x2b5151bad790 #wsdl=#<Savon::Wasabi::Document:0x2b5151bad678 #document="http://192.168.2.195/servlet/axis2//services/OXContextService?wsdl", #request=#<HTTPI::Request:0x2b5151bad628>>, #http=#<HTTPI::Request:0x2b5151bad628>, #config=#<struct Savon::Config _logger=#<Savon::Logger:0x2b5151bad6c8 #device=#<IO:0x2b514a775ad0>>, pretty_print_xml=nil, raise_errors=true, soap_version=1, env_namespace=nil, soap_header=nil>>
>> client.request :list_by_database do
?> soap.body = {
?> :auth => {
?> :login => "oxadminmaster",
?> :password => "admin_master_password"
>> },
?> :db => {
?> :id => 3
>> }
>> }
>> end
HTTPI executes HTTP GET using the net_http adapter
SOAP request: https://192.168.2.195/servlet/axis2/services/OXContextService.OXContextServiceHttpsSoap11Endpoint/
SOAPAction: "urn:listByDatabase", Content-Type: text/xml;charset=UTF-8, Content-Length: 657
<?xml version="1.0" encoding="UTF-8"?><env:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:ins0="http://soap.admin.openexchange.com" xmlns:ns="http://soap.admin.openexchange.com" xmlns:ins1="http://dataobjects.soap.admin.openexchange.com/xsd" xmlns:ins2="http://dataobjects.rmi.admin.openexchange.com/xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"><env:Body><ins0:listByDatabase><ins0:db><ins1:id>3</ins1:id></ins0:db><ins0:auth><ins2:login>oxadminmaster</ins2:login><ins2:password>admin_master_password</ins2:password></ins0:auth></ins0:listByDatabase></env:Body></env:Envelope>
HTTPI executes HTTP POST using the net_http adapter
warning: peer certificate won't be verified in this SSL session
SOAP response (status 200):
It works, no problems there. However, when I try the following code...
class Ox_Context_Service
extend Savon::Model
attr_accessor :data, :returnCode
document "http://192.168.2.195/servlet/axis2/services/OXContextService?wsdl"
def list_by_database oxMasterUser, oxMasterPassword
begin
response = client.request :list_by_database do
soap.body = {
:auth => {
#for the sake of testing
:login => "#{oxMasterUser}",
:password => "#{oxMasterPassword}"
},
:db => {
:id => 3
#For the sake of testing
}
}
end
if response.success?
data = response.body[:list_by_database_response][:return]
if data
#data = data
#returnCode = "#{response.http.code}"
end
end
end
rescue Savon::Error => fault
#data = {}
#returnCode = "#{fault}"
end
end
Using the following CURL...
curl localhost:4545/oxContextService/list_by_database -d 'oxUsername=oxadminmaster' -d 'oxPassword=admin_master_password'
I get...
HTTPI executes HTTP GET using the net_http adapter
SOAP request: https://192.168.2.195/servlet/axis2/services/OXContextService.OXContextServiceHttpsSoap11Endpoint/
SOAPAction: "urn:listByDatabase", Content-Type: text/xml;charset=UTF-8, Content-Length: 657
<?xml version="1.0" encoding="UTF-8"?><env:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:ins0="http://soap.admin.openexchange.com" xmlns:ns="http://soap.admin.openexchange.com" xmlns:ins1="http://dataobjects.soap.admin.openexchange.com/xsd" xmlns:ins2="http://dataobjects.rmi.admin.openexchange.com/xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"><env:Body><ins0:listByDatabase><ins0:auth><ins2:login>oxadminmaster</ins2:login><ins2:password>admin_master_password</ins2:password></ins0:auth><ins0:db><ins1:id>3</ins1:id></ins0:db></ins0:listByDatabase></env:Body></env:Envelope>
HTTPI executes HTTP POST using the net_http adapter
warning: peer certificate won't be verified in this SSL session
SOAP response (status 500):
<?xml version='1.0' encoding='UTF-8'?><soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"><soapenv:Body><soapenv:Fault><faultcode>soapenv:Server</faultcode><faultstring>Authentication failed</faultstring><detail /></soapenv:Fault></soapenv:Body></soapenv:Envelope>
So- two identical calls, two different results. What could be going wrong here, SO?
Edit: The Rails call fails because the Auth block is sent first, instead of last. I think the new question is how to enforce XML structure order without explicitly writing out the XML.
soap.body = {
:auth => {
#for the sake of testing
:login => "#{oxMasterUser}",
:password => "#{oxMasterPassword}"
},
:db => {
:id => 3
#For the sake of testing
}
:order! => [:db, :auth]
}
The order! will force a specific order based on that array. Lesson learned: Read the manual.

eBay API GetCategoryFeaturesRequest returns 'Unsupported API call' error

I am trying to get CategoryFeatures using Typhoeus::Request from eBay API. I am using Ruby Version 1.9.3 and Rails 3.2.3 with Ubuntu Server 11.04.
My code is below:
xml = <GetCategoryFeaturesRequest xmlns="urn:ebay:apis:eBLBaseComponents">
<RequesterCredentials>
<eBayAuthToken> MY_eBayAuthToken </eBayAuthToken>
</RequesterCredentials>
<WarningLevel>High</WarningLevel>
</GetCategoryFeaturesRequest>
header = {
"X-EBAY-API-DEV-NAME" => my_devID,
"X-EBAY-API-APP-NAME" => my_appID,
"X-EBAY-API-CERT-NAME" => my_certID,
"X-EBAY-API-CALL-NAME" => "GetCategoryFeatures",
"X-EBAY-API-COMPATIBILITY-LEVEL" => "1.3.0",
"X-EBAY-API-SITEID" => 1,
"Content-Type" => "text/xml",
}
url = "https://api.sandbox.ebay.com/ws/api.dll "
res = Typhoeus::Request.post(url, :body => xml, :headers => header )
I got this error:
<?xml version=\"1.0\" encoding=\"UTF-8\" ?>
<GeteBayOfficialTimeResponse xmlns=\"urn:ebay:apis:eBLBaseComponents\">
<Timestamp>2012-07-05 06:41:28</Timestamp>
<Ack>Failure</Ack><Errors>
<ShortMessage>Unsupported API call.</ShortMessage>
<LongMessage>The API call \"GeteBayOfficialTime\" is invalid or not supported in this release.</LongMessage>
<ErrorCode>2</ErrorCode>
<SeverityCode>Error</SeverityCode>
<ErrorClassification>RequestError</ErrorClassification>
</Errors>
<Build>14991382</Build>
</GeteBayOfficialTimeResponse>
Does anyone know how to fix it?
Try to test your XMl in the test tool :
https://developer.ebay.com/DevZone/build-test/test-tool/default.aspx
I run this XML and it works:
<?xml version="1.0" encoding="utf-8"?>
<GetCategoryFeaturesRequest xmlns="urn:ebay:apis:eBLBaseComponents">
<RequesterCredentials>
<eBayAuthToken> my_Token </eBayAuthToken>
</RequesterCredentials>
</GetCategoryFeaturesRequest>

Resources