監(jiān)理公司管理系統(tǒng) | 工程企業(yè)管理系統(tǒng) | OA系統(tǒng) | ERP系統(tǒng) | 造價(jià)咨詢管理系統(tǒng) | 工程設(shè)計(jì)管理系統(tǒng) | 甲方項(xiàng)目管理系統(tǒng) | 簽約案例 | 客戶案例 | 在線試用
X 關(guān)閉

用Perl和XML輕松開(kāi)發(fā)多種界面的Web服務(wù)

申請(qǐng)免費(fèi)試用、咨詢電話:400-8352-114

AMTeam.org

用Perl和XML輕松開(kāi)發(fā)多種界面的Web服務(wù)


 
簡(jiǎn)介

與Web服務(wù)有關(guān)的的一個(gè)基本問(wèn)題是如何創(chuàng)建一個(gè)既能夠通過(guò)基于瀏覽器的客戶端又能夠通過(guò)編程方式讓客戶端自動(dòng)訪問(wèn)應(yīng)用程序。在本文中,我們將討論如何利用Perl和XML簡(jiǎn)單地創(chuàng)建多界面的Web服務(wù)。

我們之所以選擇Perl和XML,與SOAP、XML-RPC和REST的優(yōu)缺點(diǎn)無(wú)關(guān),也不是為了試圖解決哪種工具更適合用來(lái)開(kāi)發(fā)Web服務(wù)的問(wèn)題。我們?cè)谶@里想要說(shuō)明的是,只要?jiǎng)狱c(diǎn)腦筋并使用一些Perl模塊,就可以創(chuàng)建出實(shí)用的而且能夠通過(guò)多種客戶端進(jìn)行訪問(wèn)的Web服務(wù)。

例子━━WebSemDiff:多界面的XML Semantic Diff Web服務(wù)

在本篇文章中,我們將建立XML::SemanticDiff模塊的一個(gè)Web界面。XML::SemanticDiff能夠在忽略細(xì)節(jié)的情況下比較二個(gè)XML文檔的內(nèi)容。

閱讀本文之前,我建議讀者應(yīng)當(dāng)對(duì)CGI::XMLApplication有個(gè)基本的了解。對(duì)它有一定的了解會(huì)對(duì)理解我們本篇文章的內(nèi)容有所幫助。事實(shí)上只要了解一個(gè)典型的包括三個(gè)部分的CGI::XMLApplication應(yīng)用程序就夠了:連接客戶端和應(yīng)用程序的CGI腳本、處理任務(wù)的Perl模塊以及將Perl模塊中返回的DOM樹(shù)轉(zhuǎn)換為客戶端應(yīng)用程序能夠接受的XSLT樣式表。

理解CGI::XMLApplication的基本架構(gòu)是十分重要的,因?yàn)镾OAP::Lite模塊也使用了相同的架構(gòu),開(kāi)發(fā)多客戶端訪問(wèn)應(yīng)用的根本目的在于對(duì)這二個(gè)模塊整合的理解。

首先,我們來(lái)看看CGI::XMLApplication和SOAP::Lite用來(lái)比較上傳到服務(wù)器的文件所使用的基本模塊:

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 );

在導(dǎo)入必要的模塊以及聲明軟件包與CGI::XMLApplication的繼承關(guān)系后,我們需要實(shí)現(xiàn)使瀏覽器界面工作的方法。

瀏覽器界面有二種狀態(tài):缺省狀態(tài)是提醒用戶上傳二個(gè)XML文檔進(jìn)行比較,顯示比較結(jié)果的結(jié)果狀態(tài)(或在比較時(shí)出現(xiàn)的錯(cuò)誤)。selectStylesheet()方法返回由應(yīng)用程序生成的DOM樹(shù)轉(zhuǎn)換成的樣式表的路徑。在這里我們不對(duì)semdiff_default.xsl和semdiff_result.xsl這二個(gè)樣式表進(jìn)行詳細(xì)的討論。

sub selectStylesheet {
my ( $self, $context ) = @_;
my $style = $context->{style} || 'default';
my $style_path = '/www/site/stylesheets/';
return $style_path . 'semdiff_' . $style . '.xsl';
}

