SOAP快速入门
目录 |
•SOAP快速入门 |
SOAP(简单对象访问协议)是一种远程调用远程服务器上存在的类和对象方法的协议。它是像CORBA、DCOM和XML-RPC等一系列类似项目中的最新项目。
SOAP指定了一种在XML中编码参数和返回值的标准方法,以及通过一些常见的网络协议(如HTTP(Web)和SMTP(电子邮件))传递它们的标准方法。然而,本文仅旨在快速介绍编写SOAP服务器和客户端。我们将几乎触及表面,了解所有可能的内容。
我们将使用来自CPAN的SOAP::Lite模块。不要被“Lite”后缀误导——这指的是使用模块的努力,而不是它的功能。
基于CGI的服务器编写
这是一个简单的基于CGI的SOAP服务器(hibye.cgi)
#!perl -w
use SOAP::Transport::HTTP;
SOAP::Transport::HTTP::CGI
-> dispatch_to('Demo')
-> handle;
package Demo;
sub hi {
return "hello, world";
}
sub bye {
return "goodbye, cruel world";
}
Paul Kulchenko是即将于2001年7月23日至27日在加州圣地亚哥举行的O'Reilly开源大会的特邀演讲者。抓住这个机会,在美丽的海滩前沙顿圣地亚哥酒店和码头放松身心,同时与开源领袖们交流。有关更多信息,请访问我们的会议主页。您可以在网上注册。 |
这基本上分为两部分:前四行设置了一个围绕类的SOAP包装器。从‘package Demo’开始,都是被包装的类。
在先前的规范版本(1.0)中,SOAP通过HTTP应该使用一种新的HTTP方法,M-POST。现在通常首先尝试正常的POST,如果服务器需要,再使用M-POST。如果您不了解POST和M-POST之间的区别,不用担心,您不需要了解所有具体的细节才能使用该模块。
客户端
此客户端打印出hi()
方法调用的结果(hibye.pl)
#!perl -w
use SOAP::Lite;
print SOAP::Lite
-> uri('http://www.soaplite.com/Demo')
-> proxy('http://services.soaplite.com/hibye.cgi')
-> hi()
-> result;
uri()
标识了服务器上的类,而proxy()
标识了服务器本身的地址。由于两者看起来都像URL,我将花一分钟解释它们之间的区别,因为它非常重要。
proxy()
proxy()
只是要联系以提供方法的服务器的地址。您可以使用http:、mailto:,甚至ftp: URL。
uri()
每个服务器都可以通过一个proxy() URL提供许多不同的服务。每个服务都有一个唯一的类似URI的标识符,您通过uri()方法指定给SOAP::Lite。如果您陷入SOAP文档的纠缠,那么“命名空间”对应于uri()方法。
如果您连接到互联网,您可以运行您的客户端,并且应该看到
hello, world
就这样!
如果您的返回多个值的方法(hibye.cgi)
#!perl -w
use SOAP::Transport::HTTP;
SOAP::Transport::HTTP::CGI
-> dispatch_to('Demo')
-> handle;
package Demo;
sub hi {
return "hello, world";
}
sub bye {
return "goodbye, cruel world";
}
sub languages {
return ("Perl", "C", "sh");
}
那么result()
方法只会返回第一个。要访问其余的,请使用paramsout()
方法(hibyeout.pl):
#!perl -w
use SOAP::Lite;
$soap_response = SOAP::Lite
-> uri('http://www.soaplite.com/Demo')
-> proxy('http://services.soaplite.com/hibye.cgi')
-> languages();
@res = $soap_response->paramsout;
$res = $soap_response->result;
print "Result is $res, outparams are @res\n";
此代码将生成
Result is Perl, outparams are Perl C sh
传递值
方法可以接受参数。这是一个将华氏度和摄氏度之间进行转换的SOAP服务器(temper.cgi)
#!perl -w
use SOAP::Transport::HTTP;
SOAP::Transport::HTTP::CGI
-> dispatch_to('Temperatures')
-> handle;
package Temperatures;
sub f2c {
my ($class, $f) = @_;
return 5 / 9 * ($f - 32);
}
sub c2f {
my ($class, $c) = @_;
return 32 + $c * 9 / 5;
}
下面是一个示例查询(temp.pl):
#!perl -w
use SOAP::Lite;
print SOAP::Lite
-> uri('http://www.soaplite.com/Temperatures')
-> proxy('http://services.soaplite.com/temper.cgi')
-> c2f(37.5)
-> result;
您还可以创建一个代表远程类的对象,然后在该对象上调用方法(tempmod.pl):
#!perl -w
use SOAP::Lite;
my $soap = SOAP::Lite
-> uri('http://www.soaplite.com/Temperatures')
-> proxy('http://services.soaplite.com/temper.cgi');
print $soap
-> c2f(37.5)
-> result;
自动分发
由于是Perl,做这件事的方式不止一种:SOAP::Lite提供了另一种客户端语法(tempauto.pl)。
#!perl -w
use SOAP::Lite +autodispatch =>
uri => 'http://www.soaplite.com/Temperatures',
proxy => 'http://services.soaplite.com/temper.cgi';
print c2f(37.5);
指定uri和代理参数后,您可以使用与本地相同的语法调用远程函数(例如,c2f)。这是通过UNIVERSAL::AUTOLOAD完成的,它捕获所有未知的方法调用。请注意,所有对未定义方法的调用都将尝试使用SOAP。
对象访问(这应该是“简单对象访问协议”,对吧?)
方法也可以返回真实对象。让我们扩展我们的
#!perl -w
use SOAP::Transport::HTTP;
SOAP::Transport::HTTP::CGI
-> dispatch_to('Temperatures')
-> handle;
package Temperatures;
sub f2c {
my ($class, $f) = @_;
return 5/9*($f-32);
}
sub c2f {
my ($class, $c) = @_;
return 32+$c*9/5;
}
sub new {
my $self = shift;
my $class = ref($self) || $self;
bless {_temperature => shift} => $class;
}
sub as_fahrenheit {
return shift->{_temperature};
}
sub as_celsius {
my $self = shift;
return $self->f2c( $self->{_temperature} );
}
这是一个访问此类的客户端(tempobj.pl):
#!perl -w
use SOAP::Lite;
my $soap = SOAP::Lite
-> uri('http://www.soaplite.com/Temperatures')
-> proxy('http://services.soaplite.com/temper.cgi');
my $temperatures = $soap
-> call(new => 100) # accept Fahrenheit
-> result;
print $soap
-> as_celsius($temperatures)
-> result;
具有自动分发的类似代码更短,更易于阅读(tempobja.pl):
#!perl -w
use SOAP::Lite +autodispatch =>
uri => 'http://www.soaplite.com/Temperatures',
proxy => 'http://services.soaplite.com/temper.cgi';
my $temperatures = Temperatures->new(100);
print $temperatures->as_fahrenheit();
错误处理
SOAP调用可能因多种原因而失败,例如传输错误、参数不正确或服务器错误。以下处理传输错误(例如,如果客户端和服务器之间出现网络中断,则可能发生这种情况)。所有其他错误都由fault()
方法表示(temperr.pl):
#!perl -w
use SOAP::Lite;
my $soap = SOAP::Lite
-> uri('http://www.soaplite.com/Temperatures')
-> proxy('http://services.soaplite.com/temper.cgi');
my $result = $soap->c2f(37.5);
unless ($result->fault) {
print $result->result();
} else {
print join ', ',
$result->faultcode,
$result->faultstring,
$result->faultdetail;
}
faultcode()
提供了有关错误主要原因的信息。可能的值可能包括:
客户端:您在请求中提供了不正确的信息。
当远程调用参数不正确时,可能会发生此错误。参数可能超出范围,例如,当期望正整数时提供了负数;或者类型不正确,例如,提供了字符串而不是期望的数字。
服务器:服务器端有问题。
这意味着提供的信息是正确的,但由于暂时困难(例如,数据库不可用),服务器无法处理请求。
MustUnderstand:具有mustUnderstand属性的header元素,但服务器没有理解。
服务器能够解析请求,但客户端请求的功能无法提供。例如,假设请求需要执行SQL语句,并且客户端想要确保多个请求将在一个数据库事务中执行。这可以通过具有一个公共TransactionID的三次不同调用来实现。
在这种情况下,SOAP头可能通过一个名为“TransactionID”的新头元素进行扩展,该元素在3次单独调用中携带一个公共标识符。然而,如果服务器不理解提供的TransactionID头,它可能无法在调用之间保持事务完整性。为此,客户端可以指示服务器“必须理解”元素“TransactionID”。如果服务器看到这一点并且不理解该元素的含义,它将不会尝试处理请求。
此功能使服务更可靠,分布式系统更健壮。
VersionMismatch:服务器无法理解客户端使用的SOAP版本。
这为(可能的)未来扩展提供,当SOAP的新版本可能具有不同的功能时,并且只有了解这些功能的客户端才能正确使用它。
其他错误
服务器允许创建自己的错误,例如Client.Authentication
。
faultstring()
提供可读的解释,而faultdetail()
提供对更详细信息的访问,这可能是一个字符串、对象或更复杂的结构。
例如,如果您将uri
更改为其他内容(让我们尝试使用'Test'
而不是'Temperatures'
),此代码将生成:
Client, Bad Class Name, Failed to access class (Test)
默认情况下,客户端会在传输错误时带诊断信息终止,对于失败的调用则不采取任何行动,因此,您可以从结果中获取故障信息。您可以使用on_fault()
处理程序来更改此行为,可以是针对单个对象,这样它就会在传输错误和SOAP故障(temperrh.pl)时终止。
#!perl -w
use SOAP::Lite;
my $soap = SOAP::Lite
-> uri('http://www.soaplite.com/Temperatures')
-> proxy('http://services.soaplite.com/temper.cgi')
-> on_fault(sub { my($soap, $res) = @_;
die ref $res ? $res->faultdetail : $soap->transport->status, "\n";
});
或者您可以全局设置它(temperrg.pl)
#!perl -w
use SOAP::Lite
on_fault => sub { my($soap, $res) = @_;
die ref $res ? $res->faultdetail : $soap->transport->status, "\n";
};
my $soap = SOAP::Lite
-> uri('http://www.soaplite.com/Temperatures')
-> proxy('http://services.soaplite.com/temper.cgi');
现在,将您的SOAP调用包装在eval {}
块中,并捕获传输错误和SOAP故障(temperrg.pl)
#!perl -w
use SOAP::Lite
on_fault => sub { my($soap, $res) = @_;
die ref $res ? $res->faultdetail : $soap->transport->status, "\n";
};
my $soap = SOAP::Lite
-> uri('http://www.soaplite.com/Temperatures')
-> proxy('http://services.soaplite.com/temper.cgi');
eval {
print $soap->c2f(37.5)->result;
1 } or die;
您还可以考虑这种变体,它在失败时返回undef
并设置$!
,就像许多Perl函数所做的那样(temperrv.pl)
#!perl -w
use SOAP::Lite
on_fault => sub { my($soap, $res) = @_;
eval { die ref $res ? $res->faultdetail : $soap->transport->status };
return ref $res ? $res : new SOAP::SOM;
};
my $soap = SOAP::Lite
-> uri('http://www.soaplite.com/Temperatures')
-> proxy('http://services.soaplite.com/temper.cgi');
defined (my $temp = $soap->c2f(37.5)->result) or die;
print $temp;
最后,如果您想忽略错误(然而,您仍然可以使用fault()
方法调用检查它们)
use SOAP::Lite
on_fault => sub {};
或者
my $soap = SOAP::Lite
-> on_fault(sub{})
..... other parameters
服务调度(一个服务器上的不同服务)
到目前为止,我们的CGI程序只有一个类来处理传入的SOAP调用。但我们可能有一个CGI程序将SOAP调用调度到许多类。
什么是SOAP调度?当一个SOAP请求被服务器接收时,它会绑定到请求中指定的类。这个类可能是已经在服务器端加载的(在服务器启动时,或作为先前调用的结果),或者根据服务器配置按需加载。《调度》是指确定哪个类应该处理给定的请求,并在必要时加载该类的过程。《静态》调度意味着类名在配置中指定,而《动态》调度意味着只指定一个类池,例如,在特定目录中,并且该目录中的任何类都可以访问。
想象一下,您想在服务器端提供对两个不同类的访问,并想为两者提供相同的‘代理’地址。您应该怎么做?有几种选项可供选择
静态内部
… 您已经熟悉了(hibye.cgi)
use SOAP::Transport::HTTP;
SOAP::Transport::HTTP::CGI
-> dispatch_to('Demo')
-> handle;
package Demo;
sub hi {
return "hello, world";
}
sub bye {
return "goodbye, cruel world";
}
1;
静态外部
类似于静态内部
,但是模块位于服务器代码之外(hibyeout.cgi)
use SOAP::Transport::HTTP;
use Demo;
SOAP::Transport::HTTP::CGI
-> dispatch_to('Demo')
-> handle;
以下模块当然应该在@INC列表中的一个目录中(Demo.pm)
package Demo;
sub hi {
return "hello, world";
}
sub bye {
return "goodbye, cruel world";
}
1;
动态
如您在静态内部
和静态外部
模式中看到的那样,模块名称是在服务器代码中硬编码的。但是,如果您想在不需要修改服务器的情况下动态添加新模块怎么办?动态调度允许您这样做。指定一个目录,该目录中的任何模块都可以用于调度(hibyedyn.cgi)
#!perl -w
use SOAP::Transport::HTTP;
SOAP::Transport::HTTP::CGI
-> dispatch_to('/home/soaplite/modules')
-> handle;
然后将Demo.pm
放在/home/soaplite/modules
目录中(Demo.pm)
package Demo;
sub hi {
return "hello, world";
}
sub bye {
return "goodbye, cruel world";
}
1;
就这样。现在/home/soaplite/modules
目录中的任何模块都可用,但不要忘记,客户端指定的URI应与您想调度调用到的模块/类名匹配。
混合
我们需要它做什么?不幸的是,动态调度也有一个显著的缺点:出于安全原因,动态调度禁用了对@INC的访问。为了解决这个问题,您可以将动态和静态方法结合起来。您需要做的就是这样做(hibyemix.cgi)
#!perl -w
use SOAP::Transport::HTTP;
SOAP::Transport::HTTP::CGI
-> dispatch_to('/home/soaplite/modules', 'Demo', 'Demo1', 'Demo2')
-> handle;
现在,Demo、Demo1和Demo2都从@INC中的任何位置预加载,但动态访问已启用,以便在/home/soaplite/modules
中的任何模块,并且它们将在需要时加载。
类型和名称
截至目前,Perl是一种无类型语言(在这个意义上,整数123
和字符串'123'
之间没有区别),这大大简化了从SOAP消息到Perl数据的转换过程。对于大多数简单数据,我们在这个阶段可以忽略类型。然而,这种方法也有缺点:我们在生成SOAP消息时需要提供额外的信息,因为其他服务器或客户端可能期望类型信息。SOAP::Lite不会强迫你显式地为每个参数指定类型,而是根据实际值尝试猜测每个数据类型(根据Perl的另一个口号,DWIM,“按我所想”)。
例如,具有值123
的变量在SOAP消息中成为类型为int
的元素,而具有值'abc'
的变量成为类型为string
。然而,还有更复杂的案例,例如包含二进制数据的变量,这必须进行Base64编码,或者对象(blessed references),例如,根据它们的Perl包指定类型和名称(除非指定)。
尽管自动类型化可能不适用于所有情况。例如,没有默认方法可以从值123
创建类型为string
或类型为long
的元素。你可以通过多种方式改变这种行为。首先,你可以完全禁用自动类型化(通过调用带有值为0的autotype()
),或更改不同类型的自动类型化。
或者,你可以使用SOAP::Data类的对象来显式指定特定变量的类型
my $var = SOAP::Data->type( string => 123 );
$var
成为一个类型为string
、值为123
的元素。你可以在这个变量可以在使用普通Perl变量进行SOAP调用的地方使用。这也允许你提供不仅特定的数据类型,还可以提供特定的名称和属性。
由于许多服务依赖于参数的名称(而不是位置),你可以使用相同的语法为请求参数指定名称。要为$var
变量添加名称,请调用$var->name('myvar')
,或者甚至使用type()
方法进行链式调用
my $var = SOAP::Data->type(string => 123)->name('myvar');
# -- OR --
my $var = SOAP::Data->type('string')->name(myvar => 123);
# -- OR --
my $var = SOAP::Data->type('string')->name('myvar')->value(123);
你可以始终使用value()
方法获取或设置变量的值
$var->value(321); # set new value
my $realvalue = $var->value; # store it in variable
结论
这应该足以让你开始构建SOAP应用程序。你可以阅读man手册(或者如果你勇敢的话,甚至源代码)来了解更多信息,并不要忘记定期检查www.soaplite.com以获取更多文档、示例和SOAP相关的乐趣。
本文的第二部分可以在这里找到
主要贡献者
Nathan Torkington
基本上开始了这项工作,并推动了整个过程。
Tony Hong
宝贵的评论和输入帮助我保持这些材料的最新和简单。
这篇作品继续这里
标签
反馈
这篇文章有什么问题吗?请通过在GitHub上打开问题或拉取请求来帮助我们。