簡介 Perl的普及與互聯(lián)網(wǎng)的蓬勃發(fā)展有直接的關系。在互聯(lián)網(wǎng)發(fā)展的早期,人們發(fā)現(xiàn)僅僅使用靜態(tài)的HTML文檔不能生成有效的交互式環(huán)境,于是引進了公用網(wǎng)關接口(CGI)的概念。Perl強大的功能和容易擴充的特性使得它成為開發(fā)CGI應用最自然的選擇,并由此迅速地成為CGI腳本的首選語言。CGI本身并非十全十美。但由于得到了眾多開發(fā)商的青睞,CGI的應用至今仍然十分廣泛,而且沒有跡象表明在近期會“退休”。
CGI::XMLApplication提供了一個基于XML、可以作為傳統(tǒng)CGI腳本的模塊。典型的CGI::XMLApplication腳本包括三部分:一個很小的提供對該應用程序訪問支持的可執(zhí)行腳本、實現(xiàn)各種管理者方法的邏輯模塊、根據(jù)應用狀態(tài)可能有一個或多個XSLT樣式表,XSLT樣式表能夠將模塊返回的結果轉化成瀏覽器可以向用戶顯示的格式。
下面我們通過例子來簡要地介紹CGI::XMLApplication的應用。
例1:CGI XSLT網(wǎng)關 CGI::XMLApplication假定,參與一個項目的設計和開發(fā)人員使用XSLT樣式表分離應用的邏輯和表示,這樣可以使這種分離顯得非常直接,也不會對項目帶來影響。開發(fā)人員只要能夠使setStylesheet返回符合當前應用狀態(tài)的XSLT樣式表的位置即可。應用建立的DOM樹的轉換、XSLT參數(shù)向轉換引擎的傳遞、轉換后內(nèi)容向瀏覽器的傳輸對用戶而言都是透明的。
為了重點說明這種分離,我們的第一個例子不是傳統(tǒng)意義上的Web應用,而是一個通用的XSLT網(wǎng)關,它可以添加到服務器的cgi-bin中,將整個XML內(nèi)容的目錄樹轉化為符合請求的瀏覽器的格式,而這一切對于用戶、樣式表和文檔的作者而言也都是透明的。
第一步是建立連接客戶端的請求和應用的CGI腳本。我們希望XML文檔能夠方便地通過URL瀏覽,并使創(chuàng)建這些文檔間的超鏈接非常直觀。因此,我們將創(chuàng)建一個沒有擴展名的CGI腳本,以便將它作為URL路徑中的一個節(jié)點,節(jié)點右邊的所有內(nèi)容將在包含XML內(nèi)容的虛擬文檔環(huán)境中進行解釋。在這種情況下,我們將CGI稱作是樣式表選擇者。
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';
在加載合適的模塊和設置一些在整個腳本范圍內(nèi)有效的變量后,我們開始向被傳遞給處理該應用邏輯的類的%context中添加一些域。在這個應用軟件中,我們只傳輸要求的指向腳本文件路徑右邊的URL(REQUEST條目)和包含有存儲在查詢參數(shù)style中的數(shù)據(jù)的STYLE關健字。
$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邏輯類的一個實例,并通過調用其run方法處理請求,將%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);
象我在簡介中提到的那樣,CGI::XMLApplication通過事件調用起作用:應用程序類中一個給定的方法的執(zhí)行依賴于一個指定域的輸入(一般情況下是用來提交表格的按鈕的名字。),必須執(zhí)行二種調用方法:selectStylesheet和requestDOM方法。
selectStylesheet返回有關的XSLT樣式表的全文件系統(tǒng)路徑。為了簡單起見,我們假定樣式表將保存在一個單一的目錄中。我們可以通過$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'; }
下一步,我們需要創(chuàng)建requestDOM方法,該方法將返回被傳輸?shù)腦ML文檔的XML::LibXML DOM表達式。由于我們的網(wǎng)關只適用于靜態(tài)文件,我們需要使用XML::LibXML對文檔進行解析,并返回結果樹。
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)可以安全地在服務器的cgi-bin目錄中安全地運行了,并在一些適當?shù)哪夸浿猩陷d一些XML文檔和一個或二個XSLT樣式表。下面我們就可以開始檢驗我們的工作成果了。對http://localhost/cgi-bin/stylechooser/mydocs/somefile.xml的請求將會使互聯(lián)網(wǎng)服務器從/opt/www/htdocs/xmldocs/目錄中選取mydocs/somefile.xml文件,使用/opt/www/htdocs/stylesheets/中的樣式表default.xsl對該文件進行轉換,并將它傳輸給客戶。
如果需要,我們可以擴充這一基本的框架,例如,可以在樣式表選擇CGI腳本程序添加一些查找組件,選擇合適的樣式表,可以設置或讀取HTTP cookies,對網(wǎng)站進行修飾。
例2:一個簡單的購物系統(tǒng) 在該例子中,我們將使用CGI::XMLApplication創(chuàng)建一個簡化的Web應用程序,購物系統(tǒng)。
與上個例子相同,這個應用程序中與CGI-BIN有關的部分仍然非常地少。我們所需要作的只不過是初始化CustomerOrder應用類并調用它的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);
在這個例子中,我們假定該應用中的產(chǎn)品信息存儲在關系數(shù)據(jù)庫中,產(chǎn)品清單不是太長,使我們在應用中不會出現(xiàn)多屏才能顯示相關信息的麻煩:用戶輸入訂購的產(chǎn)品數(shù)量的主要數(shù)據(jù)輸入屏,顯示訂購單內(nèi)容和所選物品總價格的確認屏,顯示訂單已經(jīng)處理的提示。為了簡單起見,我們在這里沒有涉及送貨和財務數(shù)據(jù)的輸入等問題。
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中繼承的類后,我們開始創(chuàng)建應用中與各種狀態(tài)有關的事件調用。首先,我們必須通過創(chuàng)建registerEvents()方法注冊這些事件。在本例中,我們將注冊order_confirm 和order_send方法,這二個方法設置%context中的SCREENSTYLE域。稍后,我們將利用該屬性定義在顯示客戶端的數(shù)據(jù)時應該使用三個XSLT樣式表中的哪一個。
需要注意的是,這些事件將被映射到實現(xiàn)它們的實際的子程序中,子程序的命名規(guī)則是event_<事件名>,例如,order_confim事件是由event_order_confim執(zhí)行的。另外,還需要注意的是,各種事件的選擇是由CGI::XMLApplication根據(jù)其查找一個與注冊事件同名的表格參數(shù)的能力進行的。例如,要執(zhí)行order_confirm事件,表格組件中必須包含一個提交非空值的名字為order_confirm的表格域。
# 事件的注冊和事件調用
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'; }
如果沒有請求執(zhí)行其他的事件,則缺省地執(zhí)行event_default。在本例中,我們只使用它將SCREENSTYLE域設定為一個合適的值。
sub event_default { my ($self, $context) = @_; $context->{SCREENSTYLE} = 'order_default.xsl'; }
每次請求都會執(zhí)行event_init方法,而且總是在其他方法之前執(zhí)行它,這使得它非常適合對應用中被其他事件使用的部分進行初始化。在本例中,我們使用它返回利用fetch_recordset()方法從數(shù)據(jù)庫中獲取的產(chǎn)品信息的、最初的DOM樹。
sub event_init { my ($self, $context) = @_; $context->{DOMTREE} = $self->fetch_recordset(); }
state-handler方法完成后,我們需要執(zhí)行必需的selectStylesheet和requestDOM方法。
與在第一個例子中一樣,我們假設所有的應用的樣式表都存儲在服務器上相同的目錄中。我們所需要作的是返回$context->{SCREENSTYLE}的值所指定的路線,并添加到末尾。
# 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處理程序之前,我們先來詳細地研究fetch_recordset helper方法。
需要記住的是,我們要做的工作是從一個關系數(shù)據(jù)庫中選擇所訂購產(chǎn)品的有關信息,但傳遞給XSLT處理器的數(shù)據(jù)必須是DOM樹。在本例中,我們不通過編程的方法,而是利用XML::Generator::DBI,它能夠從執(zhí)行SQL SELECT語句得到的數(shù)據(jù)中生成SAX數(shù)據(jù)。創(chuàng)建要求的DOM樹就是建立XML::LibXML::SAX::Builder(它從SAX事件中創(chuàng)建XML::LibXML DOM樹)的實例。
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方法完成了另一項很重要的任務,但它返回的DOM樹只包含我們想向客戶發(fā)送信息的一部分,我們還必須獲取用戶輸入的產(chǎn)品數(shù)量,另外,還需要提供一個訂購產(chǎn)品的總計。
sub requestDOM { my ($self, $context) = @_; my $root = $context->{DOMTREE}->getDocumentElement(); my $grand_total = '0';
為了將當前的訂貨數(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); }
最后,我們將增加一些有關訂單的元信息,方法是在具有<order-total>元素的根元素中添加一個<instance-info>元素,該元素中包含有當前所選貨物的總價值。
$grand_total ||= '0.00'; my $info = XML::LibXML::Element->new('instance-info'); $info->appendTextChild('order-total', $grand_total); $root->appendChild($info);
return $context->{DOMTREE}; }
細心的讀者可能已經(jīng)注意到,我們這個非常簡單的應用程序在order_send方法中沒有作任何實際的事。決定如何處理這些數(shù)據(jù)是產(chǎn)品訂購應用程序中與具體的購物網(wǎng)站最有關的部分。
結束語 CGI::XMLApplication在CGI腳本程序的編程中提供了一種清晰的、模塊化的隔離系統(tǒng)的內(nèi)容和表示的方法,單就這一點,就值得我們對它進行一番研究。此外,它還可以使我們避免糾纏于一些細節(jié)問題,而集中精力解決主要的問題。
|