How to generate the following XML using Savon - ruby

I have been trying to know how to build a SOAP XML request having different tags with different namespaces through Savon - as that shown below - but haven't been able to do it yet. Could anybody please help me? The request written below was generated by SOAPUI (linked it a WSDL). Notice the urn and urn1 prefixes for namespaces. I use Savon 2, Ruby 1.9 and Rails 3.
<soapenv:Envelope xmlns:urn="urn:safetypay:messages:mws:api" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:urn1="urn:safetypay:schema:mws:api">
<soapenv:Body>
<urn:ExpressTokenRequest>
<urn1:Language>ES</urn1:Language>
<urn:Signature>2984764ac8214cca0a44e06badaba4bbc93ed381af59199f65d9c</urn:Signature>
</urn:ExpressTokenRequest>
</soapenv:Body>
</soapenv:Envelope>
Thank you in advance :)
I WANT TO SHARE THE SOLUTION:
Finally, I was able to puzzle it out. Notice that the code posted in this forum is not exactly the real one for security and/or brevity purposes (it's incomplete).
# Instance of the Savon client using the respective wsdl. Notice the two (multiple) namespaces.
# Also, see the namespace prefix urn (making it different to the default one).
# In addition,the code changed the envelope namespace prefix to soapenv.
# While debugging in console (irb), I used the parameters log: true, pretty_print_xml: true inside the following client instance too (e.g. ...env_namespace: :soapenv, log: true, pretty_print_xml: true... ). This allowed me to see very easily the XML Savon was sending when requesting.
# Remember to require the necessary resources. Maybe require 'savon', require 'digest',...
client = Savon.client(wsdl: "/MerchantExpressWs.wsdl", namespaces: {"xmlns:urn" => "urn:safetypay:messages:mws:api", "xmlns:urn1" =>"urn:safetypay:schema:mws:api"}, namespace_identifier: :urn, env_namespace: :soapenv)
# Assignment of variables below must be done in this case. I don't write it here to be this code not so large.
# A thing I lasted much time to know how to do was to add the prefix urn1 only to the tag Language (urn1:Language), since the code was adding urn to all due to the namespace_identifier: :urn (see the client instance above). As you can see, I simply added it to the array message (message1).
message1 = {"urn1:Language" => language, "Signature"=> signature }
# The respective operation for this request is create_express_token.
response = client.call(:create_express_token, message: message1 )
# This was the way Savon sent the request as needed by the Web Service. Savon added additional namespaces in comparison to the original XML request I wanted to send, but it was not problematic, it was OK (e.g., it added some additional stuff to the namespaces soapenv, urn, urn1)
I hope this post helps many to make easy to consume SOAP Web Services through Savon - Ruby (and maybe with Rails).
Thank God primarily for every good thing :)

Related

How to perform a savon client.call use a GET request with a payload instead of a POST?

I'm using savonrb (2.1.2) to perform my SOAP requests against a Web Service.
The problem I came into is that it looks like I do not have any chance to perform an HTTP GET request instead of a POST request using this library.
Please, note that even though I can agree with the fact that a SOAP over HTTP done through a GET method, instead that through a POST, might look unconventional or even an error, but
I cannot modify the server side, and, as client, I MUST accept this behavior as a matter of fact.
How can I overcome this problem?
Standing to what I've seen so far inside the code of savon, it looks like it is an immutable design decision:
# operation.rb
module Savon
class Operation
...
def call_with_logging(request)
#logger.log(request) { HTTPI.post(request, #globals[:adapter]) }
end
...
end
end
I just wonder if there should be a trick, through the mechanism of the savon adapters, to avoid this kind of (bad) solution.

WSDL Operations not being updated

I'm a bit stumped as to why our new operations are not being picked up when using the Savon gem but are when using a simple curl command.
For example when using Savon
#client = Savon.client(wsdl: "#url-here", basic_auth: %w(username password))
#client.operations
:operation_name
# At this point i expect the operation to be OperationName
When I then run a curl request
curl #url-here --user username:password
Within the XML response is
<operation name="OperationName">
We recently changed the naming convention of our operations to camel case (previously using snake case) so the tests used to pass but now they fail as not picking up the new operations (When using Savon)
Would there be any reason for this ?
Thanks
Since version 1 of Savon:
Your service probably uses (lower)CamelCase names for actions and params, but Savon maps those to snake_case Symbols for you.
I assume, Savon mapped your new camel case actions to snake case. So if you had updated your tests - you shouldn't.

Savon v3.x - how to pass complete xml message as soap request

