用Perl和XML轻松开发多种界面的Web服务
用Perl和XML轻松开发多种界面的Web服务
简介
与Web服务有关的的一个基本问题是如何创建一个既能够通过基于浏览器的客户端又能够通过编程方式让客户端自动访问应用程序。在本文中,我们将讨论如何利用Perl和XML简单地创建多界面的Web服务。
我们之所以选择Perl和XML,与SOAP、XML-RPC和REST的优缺点无关,也不是为了试图解决哪种工具更适合用来开发Web服务的问题。我们在这里想要说明的是,只要动点脑筋并使用一些Perl模块,就可以创建出实用的而且能够通过多种客户端进行访问的Web服务。
例子━━WebSemDiff:多界面的XML Semantic Diff Web服务
在本篇文章中,我们将建立XML::SemanticDiff模块的一个Web界面。XML::SemanticDiff能够在忽略细节的情况下比较二个XML文档的内容。
阅读本文之前,我建议读者应当对CGI::XMLApplication有个基本的了解。对它有一定的了解会对理解我们本篇文章的内容有所帮助。事实上只要了解一个典型的包括三个部分的CGI::XMLApplication应用程序就够了:连接客户端和应用程序的CGI脚本、处理任务的Perl模块以及将Perl模块中返回的DOM树转换为客户端应用程序能够接受的XSLT样式表。
理解CGI::XMLApplication的基本架构是十分重要的,因为SOAP::Lite模块也使用了相同的架构,开发多客户端访问应用的根本目的在于对这二个模块整合的理解。
首先,我们来看看CGI::XMLApplication和SOAP::Lite用来比较上传到服务器的文件所使用的基本模块:
package WebSemDiff;
use strict;
use
CGI::XMLApplication;
use XML::SemanticDiff;
use
XML::LibXML::SAX::Builder;
use XML::Generator::PerlData;
use vars qw( @ISA );
@ISA = qw( CGI::XMLApplication
);
在导入必要的模块以及声明软件包与CGI::XMLApplication的继承关系后,我们需要实现使浏览器界面工作的方法。
浏览器界面有二种状态:缺省状态是提醒用户上传二个XML文档进行比较,显示比较结果的结果状态(或在比较时出现的错误)。selectStylesheet()方法返回由应用程序生成的DOM树转换成的样式表的路径。在这里我们不对semdiff_default.xsl和semdiff_result.xsl这二个样式表进行详细的讨论。
sub selectStylesheet {
my ( $self, $context ) = @_;
my
$style = $context->{style} || 'default';
my $style_path =
'/www/site/stylesheets/';
return $style_path . 'semdiff_' . $style .
'.xsl';
}
缺省情况下,必需的getDOM()方法将返回一个XML::LibXML::Document对象。在向浏览器返回结果前,由selectStylesheet()方法设定的XSLT样式表将对该文档对象进行转换。
sub getDOM {
my ( $self, $context ) = @_;
return
$context->{domtree};
}
getXSLParameter()方法提供了从类向样式表传送值的一种方式(可以通过<xsl:param>元素获得该值)。在这里,我们只增加所有的请求参数,让样式表来选择相关的域。
sub getXSLParameter {
my $self = shift;
return
$self->Vars;
}
由于缺省状态只是一个不要求应用程序逻辑或特别处理的简单提示,因此我们只需实现对结果状态的访问即可:
# 登录事件和回调事件
sub registerEvents {
return qw( semdiff_result
);
}
sub event_semdiff_result {
my ( $self, $context ) = @_;
my
( $file1, $file2, $error );
my $fh1 = $self->upload('file1');
my $fh3 =
$self->upload('file2');
$context->{style} = 'result';
在为应用程序的状态设置合适的样式后,我们就能够获得包含有上传的XML文档的文件句柄。我们首先检查二个句柄是否存在,如果存在,则转换为二个简单的标量:
if ( defined( $fh1 ) and defined( $fh3 ) ) {
local $/ =
undef;
$file1 = <$fh1>
$file2 = <$fh3>;
其次,我们创建包含由通过调用compare_as_dom()方法生成的比较结果的DOM树。将这次调用封装在一个eval块中,以确保我们能够获得在处理上传的文档时发生的解析错误。在稍后,我们将仔细地研究 compare_as_dom()和dom_from_data()方法。
eval {
$context->{domtree} = $self->compare_as_dom( $file1,
$file2 );
};
if ( $@ ) {
$error = $@;
}
}
else {
$error = 'You
must select two XML files to compare
and wait for them to finish
uploading';
}
if ( $error ) {
$context->{domtree} = $self->dom_from_data( {
error => $error } );
}
如果二个文档完全相同,compare_as_dom()返回一个示定义的字符。如果没有返回DOM对象,也没有错误产生,我们创建一个只包含告诉用户二个文档相同的一个<message>元素的文档。
unless ( defined( $context->{domtree} )) {
my $msg = "Files
are semantically identical.";
$context->{domtree} = $self->dom_from_data( {
message => $msg } );
}
}
在完成信号收集事件后,我们就可以继续编写信号收集事件和SOAP调度程序共享的核心方法了。
首先,我们需要来创建compare()方法。它不仅仅是同名的XML::SemanticDiff的方法的容器,它还接受二个包含被比较的XML文档的句柄并返回结果。
sub compare {
my $self = shift;
my ( $xmlstring1,
$xmlstring2 ) = @_;
my $diff = XML::SemanticDiff->new( keeplinenums => 1
);
my @results = $diff->compare( $xmlstring1, $xmlstring2 );
return
@results;
}
dom_from_data()方法通过XML::Generator::PerlData对任何公用Perl数据结构的引用进行处理创建一个XML::LibXML::Document对象(DOM树形式的XML文档),并将生成器与XML::LibXML::SAX::Builder连接生成DOM树。还记得吗,我们在结果事件回调中调用了该方法来创建包含有适当信息的DOM树。
sub dom_from_data {
my ( $self, $ref ) = @_;
my $builder =
XML::LibXML::SAX::Builder->new();
my $generator =
XML::Generator::PerlData->new( Handler => $builder );
my $dom =
$generator->parse( $ref );
return $dom;
}
最后,我们将创建compare_as_dom()方法。它也是最后的二个方法的容器,它以DOM树的形式返回二个文档的比较。
sub compare_as_dom {
my $self = shift;
my $diff_messages =
$self->compare( @_ );
return undef unless scalar( @{$diff_messages} ) >
0;
return $self->dom_from_data( { difference => $diff_messages }
);
}
1;
在创建了上面的方法后,我们就仅需要创建提供能够供各种客户端应用程序访问的CGI脚本了,这也是需要综合利用CGI::XMLApplication和SOAP::Lite 的地方。
#!/usr/bin/perl -w
use strict;
use
SOAP::Transport::HTTP;
use WebSemDiff;
if ( defined( $ENV{'HTTP_SOAPACTION'} ))
{
SOAP::Transport::HTTP::CGI
-> dispatch_to('WebSemDiff')
->
handle;
}
else {
my $app = WebSemDiff->new();
$app->run();
}
SOAP::Lite的dispatch_to()方法连接SOAP与一特定的模块(或模块的目录)。在本例中,它使我们能够重用实现浏览器界面的WebSemDiff类,模块的共享意味着CGI只不过是一个请求代理,它提供了对基于连接客户端应用应用程序类的方法的访问。通过互联网浏览器访问应用程序的用户被提示上传二个XML文档,并通过compare_as_dom()方法获取结果,SOAP客户端只可以直接访问compare_as_dom、更低级的compare()等方法。
至此,我们已经开发了一个能够运行的应用程序。下面我们就来用一些客户端与它进行连接,比较二个文档,并返回相应的结果。
为了简明起见,我们将使被比较文档尽量简单。第一个文档的名字为doc1.xml:
<?xml version="1.0"?>
<root>
<el1 el1attr="good"/>
<el2
el2attr="good">Some Text</el2>
<el3/>
</root>
第二个XML文档的名字为:doc2.xml :
<?xml version="1.0"?>
<root>
<el1 el1attr="bad"/>
<el2
bogus="true"/>
<el4>Rogue</el4>
</root>
从浏览器进行访问
对/cgi-bin/semdiff.cgi的请求将提示用户上传二个文档:
图1
在对文件进行比较后,结果如下:
图2
从SOAP客户端访问
SOAP::Lite既有服务器也有客户端实现。在这里我们将使用它创建一个连接我们的应用程序的SOAP界面的客户端应用程序。为了节约篇幅,我们将跳过与变量处理、打开和读取要比较的XML文档相关的客户端脚本,而重点讨论与SOAP相关的部分:
#!/usr/bin/perl -w
use strict;
use
SOAP::Lite;
...
my $soap = SOAP::Lite
->
uri('http://my.host.tld/WebSemDiff')
->
proxy('http://my.host.tld/cgi-bin/semdiff.cgi')
-> on_fault(
&fatal_error );
my $result = $soap->compare( $file1, $file2 )->result;
print "Comparing $f1 and $f2...n";
if ( defined $result and scalar( @{$result} ) == 0 ) {
print
"Files are semantically identicaln";
exit;
}
foreach my $diff ( @{$result} ) {
print $diff->{context} . '
' .
$diff->{startline} . ' - ' .
$diff->{endline} . ' '
.
$diff->{message} .
"n";
}
将我们的二个XML文档的路径传递给该脚本代码会产生下面的结果:
Comparing docs/doc1.xml and docs/doc2.xml...
/root[1]/el1[1]
3 - 3 Attribute 'el1attr' has different value in element
'el1'.
/root[1]/el2[1] 4 - 4 Character differences in element
'el2'.
/root[1]/el2[1] 4 - 4 Attribute 'el2attr' missing from element
'el2'.
/root[1]/el2[1] 4 - 4 Rogue attribute 'bogus' in element
'el2'.
/root[1] 5 - 5 Child element 'el3' missing from element
'/root[1]'.
/root[1] 5 - 5 Rogue element 'el4' in element '/root[1]'.
另外,我们可以使用SOAP::Lite的自动调度机制来提高代码的可读性:
use SOAP::Lite +autodispatch =>
uri =>
'http://my.host.tld/WebSemDiff',
proxy
=>'http://my.host.tld/cgi-bin/semdiff.cgi',
on_fault => &fatal_error
;
my $result = SOAP->compare( $file1, $file2 );
print "Comparing $f1 and $f2...n";
# etc ..
从RESTful客户端进行访问
REST架构的爱好者会非常喜欢我们的应用程序能够提供访问未经转换的XML文档。
#!/usr/bin/perl -w
use strict;
use
HTTP::Request::Common;
use LWP::UserAgent;
my ( $f1, $f2 ) = @ARGV;
usage() unless defined $f1 and -f $f1
and defined $f2 and -f
$f2;
my $ua = LWP::UserAgent->new;
my $uri = "http://my.host.tld/cgi-bin/semdiff.cgi";
my $req = HTTP::Request::Common::POST( $uri,
Content_Type
=> 'form-data',
Content => [
file1 => [ $f1 ],
file2 => [ $f2
],
passthru => 1,
semdiff_result => 1,
]
);
my $result =
$ua->request( $req );
if ( $result->is_success ) {
print
$result->content;
}
else {
warn "Request Failure: " . $result->message
. "n";
}
sub usage {
die "Usage:nperl $0 file1.xml file2.xml
n";
}
该脚本(restful_semdiff.pl)能够将下面的XML文档输出到STDOUT:
<?xml version="1.0"
encoding="UTF-8"?>
<document>
<difference>
<context>/root[1]/el1[1]</context>
<message>
Attribute
'el1attr' has different
value in element
'el1'.
</message>
<startline>3</startline>
<endline>3</endline>
</difference>
<difference>
<context>/root[1]/el2[1]</context>
<message>
Character
differences in element
'el2'.
</message>
<startline>4</startline>
<endline>4</endline>
</difference>
...
</document>
结论
在本文中我们完全没有提到XML-RPC,原因有二个:
第一,SOAP::Lite提供的XML-RPC客户端和服务器端界面与SOAP使用的非常相似,因此使用它意义不大。
第二,与SOAP客户端不同的是,XML-RPC客户端没有与它们的请求相关联的标准和明确的HTTP头部,这意味着我们的CGI请求代理必须采取一定的措施来区分XML-RPC客户端和正常的互联网浏览器。通过对POST请求和“text/xml”的内容类型进行检查,探测XML-RPC请求是可能的,但这种方案是“不健壮的”。
通过本篇文章的介绍,我衷心地希望读者能够掌握结合利用SOAP::Lite和CGI::XMLApplication创建简洁、模块化的支持通过SOAP、REST和HTML浏览器进行访问的应用程序的方法。
- 1为你的网络服务制作文档
- 2Amazon和Google开辟Web service新纪元
- 3用户认证和数字证书为Web服务保安全
- 4知识型企业的上海OA策略(By AMT 宋亮)
- 5Web Services和J2EE:集成中的伙伴
- 6Web技术、上海OA与高等教育(by AMT 刘宇 编译)
- 7OA办公软件系统中任务管理是企业老板最关心的功能
- 8上海OA战略
- 9数字资产管理:捕获竞争优势的新方式(by AMT 刘宇 编译)
- 10经验知识的获取和作用
- 11上海OA--衰退期成长的动力
- 12WEB服务“不是”什么
- 13Web服务与二进制传输
- 14将经验和信息转化为生产力
- 15架起结构化和非结构化数据之间的桥梁(AMT 唐晓辉 编译)
- 16美公司推出下一代网络服务工具包
- 17用Perl和XML轻松开发多种界面的Web服务
- 18论文:信息系统开发过程中的上海OA(By AMT 宋亮)
- 19证据显示微软曾阻碍Sun参加网络服务标准组织
- 20Web服务之路越走越亮
- 21e信 知识生产新生态
- 22论文:知识型企业的上海OA策略(By AMT 宋亮)
- 23上海OA日趋重要
- 24联合国《2003 年电子商务与发展报告》
- 25泛普(上海)OA绩效有以下特点
- 26上海OA和信息技术有什么关系?(by AMT 仲英豪编译)
- 27Sun四处游说 要加入WS-I组织董事会
- 28OA办公软件系统中的工作“民意调查”很强大
- 29麦肯锡电子商务论丛-让老百姓轻松上网洽公,免再苦苦排队等候
- 30[经典] 如何做好上海OA实施这道大餐?(AMT 孔祥云、徐家俊、孟凡强)
成都公司:成都市成华区建设南路160号1层9号
重庆公司:重庆市江北区红旗河沟华创商务大厦18楼