Savon aims to be a simple but awesome abstraction of your SOAP service. Make sure to be
familiar with SOAP, WSDL documents and install soapUI before going Heavy Metal!

Installation & Resources

Savon is available through Rubygems and can be installed via:

$ gem install savon

The code is hosted on Github, RDoc Documentation is provided by RubyDoc.info
and there’s a Mailing list at Google Groups.

Setting up the client

Savon::Client is the interface to your SOAP service. The easiest way to get started is to use a local or remote WSDL document.

client = Savon::Client.new do
  wsdl.document = "http://service.example.com?wsdl"
end

Savon::Client.new accepts a block inside which you can access local variables and even public methods from your own class, but instance variables won’t work. If you want to know why that is, I’d recommend reading about instance_eval with delegation.

If you don’t like this behaviour or if it’s creating a problem for you, you can accept arguments in your block to specify which objects you would like to receive and Savon will yield those instead of instance evaluating the block. The block accepts 1-3 arguments and yields the following objects.

[wsdl, http, wsse]

These objects provide methods for setting up the client. In order to use the wsdl and http object, you can specify two (of the three possible) arguments.

client = Savon::Client.new do |wsdl, http|
  wsdl.document = "http://service.example.com?wsdl"
  http.proxy = "http://proxy.example.com"
end

You can also access them through methods of your client instance.

client.wsse.credentials "username", "password"

(Not) using a WSDL

You can instantiate a client with or without needing a (local or remote) WSDL document. Using a WSDL is a little easier because Savon can parse the WSDL for the target namespace, endpoint and available SOAP actions. But the (remote) WSDL has to be downloaded and parsed once for every client and so comes with a performance penalty.

To use a local WSDL, you specify the path to the file instead of the remote location.

client = Savon::Client.new do
  wsdl.document = File.expand_path("../wsdl/ebay.xml", __FILE__)
end

With the client set up, you can now see what Savon knows about your service through methods offered by Savon::WSDL::Document (wsdl). It’s not too much, but it can save you some code.

# the target namespace
client.wsdl.namespace     # => "http://v1.example.com"

# the SOAP endpoint
client.wsdl.endpoint      # => "http://service.example.com"

# available SOAP actions
client.wsdl.soap_actions  # => [:create_user, :get_user, :get_all_users]

# the raw document
client.wsdl.to_xml        # => "<wsdl:definitions name=\"AuthenticationService\" ..."

Your service probably uses (lower)CamelCase names for actions and params, but Savon maps those to snake_case Symbols for you.

To use Savon without a WSDL, you initialize a client by setting the SOAP endpoint and target namespace.

client = Savon::Client.new do
  wsdl.endpoint = "http://service.example.com"
  wsdl.namespace = "http://v1.example.com"
end

Qualified Locals

Savon reads the value for elementFormDefault from a given WSDL and defaults to :unqualified in case no WSDL document is used. The value specifies whether all locally declared elements in a schema must be qualified. As of v0.9.9, the value can be manually set to :unqualified or :qualified when setting up the client.

client = Savon::Client.new do
  wsdl.element_form_default = :unqualified
end

Preparing for HTTP

Savon uses HTTPI to execute GET requests for WSDL documents and POST requests for SOAP requests. HTTPI is an interface to HTTP libraries like Curl and Net::HTTP.

The library comes with a request object called HTTPI::Request (http) which you can access through the client. I’m only going to document a few interesting details about it and point you to the documentation for HTTPI for additional information.

SOAPAction is an HTTP header information required by legacy services. If present, the header value must have double quotes surrounding the URI-reference (SOAP 1.1. spec, section 6.1.1). Here’s how you would set/overwrite the SOAPAction header in case you need to.

client.http.headers["SOAPAction"] = '"urn:example#service"'

If your service relies on cookies to handle sessions, you can grab the cookie from the HTTPI::Response and set it for subsequent requests.

client.http.headers["Cookie"] = response.http.headers["Set-Cookie"]

WSSE authentication

Savon comes with Savon::WSSE (wsse) for you to use wsse:UsernameToken authentication.

