SOAP requests using Savon gem

I’ve been writing a little test SOAP client, which turned out to — as with so many things — not be a straightforward as I expected. SOAP rarely is, though, but unfortunately the service I’m working with doesn’t provide anything as modern as a JSON-emitting REST interface, alas. SOAP it is, then.

First, I quickly discovered that it took some real searching to find anything reasonable in the way of a SOAP client library for Ruby. There’s a bunch of SOAP crap in the Ruby docs, of course: there’s the SOAP module, there’s SOAP::RPC, and tons more. With no documentation whatsoever. I could almost get something to work with that, except that I needed to add an additional header to the SOAP envelope, and…well, there doesn’t seem to be info anywhere on how to do that. Hopeless.

Thankfully, I finally discovered the Savon gem. It’s pretty well-documented and very simple to use. With that, I was quickly able to put together a request…which didn’t work. Hmm. There was some yucky PHP example code for the service I was trying to access, so I ran that with the cool HTTPScoop program going, and looked at the request that it sent, which did work. Some differences there, indeed, and it wasn’t obvious how to fix it. The problem was that the input wasn’t properly specified, and the parameter didn’t have a namespace declared on it. Instead of:

<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://example.com/webservice/schema/">
  <SOAP-ENV:Header>
    <ns1:API-KEY>foobar</ns1:API-KEY>
  </SOAP-ENV:Header>
  <SOAP-ENV:Body>
    <ns1:DoSimpleRequest>
      <ns1:uniqueId>12345</ns1:uniqueId>
    </ns1:DoSimpleRequest>
  </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

I was getting this:

<env:Envelope xmlns:wsdl="http://example.com/webservice/schema/" xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
  <env:Header>
    <API-KEY>foobar</API-KEY>
  </env:Header>
  <env:Body>
    <wsdl:DoSimple>
      <uniqueId>12345</uniqueId>
    </wsdl:DoSimple>
  </env:Body>
</env:Envelope>

You can see that the input ended up as “DoSimple” instead of “DoSimpleRequest”. My Ruby code was doing response = client.do_simple which of course generated that. But if I changed it to response = client.do_simple_request I got a method missing error. Something about the WSDL wasn’t matching what I needed to send, which was annoying. In addition, the uniqueId parameter didn’t have the namespace prepended to it.

So, what I ended up needing to do with Savon was to specifically provide the input and action, rather than letting it figure them out from the WSDL; and then I needed to force in the namespace declaration for the parameter. The resulting, working code:

require 'rubygems'
require 'savon'

client = Savon::Client.new "http://example.com/the-soap-API.wsdl"
response = client.do_simple do |soap|
  soap.header["API-KEY"] = "foobar"
  soap.input = "DoSimpleRequest"
  soap.action = "DoSimpleRequest"
  body = Hash.new
  body["wsdl:uniqueId"] = 12345
  soap.body = body
end

puts "Response:\n"
puts response

It’s not ideal — normally I’d specify the uniqueId parameter with a nice

  soap.body = {
      :uniqueId => 12345
  }

But that resulted in uniqueId rather than wsdl:uniqueId, and I couldn’t find another way to force it. I looked through the Savon source code, and body is simply a Hash attribute on which it ends up calling @body.to_soap_xml. So I sort of fooled it by creating my own hash and then setting body to it. Obviously a fragile thing to do, so we’ll see if I missed something, and if not whether it makes sense to submit a patch to enable this in a more robust way. In any case, now I get the request that I needed:

<env:Envelope xmlns:wsdl="http://example.com/webservice/schema/" xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
  <env:Header>
    <API-KEY>foobar</API-KEY>
  </env:Header>
  <env:Body>
    <wsdl:DoSimpleRequest>
      <wsdl:uniqueId>12345</wsdl:uniqueId>
    </wsdl:DoSimpleRequest>
  </env:Body>
</env:Envelope>

Hope this saves someone some work, and I’ll be sure to follow up with the eventual outcome of this patchwork solution.