缺省情況下,必需的getDOM()方法將返回一個(gè)XML::LibXML::Document對(duì)象。在向?yàn)g覽器返回結(jié)果前,由selectStylesheet()方法設(shè)定的XSLT樣式表將對(duì)該文檔對(duì)象進(jìn)行轉(zhuǎn)換。

sub getDOM {
my ( $self, $context ) = @_;
return $context->{domtree};
}

getXSLParameter()方法提供了從類向樣式表傳送值的一種方式(可以通過(guò)<xsl:param>元素獲得該值)。在這里,我們只增加所有的請(qǐng)求參數(shù),讓樣式表來(lái)選擇相關(guān)的域。

sub getXSLParameter {
my $self = shift;
return $self->Vars;
}

由于缺省狀態(tài)只是一個(gè)不要求應(yīng)用程序邏輯或特別處理的簡(jiǎn)單提示,因此我們只需實(shí)現(xiàn)對(duì)結(jié)果狀態(tài)的訪問(wèn)即可:

# 登錄事件和回調(diào)事件
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';

在為應(yīng)用程序的狀態(tài)設(shè)置合適的樣式后,我們就能夠獲得包含有上傳的XML文檔的文件句柄。我們首先檢查二個(gè)句柄是否存在,如果存在,則轉(zhuǎn)換為二個(gè)簡(jiǎn)單的標(biāo)量:

if ( defined( $fh1 ) and defined( $fh3 ) ) {
local $/ = undef;
$file1 = <$fh1>
$file2 = <$fh3>;

其次,我們創(chuàng)建包含由通過(guò)調(diào)用compare_as_dom()方法生成的比較結(jié)果的DOM樹(shù)。將這次調(diào)用封裝在一個(gè)eval塊中,以確保我們能夠獲得在處理上傳的文檔時(shí)發(fā)生的解析錯(cuò)誤。在稍后,我們將仔細(xì)地研究 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 } );
}

如果二個(gè)文檔完全相同,compare_as_dom()返回一個(gè)示定義的字符。如果沒(méi)有返回DOM對(duì)象,也沒(méi)有錯(cuò)誤產(chǎn)生,我們創(chuàng)建一個(gè)只包含告訴用戶二個(gè)文檔相同的一個(gè)<message>元素的文檔。

unless ( defined( $context->{domtree} )) {
my $msg = "Files are semantically identical.";
$context->{domtree} = $self->dom_from_data( { message => $msg } );
}
}

在完成信號(hào)收集事件后,我們就可以繼續(xù)編寫信號(hào)收集事件和SOAP調(diào)度程序共享的核心方法了。

首先,我們需要來(lái)創(chuàng)建compare()方法。它不僅僅是同名的XML::SemanticDiff的方法的容器,它還接受二個(gè)包含被比較的XML文檔的句柄并返回結(jié)果。

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()方法通過(guò)XML::Generator::PerlData對(duì)任何公用Perl數(shù)據(jù)結(jié)構(gòu)的引用進(jìn)行處理創(chuàng)建一個(gè)XML::LibXML::Document對(duì)象(DOM樹(shù)形式的XML文檔),并將生成器與XML::LibXML::SAX::Builder連接生成DOM樹(shù)。還記得嗎,我們?cè)诮Y(jié)果事件回調(diào)中調(diào)用了該方法來(lái)創(chuàng)建包含有適當(dāng)信息的DOM樹(shù)。

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;
}

最后,我們將創(chuàng)建compare_as_dom()方法。它也是最后的二個(gè)方法的容器,它以DOM樹(shù)的形式返回二個(gè)文檔的比較。

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;

在創(chuàng)建了上面的方法后,我們就僅需要?jiǎng)?chuàng)建提供能夠供各種客戶端應(yīng)用程序訪問(wèn)的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與一特定的模塊(或模塊的目錄)。在本例中,它使我們能夠重用實(shí)現(xiàn)瀏覽器界面的WebSemDiff類,模塊的共享意味著CGI只不過(guò)是一個(gè)請(qǐng)求代理,它提供了對(duì)基于連接客戶端應(yīng)用應(yīng)用程序類的方法的訪問(wèn)。通過(guò)互聯(lián)網(wǎng)瀏覽器訪問(wèn)應(yīng)用程序的用戶被提示上傳二個(gè)XML文檔,并通過(guò)compare_as_dom()方法獲取結(jié)果,SOAP客戶端只可以直接訪問(wèn)compare_as_dom、更低級(jí)的compare()等方法。

