Simple SOAP client and server

Just for the sake of seeing it. Sometimes when you don’t know how something works you may have some ideas that take you away from how it actually works. And how simple it is. This might be the case with a SOAP service.

SOAP Server

# app.rb

require 'sinatra'
require 'sinatra/namespace'
require 'savon'

namespace '/soap' do
  post '/service' do
    builder = Nokogiri::XML::Builder.new do |xml|
      xml.Envelope xmlns: "http://schemas.xmlsoap.org/soap/envelope/" do
        xml.Body do
          xml.myResponse do
            xml.result "Hello from SOAP Server!"
          end
        end
      end
    end

    content_type 'text/xml'
    builder.to_xml
  end

  get '/wsdl' do
    content_type 'text/xml'
    send_file 'service.wsdl'
  end
end

Now the client needs to do two things. First is knowing what is available. So it calls the /wsdl endpoint which stands for Web Services Description Language. It provides the client with the description of the service. Here is how it may look like:

# service.wsdl

<?xml version="1.0"?>
<definitions xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://example.com/soap/service" xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="MyService" targetNamespace="http://example.com/soap/service">
  <types>
    <!-- Define your data types here -->
  </types>
  <message name="MyRequest">
    <part name="parameters" type="xsd:string"/>
  </message>
  <message name="MyResponse">
    <part name="result" type="xsd:string"/>
  </message>
  <portType name="MyServicePortType">
    <operation name="MyOperation">
      <input message="tns:MyRequest"/>
      <output message="tns:MyResponse"/>
    </operation>
  </portType>
  <binding name="MyServiceBinding" type="tns:MyServicePortType">
    <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
    <operation name="MyOperation">
      <soap:operation soapAction="http://example.com/MyOperation"/>
      <input>
        <soap:body use="literal"/>
      </input>
      <output>
        <soap:body use="literal"/>
      </output>
    </operation>
  </binding>
  <service name="MyService">
    <port name="MyServicePort" binding="tns:MyServiceBinding">
      <soap:address location="http://localhost:4567/soap/service"/>
    </port>
  </service>
</definitions>

It’s bulky but this is what SOAP services are known for. You may want to read this Wikipedia page to get to know more about WSDL.

SOAP Client

require 'savon'

client = Savon.client(wsdl: 'http://localhost:4567/soap/wsdl')

puts "Available operations:"
puts client.operations

operation = :my_operation # This should match the operation name defined in your WSDL
message = { parameters: 'test' }

response = client.call(operation, message: message)

puts "Response:"
puts response.to_hash

# Available operations:
# my_operation
# Response:
# {:my_response=>{:result=>"Hello from SOAP Server!"}}

And if you try some other operation name you’ll get:

Unable to find SOAP operation: :some_other_operation (Savon::UnknownOperationError)
Operations provided by your service: [:my_operation]

Leave a Reply

Your email address will not be published. Required fields are marked *