使用SOAP::Lite开发Web services(Perl SOAP)
简单对象访问协议:SOAP的应用
SOAP,是由万维网联盟(W3C)制定的的一个新通讯协议:Simple Object Access Protocol(中文:简单对象访问协议)的英文缩写,目前已经得到IBM 、Ariba 、Commerce One 、SAP 、康柏、惠普等公司的支持。它能够让不同应用程序之间通过HTTP通讯协议,以 XML格式互相交换彼此的资料。由于HTTP通讯协议在网络上无所不在,而且XML解析程序又相当容易取得,所以SOAP能够很容易地被套用与开发。当然 这些便利性是有代价的:牺牲了部份运行速度,因此SOAP本身并不是用来代替原有的低级程序,但是如果程序设计师的主要考虑在于能够很容易地与其它系统相 互沟通,那么SOAP的确能够发挥它的功效。SOAP 开发工具在许多开发环境下已经可以取得了,包括 Python,Java,Visual Basic,Perl。本身具备远程过程调用 API 程序(例如 Java 的 RMI 或者微软的 COM+)开发经验的程序设计员将会发现SOAP开发工具使用起来有一种类曾似曾相识的感觉。
在这里介绍如何使用 Perl程序语言来开发网络服务(Web services),以及如何在SOAP服务器上面建立应用程序。
在开始设计一个SOAP倾听程序(listener)之前,我们得先了解一下SOAP如何处理来自客户端的请求。首先客户端会送出一个 XML文件给服务器,称为「SOAP 封装(envelope)」。服务器在接收到这个文件以后会分析这个文件,读取文件内含的类别名称(class name)与函数名称(function name),并且在这些名称与特定的Perl程序对象之间建立对应关系。在下面的范例程序中,我们建立一个称为World的类别,内含两个函数 HelloWorld 与 GoodbyeWorld:
#server side code
package World;
sub new {
bless {}, shift;
};
sub HelloWorld {
my ($self) = @_;
return "Hello World\n";
};
sub GoodByeWorld {
my ($self,$adjective) = @_;
return "Goodbye $adjective World\n";
}
1;
虽然我们不太可能实际看见一个SOAP请求(request)长得是什么样子,但是了解这些SOAP请求内容的幕后工作方式,对于程序的侦错将会很有帮 助。在这里,我们用下面这个非常简单的例子来说明SOAP客户端如何对前面提到的World类别提出请求,并且呼叫里面的 HelloWorld 函数:
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENC=http://schemas.xmlsoap.org/soap/encoding/
SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:xsi=http://www.sorw.org/2001/XMLSchema-instance xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsd="http://www.sorw.org/2001/XMLSchema">
<SOAP-ENV:Body>
<namesp1:HelloWorld xmlns:namesp1="World"/>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
你可以在这个XML文件中很清楚地看见封包与主体(body)开始的地方:HelloWorld 函数是在 body 里面被呼叫的。在该行叙述中,命名空间(namespace)属性的值就是我们要呼叫的函数的名称,而XML命名空间(XML namespace)属性的值就是该函数所隶属的对象类别的名称。在这个例子里面,我们呼叫了World对象类别的 HellowWorld 函数。当我们要呼叫 GoodByeWorld 函数的时候,大部分内容都不需修改,只要将 body 的部分稍作修改即可:
<SOAP-ENV:Body>
<namesp2:GoodByeWorld xmlns:namesp2="World">
<c-gensym9 xsi:type="xsd:string">cruel</c-gensym9>
</namesp2:GoodByeWorld>
</SOAP-ENV:Body>
由于SOAP本身是通过HTTP通讯协议来传递,因此我们可以直接利用Perl本身提供的Web服务器相关功能。首先编写一个简单的CGI程序,这个程序 会把接收到的请求传递给我们之前写好的World类别,并且把SOAP产生的响应信息回传给浏览器。在开始执行之前请别忘记确认你的服务器支持CGI程 序,并且要把该程序的权限设定为允许执行。下面这个CGI程序会接收SOAP请求,并且传递给我们之前写好的World类别:
#server side code
#!/usr/bin/perl
use SOAP::Transport::HTTP;
use World;
SOAP::Transport::HTTP::CGI
->dispatch_to('World')
-> handle;
我们要做的就只有这些,剩下的部分全都交给SOAP模块来处理就行了。你必须为每个你打算呼叫的SOAP类别都分别写一个类似上面的CGI程序。
SOAP::Lite 也可以根据所要求的名称来动态加载不同的模块,这项功能可以让我们编写一个能够加载某个目录中任何一个模块的CGI程序。这种做法比较缺乏安全性,而且也 比较不容易控制究竟要加载哪个模块,不过把各个SOAP模块统一放置在同一个目录下,在档案管理上的确会比较方便。要动态加载不同的SOAP模块,我们必 须把相关的 use World 叙述拿掉,并且把存放SOAP模块档案的目录路径当成dispatch_to() 方法的第一个参数:
#!/usr/bin/perl
use SOAP::Transport::HTTP;
SOAP::Transport::HTTP::CGI
->dispatch_to('/home/httpd/soap_modules/')
-> handle;
SOAP客户端程序就像SOAP服务器程序一样都很容易编写。你只需要加载 SOAP::Lite 模块,并且知道一些远程服务(或者说「端点 endpoint」)的相关信息就行了。事实上,收集远程服务的相关信息可能正是最困难的部分。你必须知道远程服务器的URL,服务器所提供服务内含方法 的命名空间URI,以及这些方法的名称与需要传入的参数。一旦取得了这些信息,我们就可以开始编写一个SOAP客户端程序了。在下面的范例程序里面,我为 前面刚建立好的SOAP服务器写一个客户端程序:
#client side code
use SOAP::Lite;
my $s = SOAP::Lite
->uri('World')
->proxy('http://soapserver.mycompany.com/soap/soapserver.cgi')
->HelloWorld();
print $s->result();
my $s = SOAP::Lite
->uri('World')
->proxy('http://soapserver.mycompany.com/soap/soapserver.cgi')
->GoodByeWorld("cruel");
print $s->result();
在这个程序里面,uri 接受的参数是远程服务器上面我们打算呼叫的类别名称。但是并非每个SOAP服务器在实际运作上都遵循这个惯例,因此你可能必须另外确认一下这里的URI所 应该使用的正确值。proxy 方法接受的参数是远程服务器上面的SOAP处理程序(handler)的 URL。在每一次试图联机到服务器的时候都必须要提供前面提到的URI以及 proxy,才能够正确联机成功。最后我们便可以呼叫远程服务器上面的方法了,在这个例子里面我们呼叫了 HelloWorld() 与 GoodByeWorld("cruel") 这两个方法,并且通过 result() 函数来取得服务器的响应信息。请留意虽然我们呼叫的两个方法都是来自于World这个类别,但是在这里我们通过两次独立的交易来建立了两个World类别 的实体(instance)。这表示两个实体之间并无法得知彼此目前的状态,因此它们之间无法相互沟通。举例来说,你不能在其中一个实体上面设定一个 class的值,然后试图在另一个实体上面取得这个值。
SOAP 服务程序可能还会要求客户端提供一个特定的 SOAPAction 字段,这个参数会通过HTTP表头来传送。要设定这个参数的值,你可以使用 on_action 方法来盖过预设的处理方式。请看下面这个范例程序:
#client side code
my $s = SOAP::Lite
->uri('World')
->on_action(sub { return "/Action#Action" })
->proxy('http://soapserver.mycompany.com/soap/soapserver.cgi')
->GoodByeWorld("cruel");
print $s->result();
在实际运作中,如果你需要了解一些侦错信息,SOAP::Lite 模块提供了一个选项来观看客户端与服务器之间通讯状况的详细内容。这个选项会显示整个请求过程的详细信息,在追踪程序错误的时候非常有用。错误信息最常出 自于不正确的URI或者 proxy 设定值,这些信息都会出现在SOAP封包(packet)的文字内容里面。要使用这项功能,只要在 use SOAP::Lite 叙述后面加上 +trace => all,就可以把侦错信息显示在标准错误输出串流(STDERR output stream)上面了。
#client side code
use SOAP::Lite +trace => all;
my $s = SOAP::Lite
->uri('World')
->proxy('http://soapserver.mycompany.com/soap/soapserver.cgi')
->GoodByeWorld("cruel");
print $s->result();
SOAP::Lite 模块也提供了一个叫做 Autodispatch 的功能,它可以让你在Perl程序里面直接呼叫远程服务器提供的方法。一旦启动了 Autodispatch 功能,当你呼叫程序里面没有定义的方法的时候,这些呼叫都会自动被传送到远程服务器并且在上面执行。Autodispatch 功能让SOAP不留痕迹地整合在Perl程序里面,只要连结上远程服务器,之后就可以直接呼叫服务器上面提供的各种方法,而不需要先参照到SOAP对象。
#client side code
use SOAP::Lite +autodispatch =>
uri=>"World",
proxy=>'http://soapserver.mycompany.com/soap/soapserver.cgi';
print HelloWorld();
print GoodByeWorld("sweet");
在上面这个程序里面,由于 HelloWorld() 与 GoodByeWorld 这两个方法在我们的Perl程序里面都没有定义,因此它们会直接被传送到 proxy 所设定的远程服务器上面去执行。使用 Autodispatch 功能就像是继承其它的类别一样,因此在程序里面呼叫远程服务器提供的方法的时候,请先确认远程服务器上面的确已经定义好这些方法以供使用了。