至此,我們已經(jīng)開(kāi)發(fā)了一個(gè)能夠運(yùn)行的應(yīng)用程序。下面我們就來(lái)用一些客戶端與它進(jìn)行連接,比較二個(gè)文檔,并返回相應(yīng)的結(jié)果。

為了簡(jiǎn)明起見(jiàn),我們將使被比較文檔盡量簡(jiǎn)單。第一個(gè)文檔的名字為doc1.xml:

<?xml version="1.0"?>
<root>
<el1 el1attr="good"/>
<el2 el2attr="good">Some Text</el2>
<el3/>
</root>

第二個(gè)XML文檔的名字為:doc2.xml :

<?xml version="1.0"?>
<root>
<el1 el1attr="bad"/>
<el2 bogus="true"/>
<el4>Rogue</el4>
</root>

從瀏覽器進(jìn)行訪問(wèn)

對(duì)/cgi-bin/semdiff.cgi的請(qǐng)求將提示用戶上傳二個(gè)文檔:

 
圖1


在對(duì)文件進(jìn)行比較后,結(jié)果如下:

 
圖2

從SOAP客戶端訪問(wèn)

SOAP::Lite既有服務(wù)器也有客戶端實(shí)現(xiàn)。在這里我們將使用它創(chuàng)建一個(gè)連接我們的應(yīng)用程序的SOAP界面的客戶端應(yīng)用程序。為了節(jié)約篇幅,我們將跳過(guò)與變量處理、打開(kāi)和讀取要比較的XML文檔相關(guān)的客戶端腳本,而重點(diǎn)討論與SOAP相關(guān)的部分:

#!/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";

}

將我們的二個(gè)XML文檔的路徑傳遞給該腳本代碼會(huì)產(chǎn)生下面的結(jié)果:

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的自動(dòng)調(diào)度機(jī)制來(lái)提高代碼的可讀性:

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客戶端進(jìn)行訪問(wèn)

REST架構(gòu)的愛(ài)好者會(huì)非常喜歡我們的應(yīng)用程序能夠提供訪問(wèn)未經(jīng)轉(zhuǎn)換的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)能夠?qū)⑾旅娴腦ML文檔輸出到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>

結(jié)論

在本文中我們完全沒(méi)有提到XML-RPC,原因有二個(gè):

第一,SOAP::Lite提供的XML-RPC客戶端和服務(wù)器端界面與SOAP使用的非常相似,因此使用它意義不大。

第二,與SOAP客戶端不同的是,XML-RPC客戶端沒(méi)有與它們的請(qǐng)求相關(guān)聯(lián)的標(biāo)準(zhǔn)和明確的HTTP頭部,這意味著我們的CGI請(qǐng)求代理必須采取一定的措施來(lái)區(qū)分XML-RPC客戶端和正常的互聯(lián)網(wǎng)瀏覽器。通過(guò)對(duì)POST請(qǐng)求和“text/xml”的內(nèi)容類型進(jìn)行檢查,探測(cè)XML-RPC請(qǐng)求是可能的,但這種方案是“不健壯的”。

通過(guò)本篇文章的介紹,我衷心地希望讀者能夠掌握結(jié)合利用SOAP::Lite和CGI::XMLApplication創(chuàng)建簡(jiǎn)潔、模塊化的支持通過(guò)SOAP、REST和HTML瀏覽器進(jìn)行訪問(wèn)的應(yīng)用程序的方法。

發(fā)布:2007-03-25 10:34    編輯:泛普軟件 · xiaona    [打印此頁(yè)]    [關(guān)閉]
相關(guān)文章:
上海OA系統(tǒng)
聯(lián)系方式

成都公司:成都市成華區(qū)建設(shè)南路160號(hào)1層9號(hào)

重慶公司:重慶市江北區(qū)紅旗河溝華創(chuàng)商務(wù)大廈18樓

咨詢:400-8352-114

加微信,免費(fèi)獲取試用系統(tǒng)

QQ在線咨詢