client.wsse.credentials "username", "password"

Or wsse:UsernameToken digest authentication.

client.wsse.credentials "username", "password", :digest

Or wsse:Timestamp authentication.

client.wsse.timestamp = true

By setting #timestamp to true, the wsu:Created is set to Time.now and wsu:Expires is set to Time.now + 60. You can also specify your own values manually.

client.wsse.created_at = Time.now
client.wsse.expires_at = Time.now + 60

Savon::WSSE is based on an autovivificating Hash. So if you need to add custom tags, you can add them.

client.wsse["wsse:Security"]["wsse:UsernameToken"] = { "Organization" => "ACME" }

When generating the XML for the request, this Hash will be merged with another Hash containing all the default tags and values. This way you might digg into some code, but then you can even overwrite the default values.

Executing SOAP requests

Now for the fun part. To execute SOAP requests, you use the Savon::Client#request method. Here’s a very basic example of executing a SOAP request to a get_all_users action.

response = client.request :get_all_users

This single argument (the name of the SOAP action to call) works in different ways depending on whether you’re using a WSDL document. If you do, Savon will parse the WSDL document for available SOAP actions and convert their names to snake_case Symbols for you.

Savon converts snake_case_symbols to lowerCamelCase like this:

:get_all_users.to_s.lower_camelcase  # => "getAllUsers"
:get_pdf.to_s.lower_camelcase        # => "getPdf"

This convention might not work for you if your service requires CamelCase method names or methods with UPPERCASE acronyms. But don’t worry. If you pass in a String instead of a Symbol, Savon will not convert the argument. The difference between Symbols and String identifiers is one of Savon’s convention.

response = client.request "GetPDF"

The argument(s) passed to the #request method will affect the SOAP input tag inside the SOAP request.
To make sure you know what this means, here’s an example for a simple request:

<env:Envelope
    xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <env:Body>
    <getAllUsers />  <!-- the SOAP input tag -->
  </env:Body>
</env:Envelope>

Now if you need the input tag to be namespaced <wsdl:getAllUsers />, you pass two arguments to the #request method. The first (a Symbol) will be used for the namespace and the second (a Symbol or String) will be the SOAP action to call:

response = client.request :wsdl, :get_all_users

You may also need to bind XML attributes to the input tag. In this case, you pass a Hash of attributes following to the name of your SOAP action and the optional namespace.

response = client.request :wsdl, "GetPDF", :id => 1

These arguments result in the following input tag.

<wsdl:GetPDF id="1" />

Wrestling with SOAP

To interact with your service, you probably need to specify some SOAP-specific options. The #request method is the second important method to accept a block and lets you access the following objects.

[soap, wsdl, http, wsse]

Notice, that the list is almost the same as the one for Savon::Client.new. Except now, there is an additional object called soap. In contrast to the other three objects, the soap object is tied to single requests.

Savon::SOAP::XML (soap) can only be accessed inside this block and Savon creates a new soap object for every request.

Savon by default expects your services to be based on SOAP 1.1. For SOAP 1.2 services, you can set the SOAP version per request.

response = client.request :get_user do
  soap.version = 2
end

If you don’t pass a namespace to the #request method, Savon will attach the target namespaces to "xmlns:wsdl". If you pass a namespace, Savon will use it instead of the default.

client.request :v1, :get_user
<env:Envelope
    xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:v1="http://v1.example.com">
  <env:Body>
    <v1:GetUser>
  </env:Body>
</env:Envelope>

You can always set namespaces and overwrite namespaces. They’re stored as a Hash.

# setting a namespace
soap.namespaces["xmlns:g2"] = "http://g2.example.com"

# overwriting "xmlns:wsdl"
soap.namespaces["xmlns:wsdl"] = "http://ns.example.com"

A little interaction

To call the get_user action of a service and pass the ID of the user to return, you can use a Hash for the SOAP body.

response = client.request :get_user do
  soap.body = { :id => 1 }
end

If you only need to send a single value or if you like to create a more advanced object to build the SOAP body, you can pass any object that’s not a Hash and responds to to_s.