using Savon version 3.x (the current master branch found https://github.com/savonrb/savon).
currently to generate a soap request in savon 3 you define the body of the message as a hash, ex:
operation.body = {
Search: {
accountID: 23,
accountStatus: 'closed'
}
}
response = operation.call
from the hash, savon will generate the complete soap message xml (envelope, headers, etc..) and pass that message onto HttpClient to post the request to your soap endpoint.
instead of a hash, i'd like to be able to pass in a complete xml message as my request, ex:
my_xml_request_message ='
..... lots more nested nodes, namespaces and tons of attributes, etc .....
'
it appears that body is sent to build to create the soap request, and is then posted by call:
https://github.com/savonrb/savon/blob/master/lib/savon/operation.rb#L79
def call
raw_response = #http.post(endpoint, http_headers, build)
Response.new(raw_response)
end
so i thinking was to monkey patch? call to allow me to override build with my xml block, ex:
def call
raw_response = #http.post(endpoint, http_headers, my_xml_request_message)
Response.new(raw_response)
end
that's where we're getting stuck - it's not clear to me if my xml is getting created or posted correctly. or if this is the correct way to proceed...
thanks in advance for any help!
I don't use Savon3 yet, because it's not stable yet. What you can do in v2 is:
client.call(:authenticate, xml: "<envelope><body></body></envelope>")
I suppose something similar will work in v3 as well. It existed in v1 and v2.
monkey patch solved our problem - so i think this is good answer for now.
we're looking to add this solution to savon 3 master if possible, details: https://github.com/savonrb/savon/issues/546
class Savon
class Operation
attr_accessor :raw_xml_envelope
def call
message = (raw_xml_envelope != nil ? raw_xml_envelope : build)
raw_response = #http.post(endpoint, http_headers, message)
Response.new(raw_response)
end
end
end
more background:
we've built a webservices (SOAP & REST) testing framework using Savon for the soap backbone. in our framework we define a couple methods describing each wsdl operation, our use case is to allow usage of the savon body() method when wanting to define the xml body as a hash (as described by savon's example_body()) or to pass in the complete raw xml envelope - which we are able to do using raw_xml_envelope() method above via monkey patch.

Formatting the response XML of a WebService when using Savon

I'm working using an external Webservice which I consume using the Savon gem.
I want to process the response of the WebService, before Savon, in order to clean the XML and get the correct Hash to work with. Currently, the Savon call method, answers with the Hash:
{:envelope => {
:body => {
:get_method_result => {
:result=>"OK",
:dataset_xml => "
<NewDataSet>
<xs:schema id=\"NewDataSet\" xmlns=\"\"........
Wich, as you can see, after dataset_xml has an XML string. So I have to take this and process it in order to have a full Hash.
All of this is happening because my response has thing like: <NewDataSet>\r\n <xs:schema id=\"NewDataSet\" xmlns=\ inside it's XML, which if I could be able to fix, then I wouldn't need to do all the after-process to turn it into a Hash.
You could simply try to parse the xml your self with nokogiri gem. Have you tried that already?
I would simply try
Nokogiri::XML(response[:body])
A friend solved it, he added a module named mod_substitute to Apache. I used it to parse the incoming XML, extracting the CDATA characters. With tha being done, the Savon gem received a clean XML which was parsed perfectly, in one step, to a Hash.
<Location />
AddOutputFilterByType SUBSTITUTE text/html
Substitute "s|CDATAREGEX|' '|i"
</Location>

Obtain XML element's value from REST server response using Ruby

n00b REST question. I'm making a GET request to an API's endpoint and getting the proper XML response. The question I have is, how do I get the value of a particular XML element in the servers REST response using Ruby?
So let's say one of the elements is 'Body' and I want to assign its value 'Blah blah blah' to a variable
Part of the XML response:
<Body>Blah blah blah</Body>
How would I do that with the response? Basically I want to do something like this
variable = params["Body"]
Thanks in advance!
The best solution is to use RestClient or HTTParty and have it parse the response for you.
Otherwise, you'll have to parse the response itself using a library such as Nokogiri:
doc = Nokogiri.XML(response)
variable = doc.at("body").text
You'll want to use an XML parser of some kind.
It sounds like you want something like XmlSimple, which will turn an XML document into ruby arrays and hashes. There's tons of examples of how to use it on the page that has been linked.
One thing to be aware of is that XML to native container mappings are imperfect. If you're dealing with a complex document, you'll likely want to use a more robust parser, like Nokogiri.
If you want full XML Object Mapping, HappyMapper is a decent library, although it isn't very active anymore. It can work with XML from any source, so you'll still want something like the libraries mentioned by #Fitzsimmons or #MarkThomas to do the HTTP request.

Resources