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

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.

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.

How to generate the following XML using Savon

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 :)

Getting information for rest template

I have seen a lot of code that uses this getForObject() method that takes a URI and replaces things in {} with the numbers at the end of the method.
My question is: what exactly IS http://localhost:8080/spring-rest/foos? Is it a file of some sort? What would it look like in this context.
In your example:
Foo foo = restTemplate.getForObject(URI, Foo.class, "1");
is actually trying to GET a REST resource with an HTTP request.
Under the covers:
The URL template is rendered to http://localhost:8080/sping-rest/foos/1
The HTTP client issues a GET request to http://localhost:8080/sping-rest/foos/1
The REST service can respond with a JSON representation of that resource, such as {"id":1,"content":"Hello, World!"}
The HTTP client converts that JSON response into an instance of Foo.class (using message converters)
You get an instance of Foo!
The spring.io website lists a lot of guides, you should definitely check them out! There's even a "Consuming a RESTful web service" guide.

is there a way to modify or send custom headers on grape?

I'm using Goliath and Grape. On my goliath server it calls the grape api like so:
when '/posts' then FrameworksAPI::API.call(env)
On my grape api class, my method is as simple as this:
get '/:id' do
Post.find(params[:id])
end
I'd like to modify the headers - specifically the 'Content-Length' but unsure how to.
Also i'd like to ask an additional question. How do i create callback/filters specifically before the method GET returns the result i'd like to modify the result.
Grape has a header helper for a few versions now.
header 'Content-Length`, 42.to_s
For your second question on modifying the body, try using after do ... at API level.
The return from your FrameworksAPI::API.call(env) method will be a triplet [status_code, headers, body]. So, instead of just returning that from your case you'd do something like:
when '/posts' then
status, headers, body = FrameworksAPI::API.call(env)
headers['whatever'] = blah
[status, headers, body]
You can also change the body, just be careful as the body maybe an array.
There is also a Content-Length middleware that is provided by Goliath. Content-Length is loaded by default although if you set a custom Content-Length it will take precedence. Just be carefull that other middlewares like the formattings don't change the body after you set your content-length.

Resources