Skip to content

Ruby Web 服务

简单对象访问协议 (SOAP) 是一种基于 XML 的消息协议,用于在 Web 服务的实现中交换结构化信息。它依赖 XML 作为其消息格式,通常使用 HTTP 或 SMTP 进行消息协商和传输。SOAP 在过去更普遍,但由于简单性和更低的开销,在新开发中已很大程度上被使用 JSON 的 RESTful API 取代。

本教程涵盖 SOAP4R,这是 Ruby 的一个历史上的 SOAP 实现。虽然 SOAP 和 SOAP4R 在今天的新项目不常用,但理解它们对于与现有遗留系统交互可能有用。对于 Ruby 中的现代 Web 服务开发,考虑使用像 HTTParty、Faraday 这样的库来消费(调用)REST/JSON API,以及 Ruby on Rails (API 模式)、Sinatra 或 Grape 这样的框架来构建它们。

SOAP4R 是用于 Ruby 的原始 SOAP 库。它不再积极维护。一个社区维护的分支 (fork),soap4r-ng (SOAP for Ruby - Next Generation),旨在提供持续支持并与较新版本 Ruby 兼容。如果您必须在 Ruby 中使用 SOAP,soap4r-ng 是推荐的选择,而非原始的 SOAP4R。

安装 soap4r-ng:

您可以使用 RubyGems 安装 soap4r-ng:

$ gem install soap4r-ng

此命令将安装 soap4r-ng 及其依赖项。

soap4r-ng 支持构建独立的 SOAP 服务器。过程包括以下关键步骤:

步骤 1: 继承自 SOAP::RPC::StandaloneServer

Section titled “步骤 1: 继承自 SOAP::RPC::StandaloneServer”

创建一个继承自 SOAP::RPC::StandaloneServer 的新类。

require 'soap/rpc/standaloneserver'
class MySOAPServer < SOAP::RPC::StandaloneServer
# ... 服务器逻辑 ...
end

这些是您的服务器类中的常规 Ruby 方法,它们将作为 Web 服务操作暴露出来。

class MySOAPServer < SOAP::RPC::StandaloneServer
# ... (此处为 initialize 方法)
# 服务方法
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

步骤 3: 在 initialize 方法中暴露方法

Section titled “步骤 3: 在 initialize 方法中暴露方法”

在您的服务器类的 initialize 方法中,使用 add_method 来声明哪些方法是 SOAP 服务的一部分,并指定它们的名称和参数签名。

class MySOAPServer < SOAP::RPC::StandaloneServer
def initialize(*args)
super # 调用父类 initialize 方法至关重要
# add_method(接收者, soap_方法名, *参数名列表_字符串形式)
add_method(self, 'add', 'a', 'b')
add_method(self, 'divide', 'a', 'b')
# 对于更复杂的参数类型 (in, out, inout):
# add_method(self, 'complex_method', ['in', 'param1'], ['out', 'param2_out'], ['inout', 'param3_inout'])
end
# ... 服务方法 (add, divide) ...
end

实例化您的服务器类并调用其 start 方法。您需要提供应用名称、命名空间 URI、主机名和端口。

server.start
# 承接上文
# server = MySOAPServer.new('MyCalculatorApp', 'urn:ruby:calculator', 'localhost', 8080)
# trap('INT') { server.shutdown }

完整的服务器示例:

#!/usr/bin/env ruby
require 'soap/rpc/standaloneserver'
class MySOAPServer < SOAP::RPC::StandaloneServer
def initialize(*args)
super # 调用父类 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', # 应用名称
'urn:ruby:calculator', # 命名空间
server_host, # 主机
server_port) # 端口
puts "正在启动 SOAP 服务器于 #{server_host}:#{server_port}..."
# 按 Ctrl+C 优雅关闭
trap('INT') do
puts "\n正在关闭 SOAP 服务器..."
server.shutdown
end
server.start
rescue StandardError => err
puts "启动服务器出错: #{err.message}"
puts err.backtrace.join("\n")
end

要运行此服务器,将其保存为 soap_server.rb 并执行 ruby soap_server.rb。它将监听 http://localhost:8080。

使用 SOAP::RPC::Driver 类来创建 SOAP 客户端。您需要服务器的端点 URL 以及服务方法的命名空间。

步骤 1: 创建一个 SOAP::RPC::Driver 实例

Section titled “步骤 1: 创建一个 SOAP::RPC::Driver 实例”

使用端点 URL 和命名空间实例化 SOAP::RPC::Driver。

require 'soap/rpc/driver'
ENDPOINT_URL = 'http://localhost:8080/' # 您的 SOAP 服务器正在运行的地方
NAMESPACE = 'urn:ruby:calculator' # 服务器中定义的命名空间
driver = SOAP::RPC::Driver.new(ENDPOINT_URL, NAMESPACE)

步骤 2: 将服务方法添加到驱动器

Section titled “步骤 2: 将服务方法添加到驱动器”

在驱动器实例上使用 add_method 方法来定义您想要调用的远程方法,以及它们的参数名称。

# driver.add_method('远程方法名', '参数1名', '参数2名', ...)
driver.add_method('add', 'a', 'b')
driver.add_method('divide', 'a', 'b')

在驱动器对象上调用方法,就好像它们是本地方法一样。

# 结果 = driver.远程方法名(值1, 值2, ...)

完整的客户端示例:

#!/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)
# 可选:启用详细的通信日志用于调试
# driver.wiredump_dev = STDERR # 或 STDOUT,或一个文件
driver.add_method('add', 'a', 'b')
driver.add_method('divide', 'a', 'b')
# 调用远程方法
sum_result = driver.add(25, 17)
puts "25 + 17 = #{sum_result}"
quotient_result = driver.divide(100, 4)
puts "100 / 4 = #{quotient_result}"
# 调用可能抛出 SOAP Fault 的方法的示例
begin
error_result = driver.divide(10, 0)
puts "10 / 0 = #{error_result}" # 这行代码不会被执行到
rescue SOAP::FaultError => e
puts "发生了 SOAP Fault: #{e.message}"
puts "故障码: #{e.faultcode}"
puts "故障字符串: #{e.faultstring}"
end
rescue StandardError => err
puts "客户端错误: #{err.message}"
puts err.backtrace.join("\n")
end

要运行此客户端(请确保 soap_server.rb 首先正在运行),将其保存为 soap_client.rb 并执行 ruby soap_client.rb。

预期输出:

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 (以及原始的 SOAP4R) 通常包含一个名为 wsdl2ruby.rb 的工具,该工具可以从 WSDL (Web 服务描述语言) 文件生成客户端存根代码。如果 WSDL 可用,这可以简化客户端开发。
  • **错误处理:**准备好处理 SOAP::FaultError(用于处理 SOAP 服务返回的错误)以及标准网络错误。
  • **复杂性:**与 REST 相比,SOAP 涉及更复杂的 XML 消息和状态管理。调试有时可能具有挑战性;在客户端驱动器上启用 wiredump_dev 可能有帮助,通过显示交换的原始 XML 消息。

虽然本教程提供了 SOAP4R (通过 soap4r-ng) 的基本概述,但请记住,行业已很大程度上转向使用 JSON 的 REST API,以获得简单性、性能和易用性。SOAP 知识的主要价值在于维护或集成现有的遗留系统。