3。Ruby 和 SOAP
简单对象访问协议(SOAP)很快的成为了远程过程调用(RPC)的标准协议。(更多关于SOAP的信息可以分别参看http://www.linuxmagazine.com/2001-10/soap_04.html 和 http://www.linuxmagazine.com/2002-08/webs_01.html)
Ruby提供了对SOAP的强大支持,不管在客户端还是服务端来说都是这样的,使用SOAP4R,只需要4部分就能创建一个SOAP请求:
- 一个端点 (endpoint), 或者处理SOAP请求的网络地址,一个endpoint一般来说都是运行在WEB服务器环境中的代码,但是也有一些其它的SOAP传输,包括邮件。
- 一个命名空间(namespace), 定义了一个环境上下文,在这里解析调用的方法名。
- 一个方法名称。 远程过程调用的方法的名字。
- 一组参数。
使用SOAP4R的时候,我们需要在创建SOAP驱动的时候指定前两个参数,第三个参数则是为这个驱动绑定方法时候使用,最后的参数是调用实际需要的方法的时候使用。
比如,我们有一个处理销售订单的SOAP运行在http://my.server.com,我们要在客户端访问这个服务,先要创建一个 SOAP::Driver 对象,创建这个对象的时候需要指定命名空间和服务器地址(创建这个对象的方法的前两个参数和记录日志有关,这里我们可以不用考率)
NS = "urn:ordersService" SVR = "http://my.server.com/orders" drv = SOAP::Driver.new(nil, nil, NS, SVR)
一旦我们建立了这个驱动,就可以用它的addMethod方法增加我们需要向服务器调用的方法的名称,第一个参数是这个方法的名称,其余的参数是这个方法需要的参数名,这里,我们需要访问的方法名为orders_for_product(译者注:此处是否应该是orders_for?),传给它的参数十客户帐号和产品代码。(Ruby不用WSDL描述SOAP接口)
drv.addMethod("orders_for", "cust_acct","prod_code")
一旦所有的东西都完成,我们可以用这个方法任意次数的向服务器调用:
customers.each do |cust_acct| products.each do |prod_code| orders = drv.orders_for(cust_acct, prod_code) process(orders) end end
这个例子也要得益于Ruby语言的动态性,比如orders_for方法动态的加到了SOAP驱动对象,所以我们可以用drv.orders_for来调用这个方法。而且我们也不需要定义这个方法的返回值的形式,SOAP驱动会自动将从服务器得到的结果转换为适当地Ruby对象。
为了更详细的说明一下,我们来看一个真正的例子。清单4显示了如何用SOAP从googel搜索引擎取得查询结果,这段代码基于Ian Macdonald的google.rb。运行这个程序之前,你需要在google 上注册一下 (http://www.google.com/accounts) ,然后取得一个key,在第三行指定你得到的key。
清单4: 使用SOAP查询google 1 require "soap/driver" 2 ENDPOINT = 'http://api.google.com/search/beta2' 3 NS = 'urn:GoogleSearch' 4 KEY = "get_a_key_from_google" 5 6 fail "Missing query args" if ARGV.empty? 7 8 query = ARGV.join(" ") 9 10 soap = SOAP::Driver.new(nil, nil, NS, ENDPOINT) 11 soap.addMethodWithSOAPAction( 12 'doGoogleSearch', NS, 'key', 'q', 'start', 'maxResults', 13 'filter','restrict', 'safeSearch', 'lr', 'ie', 'oe') 14 res = soap.doGoogleSearch( 15 KEY, query, 0, 10, false, nil, false, nil, 'latin1', 'latin1') 16 17 puts "Estimated result count: " + res.estimatedTotalResultsCount 18 19 res.resultElements.each do |entry| 20 puts 21 puts "#{entry.URL}: #{entry.title}" 22 puts entry.snippet 23 end |
实际的google查询是在google的服务器上由方法doGoogleSearch执行的,这个方法接收10个参数(关于这10个参数的具体意义,可以参考google的web API文档),但是我们的例子里,我们只是指定了查询条件,其它参数我们都使用了默认值。在第11行,我们给SOAP驱动增加了方法doGoogleSearch,第14行我们调用这个方法,执行真正的查询。
从google返回的结果是很复杂的对象,从高层来看,这个结果包括这个查询本身的一些信息,比如开始和结束的索引值,查询用到的条件,查询消耗的时间等。查询结果存在一个数组里面。这和你手工在google页面上查询看到的编号的结果一样。每条查询结果记录本身也是很复杂的对象,在我们的例子里,我们只取出了它的标题,url,和一部分文本。
SOAP接口把我们的工作变得很简单,它自动创建对结果对象的迭代,创建返回结果对象的各种属性的访问方法,然后,我们可以简单的使用:
res.resultElements.each do |element| ... end
如果你在命令提示符下运行清单4的程序,查询参数为"ruby soap",结果如下:
$ ruby google_search.rb ruby language Estimated result count: 206000 http://www.ruby-lang.org/en/: <b>Ruby</b> Home Page <b>...</b> Japanese Page If you can read this oriental |
(注:此表为译者折行之后的结果,原来的内容可能超出宽度,影响阅读)
在Ruby中编写一个SOAP服务器端也是非常简单的。你所需要做的就是需要发布的接口对象,然后把这些对象发布的SOAP服务器的servlet上。你所编写的对象不需要知道SOAP的任何什么东西。比如,清单5表示的是一个简单的类,有一个简单的方法double,接收一个参数,返回两个这个参数相加的结果。
清单5: The file doubler.rb, the Ruby SOAP doubling class class Doubler def double(arg) arg + arg end end |
要在SOAP服务器中访问这个方法,我们需要把它组装到SOAP服务器的命名空间上。在Ruby中,最简单的方法是使用web服务器工具箱WEBRick。结合soaplet.rb这个servlet代码(在SOAP4R包的samples/webrick目录下),我们可以用很少的一些代码来实现一个完整的SOAP服务器,代码见清单6。
清单6: A Ruby SOAP server 1 require 'webrick' 2 require 'soaplet' 3 require 'doubler' 4 5 server = WEBrick::HTTPServer.new(:Port => 2001) 6 7 soaplet = SOAP::WEBrickSOAPlet.new 8 soaplet.addServant('urn:doublerService', Doubler.new) 9 server.mount("/doubler", soaplet) 10 11 trap("INT") { server.shutdown } 12 server.start |
前三行只是简单的引入了需要的库,soaplet,Double类等。第5行是创建一个web server必须得步骤(本例端口为2001),第7行创建了一个soaplet(一个把SOAP请求发送到一个对象的servlet)。第8行把这个servlet帮定到一个Doubler对象,第9行将这个soaplet映射到web server的/doubler。第12行启动了服务器进程序,但是第11行干什么用呢?当你启动一个WEBRick服务时,这个服务器程序将处理请求,返回结果,但是,我们想我们的服务器程序能完整的关闭,第11行就是为了完成这种功能,这一行给server注册了一个处理SIGINT信号的处理器,当收到这样的信号时,调用server的shutdown方法,在大多数操作系统下,control-c将产生SIGINT信号,所以,我们可以在命令提示符下控制我们的web 服务器。
我们可以用下面清单7中的SOAP客户端程序来测试一下服务器端。第10行还展示了rescue的另一个特点:这个语句首先尝试将参数(传过来的时候为字符串)转换为整型,如果转换失败,则rescue将会捕捉这个异常,并且返回原来的参数。这样的结果是double方法能接收的参数可以是整型的和字符串型的,让我们看看客户端运行的时候会有什么不同。
$ ruby soap_client.rb 12 wiki 24 wikiwiki $
传入参数为12,我们得到的结果为24,当我们传入字符串wiki的时候,我们得到的是wikiwiki。Ruby中类型的多态性也传播到了SOAP接口。因为double方法是arg+arg,所以如果参数为整型,返回两个数相加的结果,如果参数为字符串,则返回两个字符串连接的结果。
清单7: The doubler SOAP client 1 require "soap/driver" 2 3 SVR = 'http://localhost:2001/doubler' 4 NS = 'urn:doublerService' 5 6 soap = SOAP::Driver.new(nil, nil, NS, SVR) 7 soap.addMethod('double', 'arg') 8 9 ARGV.each do |arg| 10 arg = (Integer(arg) rescue arg) 11 puts soap.double(arg) 12 end |