Ruby Web Services
Web Services with Ruby - SOAP (Legacy Approach)
Section titled “Web Services with Ruby - SOAP (Legacy Approach)”What is SOAP?
Section titled “What is SOAP?”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 and its Successor soap4r-ng
Section titled “SOAP4R and its Successor soap4r-ng”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-ngThis command will install soap4r-ng and its dependencies.
Writing SOAP Servers with soap4r-ng
Section titled “Writing SOAP Servers with soap4r-ng”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 ...endStep 2: Define Service Methods
Section titled “Step 2: Define Service Methods”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 endendStep 3: Expose Methods in initialize
Section titled “Step 3: Expose Methods in initialize”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) ...endStep 4: Start the Server
Section titled “Step 4: Start the Server”Instantiate your server class and call its start method. You need to provide an application name, namespace URI, hostname, and port.
# Continuing from above# server = MySOAPServer.new('MyCalculatorApp', 'urn:ruby:calculator', 'localhost', 8080)# trap('INT') { server.shutdown }Complete Server Example:
#!/usr/bin/env rubyrequire '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 endend
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.startrescue StandardError => err puts "Error starting server: #{err.message}" puts err.backtrace.join("\n")endTo run this server, save it as soap_server.rb and execute ruby soap_server.rb. It will listen on http://localhost:8080.
Writing SOAP Clients with soap4r-ng
Section titled “Writing SOAP Clients with soap4r-ng”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 runningNAMESPACE = 'urn:ruby:calculator' # Namespace defined in the server
driver = SOAP::RPC::Driver.new(ENDPOINT_URL, NAMESPACE)Step 2: Add Service Methods to the Driver
Section titled “Step 2: Add Service Methods to the Driver”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')Step 3: Invoke Remote Methods
Section titled “Step 3: Invoke Remote Methods”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 rubyrequire '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")endTo 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.0100 / 4 = 25.0SOAP Fault occurred: SOAP Fault: Cannot divide by zero.Fault Code: ServerFault String: Cannot divide by zero.Further Considerations:
Section titled “Further Considerations:”- WSDL2Ruby:
soap4r-ng(and the original SOAP4R) often included a tool calledwsdl2ruby.rbwhich 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::FaultErrorfor 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_devon 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.