response = client.request :get_user_by_id do
  soap.body = 1
end

As you already saw before, Savon is based on a few conventions to make the experience of having to work with SOAP and XML as pleasant as possible. The Hash is translated to XML using Gyoku which is based on the same conventions.

soap.body = {
  :first_name => "The",
  :last_name  => "Hoff",
  "FAME"      => ["Knight Rider", "Baywatch"]
}

As with the SOAP action, Symbol keys will be converted to lowerCamelCase and String keys won’t be touched. The previous example generates the following XML.

<env:Envelope
    xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"
    xmlns:wsdl="http://v1.example.com">
  <env:Body>
    <wsdl:CreateUser>
      <firstName>The</firstName>
      <lastName>Hoff</lastName>
      <FAME>Knight Rider</FAME>
      <FAME>Baywatch</FAME>
    </wsdl:CreateUser>
  </env:Body>
</env:Envelope>

Some services actually require the XML elements to be in a specific order. If you don’t use Ruby 1.9 (and you should), you can not be sure about the order of Hash elements and have to specify the correct order using an Array under a special :order! key.

{ :last_name => "Hoff", :first_name => "The", :order! => [:first_name, :last_name] }

This will make sure, that the lastName tag follows the firstName.

Assigning arguments to XML tags using a Hash is even more difficult. It requires another Hash under an :attributes! key containing a key matching the XML tag and the Hash of attributes to add.

{ :city => nil, :attributes! => { :city => { "xsi:nil" => true } } }

This example will be translated to the following XML.

<city xsi:nil="true"></city>

I would not recommend using a Hash for the SOAP body if you need to create complex XML structures, because there are better alternatives. One of them is to pass a block to the Savon::SOAP::XML#body method. Savon will then yield a Builder::XmlMarkup instance for you to use.

soap.body do |xml|
  xml.firstName("The")
  xml.lastName("Hoff")
end

Last but not least, you can also create and use a simple String (created with Builder or any another tool):

soap.body = "<firstName>The</firstName><lastName>Hoff</lastName>"

Besides the body element, SOAP requests can also contain a header with additional information. Savon sees this header as just another Hash following the same conventions as the SOAP body Hash.

soap.header = { "SecretKey" => "secret" }

If you’re sure that none of these options work for you, you can completely customize the XML to be used for the SOAP request.

soap.xml = "<custom><soap>request</soap></custom>"

The Savon::SOAP::XML#xml method also accepts a block and yields a Builder::XmlMarkup instance.

namespaces = {
  "xmlns:soapenv" => "http://schemas.xmlsoap.org/soap/envelope/",
  "xmlns:blz" => "http://thomas-bayer.com/blz/"
}

soap.xml do |xml|
  xml.soapenv(:Envelope, namespaces) do |xml|
    xml.soapenv(:Body) do |xml|
      xml.blz(:getBank) do |xml|
        xml.blz(:blz, "24050110")
      end
    end
  end
end

Please take a look at the examples for some hands-on exercise.

Handling the response

Savon::Client#request returns a Savon::SOAP::Response. Everything’s really just a Hash.

response.to_hash  # => { :response => { :success => true, :name => "John" } }

Alright, sometimes it’s XML.

response.to_xml  # => "<response><success>true</success><name>John</name></response>"

The response also contains the HTTPI::Response which (obviously) contains information about the HTTP response.

response.http  # => #<HTTPI::Response:0x1017b4268 ...

In case of an emergency

By default, Savon raises both Savon::SOAP::Fault and Savon::HTTP::Error when encountering these kind of errors.

begin
  client.request :get_all_users
rescue Savon::SOAP::Fault => fault
  log fault.to_s
end

Both errors inherit from Savon::Error, so you can catch both very easily.

begin
  client.request :get_all_users
rescue Savon::Error => error
  log error.to_s
end

You can change the default of raising errors and if you did, you can still ask the response to check whether the request was successful.

response.success?     # => false
response.soap_fault?  # => true
response.http_error?  # => false

