XML與現(xiàn)代CGI應(yīng)用程序
發(fā)表時(shí)間:2024-06-18 來(lái)源:明輝站整理相關(guān)軟件相關(guān)文章人氣:
[摘要]簡(jiǎn)介 Perl的普及與互聯(lián)網(wǎng)的蓬勃發(fā)展有直接的關(guān)系。在互聯(lián)網(wǎng)發(fā)展的早期,人們發(fā)現(xiàn)僅僅使用靜態(tài)的HTML文檔不能生成有效的交互式環(huán)境,于是引進(jìn)了公用網(wǎng)關(guān)接口(CGI)的概念。Perl強(qiáng)大的功能和容易擴(kuò)充的特性使得它成為開(kāi)發(fā)CGI應(yīng)用最自然的選擇,并由此迅速地成為CGI腳本的首選語(yǔ)言。CGI本身并非十...
簡(jiǎn)介
Perl的普及與互聯(lián)網(wǎng)的蓬勃發(fā)展有直接的關(guān)系。在互聯(lián)網(wǎng)發(fā)展的早期,人們發(fā)現(xiàn)僅僅使用靜態(tài)的HTML文檔不能生成有效的交互式環(huán)境,于是引進(jìn)了公用網(wǎng)關(guān)接口(CGI)的概念。Perl強(qiáng)大的功能和容易擴(kuò)充的特性使得它成為開(kāi)發(fā)CGI應(yīng)用最自然的選擇,并由此迅速地成為CGI腳本的首選語(yǔ)言。CGI本身并非十全十美。但由于得到了眾多開(kāi)發(fā)商的青睞,CGI的應(yīng)用至今仍然十分廣泛,而且沒(méi)有跡象表明在近期會(huì)“退休”。
CGI::XMLApplication提供了一個(gè)基于XML、可以作為傳統(tǒng)CGI腳本的模塊。典型的CGI::XMLApplication腳本包括三部分:一個(gè)很小的提供對(duì)該應(yīng)用程序訪(fǎng)問(wèn)支持的可執(zhí)行腳本、實(shí)現(xiàn)各種管理者方法的邏輯模塊、根據(jù)應(yīng)用狀態(tài)可能有一個(gè)或多個(gè)XSLT樣式表,XSLT樣式表能夠?qū)⒛K返回的結(jié)果轉(zhuǎn)化成瀏覽器可以向用戶(hù)顯示的格式。
下面我們通過(guò)例子來(lái)簡(jiǎn)要地介紹CGI::XMLApplication的應(yīng)用。
例1:CGI XSLT網(wǎng)關(guān)
CGI::XMLApplication假定,參與一個(gè)項(xiàng)目的設(shè)計(jì)和開(kāi)發(fā)人員使用XSLT樣式表分離應(yīng)用的邏輯和表示,這樣可以使這種分離顯得非常直接,也不會(huì)對(duì)項(xiàng)目帶來(lái)影響。開(kāi)發(fā)人員只要能夠使setStylesheet返回符合當(dāng)前應(yīng)用狀態(tài)的XSLT樣式表的位置即可。應(yīng)用建立的DOM樹(shù)的轉(zhuǎn)換、XSLT參數(shù)向轉(zhuǎn)換引擎的傳遞、轉(zhuǎn)換后內(nèi)容向?yàn)g覽器的傳輸對(duì)用戶(hù)而言都是透明的。
為了重點(diǎn)說(shuō)明這種分離,我們的第一個(gè)例子不是傳統(tǒng)意義上的Web應(yīng)用,而是一個(gè)通用的XSLT網(wǎng)關(guān),它可以添加到服務(wù)器的cgi-bin中,將整個(gè)XML內(nèi)容的目錄樹(shù)轉(zhuǎn)化為符合請(qǐng)求的瀏覽器的格式,而這一切對(duì)于用戶(hù)、樣式表和文檔的作者而言也都是透明的。
第一步是建立連接客戶(hù)端的請(qǐng)求和應(yīng)用的CGI腳本。我們希望XML文檔能夠方便地通過(guò)URL瀏覽,并使創(chuàng)建這些文檔間的超鏈接非常直觀。因此,我們將創(chuàng)建一個(gè)沒(méi)有擴(kuò)展名的CGI腳本,以便將它作為URL路徑中的一個(gè)節(jié)點(diǎn),節(jié)點(diǎn)右邊的所有內(nèi)容將在包含XML內(nèi)容的虛擬文檔環(huán)境中進(jìn)行解釋。在這種情況下,我們將CGI稱(chēng)作是樣式表選擇者。
use strict;
use lib '/path/to/secure/webapp/libs';
use XSLGateway;
use CGI qw(:standard);my $q = CGI->new();
my %context = ();
my $gateway_name = 'stylechooser';
在加載合適的模塊和設(shè)置一些在整個(gè)腳本范圍內(nèi)有效的變量后,我們開(kāi)始向被傳遞給處理該應(yīng)用邏輯的類(lèi)的%context中添加一些域。在這個(gè)應(yīng)用軟件中,我們只傳輸要求的指向腳本文件路徑右邊的URL(REQUEST條目)和包含有存儲(chǔ)在查詢(xún)參數(shù)style中的數(shù)據(jù)的STYLE關(guān)健字。
$context{REQUEST} = $q->url(-path => 1);
$context{REQUEST} =~ s/^$gateway_name\/?//;
$context{REQUEST} = 'index.xml';
$context{STYLE} = $q->param('style') if $q->param('style');
最后,我們創(chuàng)建了XSLGateway邏輯類(lèi)的一個(gè)實(shí)例,并通過(guò)調(diào)用其run方法處理請(qǐng)求,將%context作為唯一的參數(shù)。
my $app = XSLGateway->new();
$app->run(%context);
CGI腳本就完成了。下面我們創(chuàng)建完成大部分工作的XSLGateway模塊:
package XSLGateway;
use strict;
use vars qw(@ISA);
use CGI::XMLApplication;
use XML::LibXML;
@ISA = qw(CGI::XMLApplication);
象我在簡(jiǎn)介中提到的那樣,CGI::XMLApplication通過(guò)事件調(diào)用起作用:應(yīng)用程序類(lèi)中一個(gè)給定的方法的執(zhí)行依賴(lài)于一個(gè)指定域的輸入(一般情況下是用來(lái)提交表格的按鈕的名字。),必須執(zhí)行二種調(diào)用方法:selectStylesheet和requestDOM方法。
selectStylesheet返回有關(guān)的XSLT樣式表的全文件系統(tǒng)路徑。為了簡(jiǎn)單起見(jiàn),我們假定樣式表將保存在一個(gè)單一的目錄中。我們可以通過(guò)$context->{STYLE}域提供其他的樣式表,從而增加系統(tǒng)的靈活性。
sub selectStylesheet {
my $self = shift;
my $context = shift;
my $style = $context->{STYLE} 'default';
my $style_path = '/opt/www/htdocs/stylesheets/';
return $style_path . $style . '.xsl';
}
下一步,我們需要?jiǎng)?chuàng)建requestDOM方法,該方法將返回被傳輸?shù)腦ML文檔的XML::LibXML DOM表達(dá)式。由于我們的網(wǎng)關(guān)只適用于靜態(tài)文件,我們需要使用XML::LibXML對(duì)文檔進(jìn)行解析,并返回結(jié)果樹(shù)。
sub requestDOM {
my $self = shift;
my $context = shift;
my $xml_file = $context->{REQUEST} 'index.xml';
my $doc_path = '/opt/www/htdocs/xmldocs/';
my $requested_doc = $doc_path . $xml_file;
my $parser = XML::LibXML->new;
my $doc = $parser->parse_file($requested_doc);
return $doc;
}
至此,我們的CGI腳本已經(jīng)可以安全地在服務(wù)器的cgi-bin目錄中安全地運(yùn)行了,并在一些適當(dāng)?shù)哪夸浿猩陷d一些XML文檔和一個(gè)或二個(gè)XSLT樣式表。下面我們就可以開(kāi)始檢驗(yàn)我們的工作成果了。對(duì)http://localhost/cgi-bin/stylechooser/mydocs/somefile.xml的請(qǐng)求將會(huì)使互聯(lián)網(wǎng)服務(wù)器從/opt/www/htdocs/xmldocs/目錄中選取mydocs/somefile.xml文件,使用/opt/www/htdocs/stylesheets/中的樣式表default.xsl對(duì)該文件進(jìn)行轉(zhuǎn)換,并將它傳輸給客戶(hù)。
如果需要,我們可以擴(kuò)充這一基本的框架,例如,可以在樣式表選擇CGI腳本程序添加一些查找組件,選擇合適的樣式表,可以設(shè)置或讀取HTTP cookies,對(duì)網(wǎng)站進(jìn)行修飾。
例2:一個(gè)簡(jiǎn)單的購(gòu)物系統(tǒng)
在該例子中,我們將使用CGI::XMLApplication創(chuàng)建一個(gè)簡(jiǎn)化的Web應(yīng)用程序,購(gòu)物系統(tǒng)。
與上個(gè)例子相同,這個(gè)應(yīng)用程序中與CGI-BIN有關(guān)的部分仍然非常地少。我們所需要作的只不過(guò)是初始化CustomerOrder應(yīng)用類(lèi)并調(diào)用它的run()方法。這次,我們將CGI.pm中Vars作為%context的PARAMS域:
use strict;
use CGI qw(:standard);
use lib '/path/to/secure/webapp/libs';
use CustomerOrder;
my $q = CGI->new();
my %context = ();
$context{PARAMS} = $q->Vars;
my $app = CustomerOrder->new();
$app->run(%context);
在這個(gè)例子中,我們假定該應(yīng)用中的產(chǎn)品信息存儲(chǔ)在關(guān)系數(shù)據(jù)庫(kù)中,產(chǎn)品清單不是太長(zhǎng),使我們?cè)趹?yīng)用中不會(huì)出現(xiàn)多屏才能顯示相關(guān)信息的麻煩:用戶(hù)輸入訂購(gòu)的產(chǎn)品數(shù)量的主要數(shù)據(jù)輸入屏,顯示訂購(gòu)單內(nèi)容和所選物品總價(jià)格的確認(rèn)屏,顯示訂單已經(jīng)處理的提示。為了簡(jiǎn)單起見(jiàn),我們?cè)谶@里沒(méi)有涉及送貨和財(cái)務(wù)數(shù)據(jù)的輸入等問(wèn)題。
package CustomerOrder;
use strict;
use vars qw(@ISA);
use CGI::XMLApplication;
use XML::LibXML::SAX::Builder;
use XML::Generator::DBI;
use DBI;
@ISA = qw(CGI::XMLApplication);
在加載必要的模塊和定義從CGI::XMLAplication中繼承的類(lèi)后,我們開(kāi)始創(chuàng)建應(yīng)用中與各種狀態(tài)有關(guān)的事件調(diào)用。首先,我們必須通過(guò)創(chuàng)建registerEvents()方法注冊(cè)這些事件。在本例中,我們將注冊(cè)order_confirm 和order_send方法,這二個(gè)方法設(shè)置%context中的SCREENSTYLE域。稍后,我們將利用該屬性定義在顯示客戶(hù)端的數(shù)據(jù)時(shí)應(yīng)該使用三個(gè)XSLT樣式表中的哪一個(gè)。
需要注意的是,這些事件將被映射到實(shí)現(xiàn)它們的實(shí)際的子程序中,子程序的命名規(guī)則是event_<事件名>,例如,order_confim事件是由event_order_confim執(zhí)行的。另外,還需要注意的是,各種事件的選擇是由CGI::XMLApplication根據(jù)其查找一個(gè)與注冊(cè)事件同名的表格參數(shù)的能力進(jìn)行的。例如,要執(zhí)行order_confirm事件,表格組件中必須包含一個(gè)提交非空值的名字為order_confirm的表格域。
# 事件的注冊(cè)和事件調(diào)用
sub registerEvents {
return qw( order_confirm order_send );
}
sub event_order_confirm {
my ($self, $context) = @_;
$context->{SCREENSTYLE} = 'order_confirm.xsl';
}
sub event_order_send {
my ($self, $context) = @_;
$context->{SCREENSTYLE} = 'order_send.xsl';
}
如果沒(méi)有請(qǐng)求執(zhí)行其他的事件,則缺省地執(zhí)行event_default。在本例中,我們只使用它將SCREENSTYLE域設(shè)定為一個(gè)合適的值。
sub event_default {
my ($self, $context) = @_;
$context->{SCREENSTYLE} = 'order_default.xsl';
}
每次請(qǐng)求都會(huì)執(zhí)行event_init方法,而且總是在其他方法之前執(zhí)行它,這使得它非常適合對(duì)應(yīng)用中被其他事件使用的部分進(jìn)行初始化。在本例中,我們使用它返回利用fetch_recordset()方法從數(shù)據(jù)庫(kù)中獲取的產(chǎn)品信息的、最初的DOM樹(shù)。
sub event_init {
my ($self, $context) = @_;
$context->{DOMTREE} = $self->fetch_recordset();
}
state-handler方法完成后,我們需要執(zhí)行必需的selectStylesheet和requestDOM方法。
與在第一個(gè)例子中一樣,我們假設(shè)所有的應(yīng)用的樣式表都存儲(chǔ)在服務(wù)器上相同的目錄中。我們所需要作的是返回$context->{SCREENSTYLE}的值所指定的路線(xiàn),并添加到末尾。
# app config and helpers
sub selectStylesheet {
my ($self, $context) = @_;
my $style = $context->{SCREENSTYLE};
my $style_path = '/opt/www/htdocs/stylesheets/cart/';
return $style_path . $style;
}
在研究requestDOM處理程序之前,我們先來(lái)詳細(xì)地研究fetch_recordset helper方法。
需要記住的是,我們要做的工作是從一個(gè)關(guān)系數(shù)據(jù)庫(kù)中選擇所訂購(gòu)產(chǎn)品的有關(guān)信息,但傳遞給XSLT處理器的數(shù)據(jù)必須是DOM樹(shù)。在本例中,我們不通過(guò)編程的方法,而是利用XML::Generator::DBI,它能夠從執(zhí)行SQL SELECT語(yǔ)句得到的數(shù)據(jù)中生成SAX數(shù)據(jù)。創(chuàng)建要求的DOM樹(shù)就是建立XML::LibXML::SAX::Builder(它從SAX事件中創(chuàng)建XML::LibXML DOM樹(shù))的實(shí)例。
sub fetch_recordset {
my $self = shift;
my $sql = 'select id, name, price from products';
my $dbh = DBI->connect('dbi:Oracle:webclients',
'chico',
'swordfish')
die "database connection couldn't
be initialized: $DBI::errstr \n";
my $builder = XML::LibXML::SAX::Builder->new();
my $gen = XML::Generator::DBI->new(Handler => $builder,
dbh => $dbh,
RootElement => 'document',
QueryElement => 'productlist',
RowElement => 'product');
my $dom = $gen->execute($sql) die "Error Building DOM Tree\n";
return $dom;}
fetch_recordset方法完成了另一項(xiàng)很重要的任務(wù),但它返回的DOM樹(shù)只包含我們想向客戶(hù)發(fā)送信息的一部分,我們還必須獲取用戶(hù)輸入的產(chǎn)品數(shù)量,另外,還需要提供一個(gè)訂購(gòu)產(chǎn)品的總計(jì)。
sub requestDOM {
my ($self, $context) = @_;
my $root = $context->{DOMTREE}->getDocumentElement();
my $grand_total = '0';
為了將當(dāng)前的訂貨數(shù)量作為更大的文檔的一部分,我們將遍歷所有的產(chǎn)品元素,并在每行中添加<quantity>和<item-total>子元素。<quantity>的值可以從$context->{PARAMS}域獲得。
foreach my $row ($root->findnodes('/document/productlist/product')) {
my $id = $row->findvalue('id');
my $cost = $row->findvalue('price');
my $quantity = $context->{PARAMS}->{$id} '0';
my $item_total = $quantity * $cost;
$grand_total += $item_total;
# add the order quantity and item totals to the tree.
$row->appendTextChild('quantity', $quantity);
$row->appendTextChild('item-total', $item_total);
}
最后,我們將增加一些有關(guān)訂單的元信息,方法是在具有<order-total>元素的根元素中添加一個(gè)<instance-info>元素,該元素中包含有當(dāng)前所選貨物的總價(jià)值。
$grand_total = '0.00';
my $info = XML::LibXML::Element->new('instance-info');
$info->appendTextChild('order-total', $grand_total);
$root->appendChild($info);
return $context->{DOMTREE};
}
細(xì)心的讀者可能已經(jīng)注意到,我們這個(gè)非常簡(jiǎn)單的應(yīng)用程序在order_send方法中沒(méi)有作任何實(shí)際的事。決定如何處理這些數(shù)據(jù)是產(chǎn)品訂購(gòu)應(yīng)用程序中與具體的購(gòu)物網(wǎng)站最有關(guān)的部分。
結(jié)束語(yǔ)
CGI::XMLApplication在CGI腳本程序的編程中提供了一種清晰的、模塊化的隔離系統(tǒng)的內(nèi)容和表示的方法,單就這一點(diǎn),就值得我們對(duì)它進(jìn)行一番研究。此外,它還可以使我們避免糾纏于一些細(xì)節(jié)問(wèn)題,而集中精力解決主要的問(wèn)題。