Advertisements
11 comments
  1. orpheol said:

    Hey thanks for you post, it’s one of only like five useful web documents on Savon…and I didn’t quite realize it had my answer until hours later (since I didn’t understand xml namespaces) but I thought I would share…

    From the savon docs:

    “Hash keys specified as Strings are not converted and may contain namespaces”

    so you can do soap.body = { “wsdl:uniqueId” => 12345}.

    Now here’s my real question since I still don’t fully understand namespaces, how come it will prepend the namespace to the input name but not to any of the parameters? Is there some reason (say for flexibility) that you wouldn’t prepend the namespace to the parameters, or is it difficult to know which namespace to apply?

  2. I’m not entirely sure about your question, I’m afraid, but I’m no expert on namespaces myself. Asking Savon’s author, @rubiii, about the issue, I got back an interesting reply which you might also find helpful. The request I need to create can be constructed in this way by associating the namespaces with their URIs:

    Savon::Client.wsdl = false
    client = Savon::Client.new “http://example.com/the-soap-API.wsdl”
    response = client.do_simple do |soap|
    soap.namespaces[“xmlns:wsdl”] = “http://example.com/webservice/schema/”
    soap.namespaces[“xmlns:ns2”] = “http://webservice.example.com”
    soap.header = { “env:Header” => { “ns2:API-KEY” => “foobar” } }
    soap.body = { “wsdl:uniqueId” => 12345 }
    end

  3. Purvez Desai said:

    First of all thanks for your post, as Orpheol says it is the most PRACTICAL one that I’ve found regarding Savon. I know you may have moved on from here but I need some help please.

    I’m trying to generate the following request:

    abc
    123

    2205
    localTgsProvider

    The following is the Controller that I’ve created.

    require ‘rubygems’
    require ‘savon’

    class XyzController < ApplicationController
    #include Savon

    def gethoteldetails
    # Savon::Client.wsdl = false

    client = Savon::Client.new "http://tourico.com/webservices/GetHotelDetails&quot;
    @response = client.GetHotelDetails! do|soap|

    soap.namespaces["xmlns:SOAP-ENV"] = "http://schemas.xmlsoap.org/soap/envelope/&quot;
    soap.namespaces["xmlns:SOAP-ENC"] = "http://schemas.xmlsoap.org/soap/encoding/&quot;
    soap.namespaces["xmlns:xsi"] = "http://www.w3.org/2001/XMLSchema-instance&quot;
    soap.namespaces["xmlns:xsd"] = "http://www.w3.org/2001/XMLSchema&quot;
    soap.namespaces["xmlns:mO"] = "http://tourico.com/travelservices/&quot;
    soap.namespaces["xmlns:m"] = "http://tourico.com/webservices/&quot;

    soap.header["m:LoginHeader"]
    soap.header["mO:username"] = "abc"
    soap.header["mO:password"] = "123"

    soap.body["m:GetHotelDetails"]
    soap.body["m:HotelID"] = 2205
    end
    end
    end

    This is giving me a NoMethodError with nil object at
    client.GetHotelDetails! do |soap|

    and

    soap.body["m:GetHotelDetails"]

    Any ideas on what I may be doing wrong?

    Thanks in advance

  4. Purvez Desai said:

    Oh Heck everything within angled brackets has been removed so the request part has mainly disappeared. Please let me know how to include ‘code’ and I’ll re-post. Thanks

  5. I haven’t been working with this recently, but I’ll take a look at your question and see if I have some ideas for you. Regarding embedding code, WordPress uses the

    foo...bar

    format to include code. Thanks.

    • That is, place “sourcecode” in square brackets before the code, and “/sourcecode” in square brackets after the code.

  6. This article did save me a ton of research/work. Thanks for taking the time to write this up. Cheers!

  7. NoMethodError: undefined method `do_simple’ for #

    So far this has been the best resource for using Savon that I’ve found via google, except that error.

  8. montana said:

    SubArachnoid Space! how awesome – I came here looking for help with savon…

    • Ha, thanks for the note and mention of SAS. Hope you got a bit of info about Savon, too…

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: