I am trying to avoid using Nokogiri/Builder to build my XML and would like to instead use the Savon gem with Ruby 2.0.0. I have the following request I need to replicate:
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<GetList xmlns="http://tempuri.org/">
<listRequest xmlns:a="http://schemas.datacontract.org/2004/07/Services.List"
i:type="b:NpsListRequest"
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>
So far I have this:
def soap_client
soap_client = Savon.client(
wsdl: "http://10.10.10.10/ListApi.svc?wsdl"
headers: {"Authorization" => "Basic"},
basic_auth: ['username', 'password'],
env_namespace: :s,
ssl_verify_mode: :none,
log: true,
:pretty_print_xml => true
)
end
Then soap_client.call :get_list, message: {'id' => 1} which returns this:
<s:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tns="http://tempuri.org/"
xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<tns:GetList>
<id>1</id>
</tns:GetList>
</s:Body>
</s:Envelope>
I can't figure out how to replicate the first request exactly. the tns: namespace on GetList is wrong, and I can't replicate the <listRequest xmlns:a = piece either. Any thoughts on how to do this within Savon?
The namespace on GetList is correct.
What you probably need to write is
soap_client.call(:get_list,
:attributes => {'xmlns:b'=>'http://schemas.datacontract.org/'},
message: { 'ListRequest' => { 'tns:id' => 1 } }
That won't be the exact solution for your problem, because I don't have access to your wsdl and can't test. But you should get the key to a solution.
Related
TLDR: the solution can be found here
I'm using savon to make requests against a SOAP service. I know... Gross.
Regardless, I'm having trouble making Savon behave. The SOAP provider has this validator, which takes the following inputs:
Web Service: ProductData
Version: 1.0.0
Operation: getProductSellable
Endpoint: https://psproductdata100-stg.pcna.online
When I use the validator, I enter this xml:
<GetProductSellableRequest xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.promostandards.org/WSDL/ProductDataService/1.0.0/">
<wsVersion xmlns="http://www.promostandards.org/WSDL/ProductDataService/1.0.0/SharedObjects/">1.0.0</wsVersion>
</GetProductSellableRequest>
And I get this response body
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Header />
<s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<GetProductSellableResponse xmlns="http://www.promostandards.org/WSDL/ProductDataService/1.0.0/">
<ErrorMessage xmlns="http://www.promostandards.org/WSDL/ProductDataService/1.0.0/SharedObjects/">
<code>110</code>
<description>Authentication Credentials Required</description>
</ErrorMessage>
</GetProductSellableResponse>
</s:Body>
</s:Envelope>
That response is valid because I did not provide my un/pw. If I do provide credentials, I get a full response. Below is a screenshot of that happening in my browser.
However, when I use Savon to make the same request
#!/usr/bin/env ruby
require 'savon'
require 'awesome_print'
require 'byebug'
require 'pry'
endpoint = 'https://psproductdata100-stg.pcna.online'
path = 'psProductData.svc?singleWsdl'
wsdl = "#{endpoint}/#{path}"
args = {
wsdl: wsdl,
log: true,
log_level: :debug,
pretty_print_xml: true,
element_form_default: :qualified
}
client = Savon.client(args) do
convert_request_keys_to :lower_camelcase
end
message = { ws_version: '1.0.0' }
response = client.call(:get_product_sellable) do
message(message)
end
ap response
The response does not come back as expected. The XML looks close to what was sent by the validator, but not exact.
Heres the request
D, [2018-04-26T18:01:00.471662 #89854] DEBUG -- : HTTPI /peer GET request to psproductdata100-stg.pcna.online (net_http)
I, [2018-04-26T18:01:00.979809 #89854] INFO -- : SOAP request: https://psproductdata100-stg.pcna.online/psProductData.svc
I, [2018-04-26T18:01:00.979886 #89854] INFO -- : SOAPAction: "getProductSellable", Content-Type: text/xml;charset=UTF-8, Content-Length: 501
D, [2018-04-26T18:01:00.980107 #89854] DEBUG -- : <?xml version="1.0" encoding="UTF-8"?>
<env:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tns="http://www.promostandards.org/WSDL/ProductDataService/1.0.0/" xmlns:env="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ins0="http://www.promostandards.org/WSDL/ProductDataService/1.0.0/SharedObjects/">
<env:Body>
<tns:GetProductSellableRequest>
<tns:wsVersion>1.0.0</tns:wsVersion>
</tns:GetProductSellableRequest>
</env:Body>
</env:Envelope>
And the response
D, [2018-04-26T18:01:00.980224 #89854] DEBUG -- : HTTPI /peer POST request to psproductdata100-stg.pcna.online (net_http)
I, [2018-04-26T18:01:01.650449 #89854] INFO -- : SOAP response (status 200)
D, [2018-04-26T18:01:01.650731 #89854] DEBUG -- : <?xml version="1.0"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<GetProductSellableResponse xmlns="http://www.promostandards.org/WSDL/ProductDataService/1.0.0/">
<ErrorMessage xmlns="http://www.promostandards.org/WSDL/ProductDataService/1.0.0/SharedObjects/">
<code>110</code>
<description>Version mismatch.</description>
</ErrorMessage>
</GetProductSellableResponse>
</s:Body>
</s:Envelope>
and the output from Savon
{
:get_product_sellable_response => {
:error_message => {
:code => "110",
:description => "Version mismatch.",
:#xmlns => "http://www.promostandards.org/WSDL/ProductDataService/1.0.0/SharedObjects/"
},
:#xmlns => "http://www.promostandards.org/WSDL/ProductDataService/1.0.0/"
},
:"#xmlns:xsi" => "http://www.w3.org/2001/XMLSchema-instance",
:"#xmlns:xsd" => "http://www.w3.org/2001/XMLSchema"
}
Thanks to the other answers on this thread, I found this post. I read up on how namespaces work, as well as linked XSD files. By combining my new knowledge with that post, I was a able to build out something like this:
require 'savon'
require 'awesome_print'
require 'byebug'
require 'pry'
endpoint = 'https://psproductdata100-stg.pcna.online'
path = 'psProductData.svc?singleWsdl'
wsdl = "#{endpoint}/#{path}"
args = {
wsdl: wsdl,
log: false,
log_level: :debug,
pretty_print_xml: true,
element_form_default: :qualified
}
client = Savon.client(args) do
convert_request_keys_to :lower_camelcase
namespaces 'xmlns:shar' => 'http://www.promostandards.org/WSDL/ProductDataService/1.0.0/SharedObjects/'
end
message = {
'shar:wsVersion' => '1.0.0',
'shar:id' => 'REDACTED',
'shar:password' => 'REDACTED'
}
response = client.call(:get_product_sellable) do
message(message)
end
ap response.body[:get_product_sellable_response][:product_sellable_array][:product_sellable][0..2]
Now i'm more fun to talk to at dinner parties.
Similar problem with namespaces ruby savon and wsdl namespacing. I believe you have the same problem.
Additional Note: Try to use SOAP UI to debug, the generated XML from the WSDL and see the difference. Also change the XML to the XML generated from the code and validate the XML in SOAP UI. It will show the errors if any in SOAP UI, will make debugging faster and errors more understandable.
I believe your issue is a namespace problem. The wsVersion is within the shared object namespace "ins0". It is not in the "tns" namespace. Try changing your request to look like the following:
<env:Body>
<tns:GetProductSellableRequest>
<ins0:wsVersion>1.0.0</ins0:wsVersion>
</tns:GetProductSellableRequest>
</env:Body>
I am currently trying to run a Soap Call in Ruby. Using Savon client, I am not getting the response I want. This is the XML I want to create:
<soapenv:Envelope
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:sch="http://test.com/requests/test-manager/schema">
<soapenv:Header/>
<soapenv:Body>
<sch:createTestDateRequest>
<sch:testMethod>DD</sch:testMethod>
<sch:testPeriodNumber>999</sch:testPeriodNumber>
<sch:testDateTime>2017-12-14 00:00:00</sch:testDateTime>
<sch:name>Automated test</sch:name>
</sch:createTestDateRequest>
</soapenv:Body>
</soapenv:Envelope>
And I've written the following code to create that Soap Call:
require 'savon'
$soap_client_testmgr = Savon.client(
wsdl: 'http://test.com/test-manager-ws/TestManagerSoapService?wsdl',
namespace: 'http://test.com/requests/test-manager/schema',
env_namespace: :soapenv,
namespace_identifier: :sch,
pretty_print_xml: true,
log: true,
log_level: :debug)
$soap_client_testmgr.call(:create_test_date, message: {
:testMethod => 'DD',
:testPeriodNumber => $test_period,
:testDateTime => (Date.today+7).strftime('%Y-%m-%d') + ' 00:00:00',
:name => 'Automated test'})
But the XML that is generated is the following, missing the :sch before every individual name tag. This should be taken from the WSDL, correct? I've looked into namespace identifiers, but I have yet to find a fitting solution.
<soapenv:Body>
<sch:createTestDateRequest>
<testMethod>DD</testMethod>
<testPeriodNumber>999</testPeriodNumber>
<testDateTime>2017-12-14 00:00:00</testDateTime>
<name>Automated test</name>
</sch:createTestDateRequest>
</soapenv:Body>
</soapenv:Envelope>
What could solve this? I just need to add the sch: before the name tag.
Thanks!
Fixed it by using string interpolation:
$soap_client_testmgr.call(:create_test_date, message: {
:"sch:testMethod" => 'DD',
:"sch:testPeriodNumber" => $test_period,
:"sch:testDateTime" => (Date.today+7).strftime('%Y-%m-%d') + ' 00:00:00',
:"sch:name" => 'Automated test'})
I'm trying to write code in Ruby with the Savon gem (v2) that fetches account information from a SOAP api, but I'm having an issue with passing an Array.
CampaignIds is supposed to be an array of integers.
Here is my code:
client = Savon.client(wsdl: "https://api7secure.publicaster.com/Pub7APIV1/Campaign.svc?singleWsdl")
message = {
"EncryptedAccountID" => api_key,
"APIPassword" => api_password,
"CampaignIds" => [3,4],
"StartDate" => yesterday,
"EndDate" => yesterday,
"IncludeTests" => false
}
client.call(:get_comparative_report_details_data, message: message)
which produces the following request:
<?xml version="1.0" encoding="UTF-8"?>
<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ins0="http://schemas.microsoft.com/2003/10/Serialization/Arrays" xmlns:tns="http://BlueSkyFactory.Publicaster7.Public.API" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<env:Body>
<tns:GetComparativeReportDetailsData>
<tns:EncryptedAccountID>*****</tns:EncryptedAccountID>
<tns:APIPassword>*****</tns:APIPassword>
<tns:CampaignIds>3</tns:CampaignIds>
<tns:CampaignIds>4</tns:CampaignIds>
<tns:StartDate>2014-01-06</tns:StartDate>
<tns:EndDate>2014-01-06</tns:EndDate>
<tns:IncludeTests>false</tns:IncludeTests>
</tns:GetComparativeReportDetailsData>
</env:Body>
</env:Envelope>
whereas, if I play around in SOUP UI, the request should look like:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:blu="http://BlueSkyFactory.Publicaster7.Public.API" xmlns:arr="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
<soapenv:Header/>
<soapenv:Body>
<blu:GetComparativeReportData>
<blu:EncryptedAccountID>*****</blu:EncryptedAccountID>
<blu:APIPassword>*****</blu:APIPassword>
<blu:CampaignIds>
<arr:int>3</arr:int>
<arr:int>4</arr:int>
</blu:CampaignIds>
<blu:StartDate>2014-01-06T16:21:47-05:00</blu:StartDate>
<blu:EndDate>2014-01-07T16:21:47-05:00</blu:EndDate>
<blu:IncludeTests>false</blu:IncludeTests>
</blu:GetComparativeReportData>
</soapenv:Body>
</soapenv:Envelope>
Any Ideas?
You can try this syntax :
message = {
...
"CampaignIds" => {"int" => [3,4]},
...
}
That'll produce this output :
<CampaignIds>
<int>3</int>
<int>4</int>
</CampaignIds>
Hope this helps.
I just experienced the same issue and found a solution, that's not dependent on having a permissive SOAP endpoint. You can configure this behavior as a global using the unwrap key. This key is given as an option to Gyoku which generates the XML for Savon.
client = Savon.client(wsdl: 'https://example.com/wsdl', unwrap: true)
client.call(:cook_meal, message: { 'Ingredients' => ['tomato', 'basil', 'mozzarella'] })
Although the issue is old, better late than never.
How can I add an encoding attribute to the body tag using Savon?
Some background:
I am trying to use savon to connect to a SOAP resource. I can get the WSDL file and browse through the methods.
#client = Savon::Client.new("http://some.domain.com/v2messaging/service?WSDL")
when I try to use the login method
response = #client.request :service, :login do
soap.body = {
"String_1" => "username",
"String_2" => "password"
}
end
I get this error:
Failure/Error: response = #client.request :service, :login do Savon::SOAP::Fault: (env:Client) caught exception while handling request: unexpected encoding style: expected=http://schemas.xmlsoap.org/soap/encoding/, actual
The difference in the body tag. Here is the expected xml (found through SOAPUI application):
<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/" xmlns:service="etapestryAPI/service">
<env:header/>
<env:body>
<service:login env:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<String_1>username</String_1>
<String_2>password</String_2>
</service:login>
</env:body>
</env:Envelope>
Savon sends:
<?xml version="1.0" encoding="UTF-8"?>
<env:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:service="etapestryAPI/service" xmlns:env="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ins0="http://java.sun.com/jax-rpc-ri/internal" xmlns:ins1="etapestryAPI/service">
<env:Body>
<service:login>
<String_1>username</String_1>
<String_2>password</String_2>
</service:login>
</env:Body>
</env:Envelope>
There are a few difference between these, but the error returned has to do with the env:encodingStyle attribute on the env:login tag. How can add this attribute?
I figured this one out. To add an attribute to the function tag (in this case login), you can pass in an additional parameter to the method:
response = #client.request :service, :login, "env:encodingStyle" => "http://schemas.xmlsoap.org/soap/encoding/" do
soap.body = {
"String_1" => "username",
"String_2" => "password"
}
end
This will probably now work without passing the block.
I have this (working) example in php:
$usernameToken = new SoapHeaderUsernameToken( $password, $username );
$soapHeaders[] = new SoapHeader("urn:localhost-catalog", 'UsernameToken', $usernameToken);
$client = new SoapClient("my_client_url");
$client->__setSoapHeaders( $soapHeaders );
But I want a client in Ruby. I'm using the savon gem, which can do authentication for you. So far, I have the following method:
def my_soap_client
client = Savon::Client.new do
wsdl.document = "my_client_url"
end
client.wsse.credentials "usr", "pass" # Adding :digest doesn't seem to help
return client
end
# I make requests like this:
my_soap_client.request :wsdl, "getProductGroups", :parent_id => 1
I can successfully list available actions, but if I try to make a request, the result is always "(SOAP-ENV:Client) Missing parameter". Also, it doesn't matter if I pass correct credentials or fake ones. That is why I believe the authentication fails.
Hope someone can help!
Using Wireshark sniffer I intercepted SOAP request that your PHP code sends. It looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:ns1="urn:examples:helloservice"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:ns2="urn:localhost-catalog"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<SOAP-ENV:Header>
<ns2:UsernameToken>
<Password>pass</Password>
<Username>login</Username>
</ns2:UsernameToken>
</SOAP-ENV:Header>
<SOAP-ENV:Body>
<ns1:getProductGroups>
<parentId>1</parentId>
</ns1:getProductGroups>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
Here you can see that this code adds ns2 namespace for UsernameToken header.
Then I intercepted SOAP request that sends your Ruby code:
<?xml version="1.0" encoding="UTF-8"?>
<env:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:wsdl="http://www.ecerami.com/wsdl/HelloService.wsdl"
xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
<env:Header>
<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<wsse:UsernameToken wsu:Id="UsernameToken-1"
xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<wsse:Username>usr</wsse:Username>
<wsse:Password
Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">
pass
</wsse:Password>
</wsse:UsernameToken>
</wsse:Security>
</env:Header>
<env:Body>
<wsdl:getProductGroups>
<parentId>1</parentId>
</wsdl:getProductGroups>
</env:Body>
</env:Envelope>
You can see the difference - UsernameToken is wrapped into Security element and namespace for UsernameToken is "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
Your PHP code uses custom authorization and savon does not support it via wsse property. I looked into Savon source code, it uses Akami.wsse and in its code there is no possibility to set namespace. This means you need to manually construct headers. Here is the code in Ruby that generates SOAP request like you possibly need:
require 'savon'
client = Savon::Client.new do
wsdl.document = "test.wsdl"
end
client.request :wsdl, "sayHello" do
soap.namespaces["xmlns:ns2"] = "urn:localhost-catalog"
soap.header = { "ns2:UsernameToken" =>
{"Password" => "pass", "Username" => "login"}
}
soap.body = { :parent_id => 1 }
end
It generate the following SOAP request:
<?xml version="1.0" encoding="UTF-8"?>
<env:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:wsdl="http://www.ecerami.com/wsdl/HelloService.wsdl"
xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:ns2="urn:localhost-catalog">
<env:Header>
<ns2:UsernameToken>
<Password>pass</Password>
<Username>login</Username>
</ns2:UsernameToken>
</env:Header>
<env:Body>
<wsdl:sayHello>
<parentId>1</parentId>
</wsdl:sayHello>
</env:Body>
</env:Envelope>
Hope this helps!
to set a header in Savon use the following expression:
client.request :wsdl, "getProductGroups" do
soap.header = { :header_key => "your header goes here",
:attributes! => { :attr => "your attribute" }
}
soap.body = { :parent_id => 1 }
end
perhaps that helps you.
-st