Skip to content

Ruby Web Services

Web Services with Ruby - SOAP (Legacy Approach)

Section titled “Web Services with Ruby - SOAP (Legacy Approach)”

The Simple Object Access Protocol (SOAP) is an XML-based messaging protocol for exchanging structured information in the implementation of web services. It relies on XML for its message format and typically uses HTTP or SMTP for message negotiation and transmission. SOAP was more prevalent in the past but has largely been superseded by RESTful APIs using JSON for new development due to simplicity and lower overhead.

This tutorial covers SOAP4R, a historical SOAP implementation for Ruby. While SOAP and SOAP4R are not commonly chosen for new projects today, understanding them can be useful for interacting with legacy systems. For modern web service development in Ruby, consider using libraries like HTTParty, Faraday for consuming REST/JSON APIs, and frameworks like Ruby on Rails (API mode), Sinatra, or Grape for building them.

SOAP4R was the original SOAP library for Ruby. It is no longer actively maintained. A community-maintained fork, soap4r-ng (SOAP for Ruby - Next Generation), aims to provide continued support and compatibility with newer Ruby versions. If you must work with SOAP in Ruby, soap4r-ng is the recommended choice over the original SOAP4R.

Installing soap4r-ng:

You can install soap4r-ng using RubyGems:

$ gem install soap4r-ng

This command will install soap4r-ng and its dependencies.

soap4r-ng supports building standalone SOAP servers. The process involves these key steps:

Step 1: Inherit from SOAP::RPC::StandaloneServer

Section titled “Step 1: Inherit from SOAP::RPC::StandaloneServer”

Create a new class that inherits from SOAP::RPC::StandaloneServer.

require 'soap/rpc/standaloneserver'
class MySOAPServer < SOAP::RPC::StandaloneServer
# ... server logic ...
end

These are regular Ruby methods within your server class that will be exposed as web service operations.

class MySOAPServer < SOAP::RPC::StandaloneServer
# ... (initialize method here)
# Service methods
def add(a, b)
return a + b
end
def divide(a, b)
raise SOAP::FaultError, "Division by zero" if b == 0
return a.to_f / b
end
end

In the initialize method of your server class, use add_method to declare which methods are part of the SOAP service, specifying their names and parameter signatures.

class MySOAPServer < SOAP::RPC::StandaloneServer
def initialize(*args)
super # Important to call super for StandaloneServer initialization
# add_method(receiver, soap_method_name, *param_names_as_strings)
add_method(self, 'add', 'num1', 'num2')
add_method(self, 'divide', 'numerator', 'denominator')
# For more complex parameter types (in, out, inout):
# add_method(self, 'complex_method', ['in', 'param1'], ['out', 'param2_out'], ['inout', 'param3_inout'])
end
# ... service methods (add, divide) ...
end

Instantiate your server class and call its start method. You need to provide an application name, namespace URI, hostname, and port.

server.start
# Continuing from above
# server = MySOAPServer.new('MyCalculatorApp', 'urn:ruby:calculator', 'localhost', 8080)
# trap('INT') { server.shutdown }

Complete Server Example:

#!/usr/bin/env ruby
require 'soap/rpc/standaloneserver'
class MySOAPServer < SOAP::RPC::StandaloneServer
def initialize(*args)
super # Call superclass initialize
add_method(self, 'add', 'a', 'b')
add_method(self, 'divide', 'a', 'b')
end
def add(a, b)
return a.to_f + b.to_f
end
def divide(a, b)
if b.to_f == 0
raise SOAP::FaultError, "Cannot divide by zero."
end
return a.to_f / b.to_f
end
end
begin
server_host = 'localhost'
server_port = 8080
server = MySOAPServer.new('CalculatorService', # App name
'urn:ruby:calculator', # Namespace
server_host, # Host
server_port) # Port
puts "Starting SOAP server on #{server_host}:#{server_port}..."
# Graceful shutdown on Ctrl+C
trap('INT') do
puts "\nShutting down SOAP server..."
server.shutdown
end
server.start
rescue StandardError => err
puts "Error starting server: #{err.message}"
puts err.backtrace.join("\n")
end

To run this server, save it as soap_server.rb and execute ruby soap_server.rb. It will listen on http://localhost:8080.

The SOAP::RPC::Driver class is used to create SOAP clients. You’ll need the server’s endpoint URL and the namespace of the service methods.

Step 1: Create a SOAP::RPC::Driver Instance

Section titled “Step 1: Create a SOAP::RPC::Driver Instance”

Instantiate SOAP::RPC::Driver with the endpoint URL and namespace.

require 'soap/rpc/driver'
ENDPOINT_URL = 'http://localhost:8080/' # Where your SOAP server is running
NAMESPACE = 'urn:ruby:calculator' # Namespace defined in the server
driver = SOAP::RPC::Driver.new(ENDPOINT_URL, NAMESPACE)

Use add_method on the driver instance to define the remote methods you want to call, along with their parameter names.

# driver.add_method('remote_method_name', 'param1_name', 'param2_name', ...)
driver.add_method('add', 'a', 'b')
driver.add_method('divide', 'a', 'b')

Call the methods on the driver object as if they were local methods.

# result = driver.remote_method_name(value1, value2, ...)

Complete Client Example:

#!/usr/bin/env ruby
require 'soap/rpc/driver'
ENDPOINT_URL = 'http://localhost:8080/'
NAMESPACE = 'urn:ruby:calculator'
begin
driver = SOAP::RPC::Driver.new(ENDPOINT_URL, NAMESPACE)
# Optional: Enable detailed wire logging for debugging
# driver.wiredump_dev = STDERR # or STDOUT, or a file
driver.add_method('add', 'a', 'b')
driver.add_method('divide', 'a', 'b')
# Call remote methods
sum_result = driver.add(25, 17)
puts "25 + 17 = #{sum_result}"
quotient_result = driver.divide(100, 4)
puts "100 / 4 = #{quotient_result}"
# Example of calling a method that might raise a SOAP Fault
begin
error_result = driver.divide(10, 0)
puts "10 / 0 = #{error_result}" # This line won't be reached
rescue SOAP::FaultError => e
puts "SOAP Fault occurred: #{e.message}"
puts "Fault Code: #{e.faultcode}"
puts "Fault String: #{e.faultstring}"
end
rescue StandardError => err
puts "Client Error: #{err.message}"
puts err.backtrace.join("\n")
end

To run this client (ensure soap_server.rb is running first), save it as soap_client.rb and execute ruby soap_client.rb.

Expected Output:

25 + 17 = 42.0
100 / 4 = 25.0
SOAP Fault occurred: SOAP Fault: Cannot divide by zero.
Fault Code: Server
Fault String: Cannot divide by zero.
  • WSDL2Ruby: soap4r-ng (and the original SOAP4R) often included a tool called wsdl2ruby.rb which could generate client-side stub code from a WSDL (Web Services Description Language) file. This can simplify client development if a WSDL is available.
  • Error Handling: Be prepared to handle SOAP::FaultError for errors returned by the SOAP service, and standard network errors.
  • Complexity: SOAP involves more complex XML messages and state management compared to REST. Debugging can sometimes be challenging; enabling wiredump_dev on the client driver can help by showing the raw XML messages exchanged.

While this tutorial provides a basic overview of SOAP4R (via soap4r-ng), remember that the industry has largely shifted towards REST APIs with JSON for simplicity, performance, and ease of use. Knowledge of SOAP is primarily valuable for maintaining or integrating with existing legacy systems.