And you can access the error objects themselves.

response.soap_fault  # => Savon::SOAP::Fault
response.http_error  # => Savon::HTTP::Error

Please notice, that these methods always return an error object, even if no error exists. To check if an error occured, you can either ask the response or the error objects.

response.soap_fault.present?  # => true
response.http_error.present?  # => false

Global configuration

By default, Savon logs each SOAP request and response to STDOUT using a log level of :debug.

Savon.configure do |config|
  config.log = false            # disable logging
  config.log_level = :info      # changing the log level
  config.logger = Rails.logger  # using the Rails logger
end

Please note that disabling Savon’s logger does not disable logging of any dependant libraries.
HTTPI for example will continue to log HTTP requests and has to be configured separately.

If you don’t like to rescue errors, here’s how you can tell Savon to not raise them:

Savon.configure do |config|
  config.raise_errors = false  # do not raise SOAP faults and HTTP errors
end

And changing the default SOAP version of 1.1 to 1.2 is also fairly easy:

Savon.configure do |config|
  config.soap_version = 2  # use SOAP 1.2
end

How to date a model

Since v0.9.8, Savon ships with a very lightweight DSL to be used inside your domain models. When used, Savon::Model adds a couple of class and instance methods to work with a Savon::Client instance.

Specify the location of a WSDL document:

class User
  extend Savon::Model

  document "http://service.example.com?wsdl"
end

Or manually set the SOAP endpoint and target namespace to not use a WSDL:

class User
  extend Savon::Model

  endpoint "http://service.example.com"
  namespace "http://v1.service.example.com"
end

You can specify HTTP headers:

class User
  extend Savon::Model

  headers { "AuthToken" => "BdB)33*Rdr" }
end

As well as HTTP basic and WSSE auth credentials:

class User
  extend Savon::Model

  basic_auth "username", "password"
  wsse_auth "username", "password", :digest
end

Now for the most useful feature, you have to define the service methods you’re working with via the .actions class method. Savon::Model creates both class and instance methods for every action. These methods accept a SOAP body Hash and return a Savon::SOAP::Response. You can wrap them or just call them directly:

class User
  extend Savon::Model

  actions :get_user, :get_all_users

  def self.all
    get_all_users.to_array
  end

end

You can even overwrite them and delegate to super to call the original method:

class User
  extend Savon::Model

  actions :get_user, :get_all_users

  def get_user(id)
    super(:user_id => id).to_hash[:get_user_response][:return]
  end

end

The Savon::Client instance used by your model lives at .client inside your class. It gets initialized lazily whenever you call any other class or instance method that tries to access the client. In case you need to control how the client gets initialized, you can pass a block to .client before it’s memoized:

class User
  extend Savon::Model

  client do
    http.headers["Pragma"] = "no-cache"
  end

end

Last but not least, you can opt-out of defining any service methods and directly use the Savon::Client instance:

class User
  extend Savon::Model

  document "http://service.example.com?wsdl"

  def find_by_id(id)
    response = client.request(:find_user) do
      soap.body = { :id => id }
    end

    response.body[:find_user_response][:return]
  end

end

In case you previously used the savon_model gem, please make sure to remove it from your project as it may conflict with the new implementation.

Additional resources

Are you stuck?

Then you’re probably looking for someone to help. The Mailing list is a good place to search for useful information and ask questions. Please make sure to post your questions to the Mailing list instead of sending private messages so others can benefit from these information.

Did you run into a problem?

So you think something’s not working like it’s supposed to? Or do you need a feature that Savon doesn’t support? Take a look at the open issues over own Github to see if this has already been reported. If it has not been reported yet, please open an issue and make sure to leave useful information to debug the problem.

Anything missing in this guide?

Please fork this guide on Github and help to improve it!

Do you want to help out?

Are you looking for updates?

If you’re on Twitter, make sure to follow @savonrb for updates on bug fixes, new features and releases.

Ecosystem & alternatives

If you feel like there’s no way Savon will fit your needs, you should take a look at
The Ruby Toolbox to find an alternative.