全部博文(184)
分类:
2009-08-04 20:14:11
很多人开始接触Perl都是因为把他拿来作为写CGI(Common Gateway Interface)程序的工具,当然,也因此不少人都把Perl定位在「写网站程序」。虽然事实绝非如此,不过用Perl来写CGI也确实是非常方便的。尤其在不少前辈的努力之下,让我们现在得以更方便的建立网路相关的程序。虽然目前已经有其他非常方便的脚本语言(scripting language)也可以让人非常轻松的写出CGI程序,例如像PHP或ASP一开始就是以最方便的嵌入HTML来进行互动式网页作为主要目的(目前PHP已经把触角延伸到其他方面,例如PHP-GTK),可是Perl所依赖的不单单只是方便的CGI写成模式,更重要的是程序语言本身所能达到的效果。
很多人在准备开始写CGI的时候都会遇到类似的问题:「我应该学PHP或是Perl?」其实如果你大多数的时候只希望把Perl拿来写网站,而且你手上的东西又非常的急迫,那么PHP也许可以很快的让你达到目的。虽然很多人可能不以为然,不过我倒是以为,可以把学Perl当成纯粹的学习一种程序语言,而CGI只是一种实作Perl GUI(图形使用界面,Graphical User Interface)的方式。何况越来越多人把浏览器当成是GUI以及Client/Server架构最容易的达成方式,其中当然也因为使用浏览器作为用户端程序可以降低使用平台的困扰,而Perl正是验证了这种诠释。
利用Perl来写CGI程序的另外一个最大的疑虑大概是Perl的效率问题,这也许要从网站结构跟原理说起。一般来说,整个网站的原理并不算太困难,也就是用户端发出一个需求,伺服器端收到之后,根据用户的需求发出回应,然后关闭两者的连线。而如果想要达到动态网站的目的,也就是根据使用者的需求,由伺服器端在收到需求之后,根据伺服器的设定把资料传给后端的程序,接着程序依照需求产生出结果之后再传回给网站伺服器。接着,网站伺服器就根据正常的流程把资料传回给用户端。我们可以从图一看到比较清楚的流程。
在正常的状况下,Perl每次收到由网站伺服器传来的需求时,就会重新开启一个程序(process),然后开始根据需求来产生结果。可是问题在于Perl在初始化的过程必须耗费相当的时间跟内存,因此当Perl完成初始化,然后产生出适当的结果并且回传给网站伺服器。这样的过程其实是相当漫长的,尤其当你的伺服器负载过大,或是硬件本身的效率不佳的时候,就会让人感到非常的不耐烦。而这也经常是Perl作为CGI程序最让人疑虑的部份。
当然,这部份也已经有解决的方案了。现在使用者可以利用FastCGI,或是在Apache中搭配mod_perl使用,这样一来就可以让原来的程序被保留在内存中,而不必因为每次有使用者发出浏览的需求就必须重新启动Perl,造成因为Perl初始化的延迟问题。不过如果想要有更好的效能来使用mod_perl,那么足够的内存就变成非常重要的。所幸硬件的价格不断下降,让这样的资源使用不至于发生太大的问题。
如果要使用Perl来增进网站的速度,是需要进行一些设定上的改变,当然有时候能够跟程序配合是更理想的。不过这对于刚开始准备使用Perl来作为网站程序的工具而言显然是有些困难的,所以我们并没有要在这一章中介绍更多相关于mod_perl的使用。待各位对于使用Perl来建构出网站这样的工作熟悉之后,可以继续选择相关书籍或文件进行研究。
16.1 CGI
现在使用Perl作为网路应用程序的主要程序语言时,几乎所有人第一个会遇到的就是CGI这个模块。CGI模块提供的功能非常的强大,不管是输出至网页上或是透过CGI取得使用者输入的参数,另外也可以利用CGI动态产生HTML的各种表单选项。对于动态网页的支援,CGI目前已经可以算是非常完善。甚至有时候会让人感到相当意外,因为有些时候你会忽然发现,原来这些东西CGI.pm也可以达到。
现在我们就来看一下,如果要使用Perl开始写CGI程序,那么应该怎么下手呢?毫无疑问,你总得先载入CGI这么模块,所以就像我们所熟知的方式,先在你的程序加入这一行吧:
use CGI;
接下来,我们应该建立一个新的CGI物件,方法也不太难,也就是利用new这个物件的操作方法。所以只要这样子就可以建立起CGI物件:
my $q = CGI->new;
如果你想要把任何内容输出到网页上,你大概都要先送出HTML的标头档到网页上,也就是所谓的header。最简单的方式当然就只要这么写:
print $q->header;
不过其实header有很多的参数,例如你应该要可以设定内容的编码或输出的内容型态等等。所以header这个函数也允许你使用相关的参数来改变header的属性,例如你可以设定网页输出的内容编码为UTF-8:
print $q->header(-charset => 'utf-8');
而且你可以一次指定多种属性,就像这样的方式:
print $q->header(-charset => 'utf-8',
-type => 'text/html');
当然,HTML的标头档还有各式各样的属性,使用者也都可以利用header来设定,而且这样子的方式似乎也比起手动输入各种header的设定值要简洁不少,因此这个函数对于经常使用CGI的人来说还是非常方便的。而另一个重要的功能则是CGI的param,也就是利用CGI传回来的参数。很多时候,我们都会利用这样的方式来取得由HTML表单中传回来的栏位值:
如果你的HTML里面写的是这个样子:
那么你的Perl就可以这么使用:
$q->param('name');
也就是藉由HTML里面的表单栏位名称作为param的参数来取得使用者输入的值,这样就可以让程序设计师很方便的取得使用输入的结果。举个简单的例子吧!如果我们想制作一个使用者登入的程序,那么画面上大多不外乎就是使用者名称?密码两个栏位。所以我们通常的作法就是利用取得的使用者名称去数据库搜寻相对应的密码资料,如果没有相关的资料就表示没有这个使用者,或是使用者输入的使用者的名称有误。如果可以找到相对应的使用者,那么我们就比对数据库中撷取出来的密码与使用者输入的是否一样。而程序的写法大概就像是这样:
#!/usr/bin/perl -w
use strict;
use CGI;
use DBI;
my $q = CGI->new;
print $q->header;
my $user = $q->param('user');
my $dbh = ('DBD:mysql:database=foo', 'user', 'passwd');
my $sql = "SELECT password FROM table WHERE user = '$user'";
my $sth = $dbh->prepare($sql);
$sth->execute;
unless (my @pwd_check = $sth->fetchrow_array) {
print "没有这位使用者";
else {
unless ($pwd_check[0] eq $q->param('passwd')) {
print "密码错误";
else {
......
}
}
有些时候,你会希望可以一次取得所有由HTML表单传过来的栏位名称,这时候CGI的另一个函数就可以派上用场了。也就是可以使用params这个函数,它会传回一个数组,这个数组就包含了所有由HTML传来的表单栏位。因此你的程序中可以这样写:
my @params = $q->params();
我们一开始就提到了关于利用CGI这么模块送出动态header的方式,其实它不只能够送出档头,你还可以动态的产生其他的网页元素,例如你可以使用这样的方式来印出字级为h1的文字内容:
print h1('这是h1级的字');
或是使用
print start_h1,"这是h1级的字,end_h1;
结果都可以产生
这样的HTML内容
类似的用法还有包括下面几种:
start_table() # 送出
当然,你还可能不直接送出header,因为你想要在处理完某些状况之后,把网页的位址送到其他地方。这时候你就应该使用redirect这个函数。它的用法也是非常的简单,你只需要告诉它你想转往的其他网址就可以了。
$q->redirect('');
至于HTML的元素,也有很多时候你可以利用CGI这么模块动态产生,例如你可以使用img来产生出这样的HTML标签。其他诸如ul,comment等等也都能够轻易的被产生。不过另外一组非常完整的则是HTML表单的产生方式,也就是你可以利用CGI来产生大多数的表单栏位。
一个非常基本的例子,就是利用CGI模块来产生一个text的表单栏位。
print $q->textfield( -name=>'field',
-default=>'default value');
另外的一些栏位也都可以用类似的方式产生,就像接下来的例子:
print $query->textarea( -name => 'textarea',
-default => '会放在栏位的预设值',
-rows => 5,
-columns => 10);
print $query->password_field( -name => 'password',
-value => '这里也是预设值',
print $query->checkbox_group( -name=>'checkbox',
-values=>['value1','value2','value3','value4'],
-default=>['value1','value3'],
-linebreak=>'true',
-labels=>\%labels);
当然,还有各式各样的表单栏位,使用的方式也都大同小异,事实上,CGI模块的文件中有详细的描述。不过有一个非常重要的却是不可忽略的,也就是错误讯息。当使用者使用CGI进行一些运作时,有时候会有一些错误,这时候CGI模块会透过cgi_error来传回错误的讯息,所以你可以在程序中加上这个函数,让CGI发生错误时能送出错误讯息。就像这样的方式:
if ($q->cgi_error) {
print "无法处理CGI程序";
print $q->cgi_error;
}
接下来,我们再来讲一个CGI模块的重要功能,也就是cookie的存取。许多时候,网站设计者为了减少使用者重复输入资料的困扰,或是取得使用者浏览的纪录,常常会藉由用户端浏览器的cookie功能来纪录一些相关性的资料。而CGI也可以针对这些cookie进行存取。如果我们想要取得已经存在用户端浏览器上的cookie资料,我们只需要这么作:
use CGI;
my $q = CGI->new;
my $cookie = $q->cookie('cookie_name');
接下来,我们当然也可能需要写入cookie到使用者端,那么我们可以利用CGI的档头来完成这项工作,也就是HTML的header。而完整的用法就是先把你所要写入的cookie值,属性都先设定好,然后直接用header来把这些内容送给使用者的浏览器中。
$cookie = $q->cookie( -name=>'cookie_name',
-value=>'value',
-expires=>'+1h',
-domain=>'.my.domain',
-secure=>1);
print $q->header( -cookie=>$cookie);
很显然的,CGI的功能还不止于此,虽然我们已经介绍了大部份使用CGI时常用的功能。不过如果你还有进一步的需求,应该务必使用perldoc CGI来详细阅读CGI的相关文件。
16.2 Template
Template虽然不单单用于网路应用程序中,可是却有许多人在写网站相关的程序时总会大量的使用,因为对于能够让程序设计师单独的处理程序而不需要担心网页的设计对于许多视设计为畏途的程序设计师而言,实在是非常的重要。
Template其实完整的名称是Template Toolkit,因为目前的Template Toolkit的版本是2.13,所以一般人又习惯称呼目前的为TT2。至于正接受Perl基金会发展的则是Template::Toolkit的第三版,也就是俗称的TT3。当然,既然可以接受Perl基金会的赞助开发新版Template::Toolkit,可见这个模块对于Perl社群的重要性了。
首先我们先来谈谈template系统的概念,让大家能更深刻的感受使用template系统对程序设计师在网站程序的重要性。我们在刚刚可以看到CGI这个模块的运作,所以我们可以透过CHI的使用,输出绝大多数的HTML标签,也就是完成一个HTML页面的输出。当然,还有一种可能就是直接使用print指令,一个一个的把HTML标签手动印出。可是不管是上面两种方式选择哪一种都会遇到相同的问题,也就是要怎么跟网页设计者一起合作来完成一个美观,功能又强的网站呢?有一种可能的方式也许是由设计者做好一般的HTML页面,然后你将这个页面当作一般的文字文件将它逐行读入。然后自行置换掉要动态产生的部份。如此一来,只要网页设计者能在网页中加上让你的程序可以认得的关键字,那么你就可以不理会画面的改变,而且还能够让程序正确的执行。这样的概念正是孕育出模板系统的主要想法。
Perl的模板系统其实不只一种,可是Template Toolkit却是深受许多人喜爱的,因为它不但可以让使用者把模板与程序码分隔,也可以让程序设计师在模板中加上各式各样的简单控制。虽然说简单,可是功能却也一点也不含糊。因为使用者可以在里面使用循环,可以取得由程序传来的各种变量,当然也可以在模板中自己设定变量。当然,最后如果所有的方法都用尽,还不能达成你的要求,你也可以在模板里面加上Perl的程序码。
基本的使用TT2,你应该要有一个Perl的程序码,跟相关的模板。而如果你只需要单纯的变量替换,那么使用的方式非常的简单。你可以这么写:
use Template;
my $config = {
INCLUDE_PATH => '/template/path',
EVAL_PERL => 1,
};
my $template = Template->new($config);
my $replace = "要放入模板的变量";
my $vars = {
var => $replace,
};
my $temp_file = 'template.html';
my $output;
$template->process($temp_file, $vars, $output)
|| die $template->error();
print $output;
这时候,你就必须有一个符合Perl程序码中指定的模板文件,也就是template.html。而在这个模板中,大多数都是一般的HTML标签。而需要被置换的变量则被定义在$vars中。我们可以先来看看这个templte可能的形式。
好了,现在你可以把这个HTML拿去给网页设计的人,让他负责美化页面,只要他在设计完页面后,能把关键的标签[% var %]留在适当的位置就可以了。不过你可不能轻松,让我们来研究一下Template是怎么运作的。首先,在我们新建立一个Template的物件时,我们必须设定好相关的内容,在这里的例子中,我们只有设定了两个参数,一个是INCLUDE_PATH,也就是你的template文件所放置的位置,这里的内容可以是一个串列。也就是说,你可以指定不只一个路径。另一个我们设定的是EVAL_PERL这个选项是设定是否让你的Template执行Perl的区块。当然,选项还有好几项,例如你可以设定POST_CHOMP,这个选项跟chomp函数有一些类似,它可以帮你去除使用者参数的空白字符。另外,还有PRE_PROCESS的选项则是设定所有的模板在被载入之后,都必须预先执行某个程序,例如先把档头输出到模板中等等。另外,你还可以修改Template预设的标签设定,例如我们刚刚看到的[% var %],就是利用Template的预设标签[% %]把它表现出来。而Template允许你在建立Template物件时使用START_TAG跟END_TAG来改变这样的预设标签。如果你用了这样的方式:
my $template = Template->new({
START_TAG => quotemeta(''),
END_TAG => quotemeta('?>'),
});
那么刚刚在模板里的变量就应该改写成
var ?>
这样似乎跟PHP有一点像了。
不过Template的作者也知道很多人大概很习惯PHP或是ASP的标签,所以在Template也提供了另一个设定选项,也就是TAG_STYLE。你可以设定成php( ... ?>)或是asp(<% ... %>),不过其实我个人以为[% ... %]的原创形式还算顺手,所以倒是没换过任何其他标签风格。Template的设定选项非常多样化,不过只要了解以上的这些项目就大概都能应付百分之八十的情况了。如果还需要其他的资讯,则可以参阅Template::Manual::Config。
接下来,我们来看看使用上有甚么需要注意的。在程序中,基本上你如果有甚么特别需要注意的部份,那大概就是变量的传递了。如果你要传一个纯量变量,跟我们刚刚的范例一样,那么就只是把变量指定为杂凑变量的一个值。就像我们刚刚的用法一样,你就只要指定:
my $vars = {
var => $replace
};
可是很多时候,我们也许会传送一整个数组或是杂凑,那么这时候你最方便的方式就是传送这些变量的引用,写法也许就像是这样:
my @grades = (86, 54, 78, 66, 53, 92, 81);
my $vars = {
$var => $replace,
$var2 => \@grades,
};
这时候,你的模板内容显然也需要改写,把印出数组的这部份加入你的模板中,一般来说,我们可以使用Template提供的FOREACH循环。所以你可以在你的template加上类似的一块:
[% FOREACH grade = grades %]
成绩:[% grade %]
[% END %]
当然,除了数组,我们还可以使用杂凑。使用的方式却也不太一样,虽然你还是传递杂凑引用,可是在模板中的使用却是直接利用杂凑键。所以你的Perl程序码跟模板中分别是这么写的:
my %hash = ( height => 178,
weight => 67,
age => 28 );
my $vars = { var => \%hash };
于是你必须在模板中做出相对应的修改:
[% var.height %]
[% var.weight %]
[% var.age %]
我们刚刚看到在模板中放了FOREACH这个Template提供的循环,其实模板中还有许多可供利用的特殊功能,例如一个非常方便的就是[% INCLUDE %]。很多时候,人们都喜欢在网页的某个部份加上一些制式的内容,最常见的当然就是版权说明了。所以我们可以把这些版权说明的内容放到某一个文件中,例如就叫做copyright.tt2吧!所以我们有了一个template文件,内容其实就是一些文字叙述:
copyright.tt2:
Copyright Hsin-Chang Chien 2004 - 2005
好了,现在我们有不少其他的模板文件,希望每一页都能加上这一段内容。那么我们只需要在这些文件的适当位置加上这样的一行()所谓的适当位置就是你希望看到这些内容的位置:
[% INCLUDE copyright.tt2 %]
这样的写法可以让你一次省去相当多的麻烦,尤其当你有可能更动这些文件的内容时。例如我现在想把版权的内容进行调整,那么你只需要修改copyright.tt2一个文件,而不需要把所有的文件一个一个叫出来修改。不过其实INCLUDE可以应付比较复杂的模板系统,而像版权声明这种纯文字的文件,还有更简洁的载入方法,也就是INSERT,如果你要被载入的文件是一个纯文字档,不需要Template帮你进行任何的处理,那么就考虑使用[% INSERT %]吧!
另外,Template还支援另一种常用的重复叙述,也就是WHILE。它的语法相当简单,也就是使用这样的区块把要执行的内容标示出来:
[% WHILE condition %]
....
[% END %]
而IF叙述的基本用法也是和Perl语法几乎一样只是他是使用大写字母,而且用Template的标签区隔出来,所以你可以轻松的使用:
[% IF condition %]
....
[% END %]
或是
[% IF condition %]
....
[% ELSE %]
....
[% END %]
而ELSIF也是被允许的,用法也是类似:
[% IF condition %]
....
[% ELSIF condition2 %]
....
[% ELSE %]
....
[% END %]
当然,你还有UNLESS可以使用,用法也是相同:
[% UNLESS condition %]
....
[% END %]
至于如果你真的想在模板里面写Perl程序,也只需要这么作:
[% PERL %]
# perl 程序码
....
[% END %]
只是我个人并不建议你常常需要这么使用,否则你有可能其实是挑错工具了。因为Perl有其他模板系统也许比较符合你的习惯跟需求。而我们接下来就要介绍另一种在Perl社群中最近非常风行的另一套网路应用程序的搭配系统,也就是Mason。至于Template,它还有很多奥妙,你可以试着参考相关的官方文件。
16.3 Mason
网页设计跟程序码怎么切割,这个想法会根据写程序的人的习惯而有很大的差距。很多人喜欢利用像Template::Toolkit这样的工具来让网页的版面跟程序码分离的越干净越好。当然也有人认为像php形式的内嵌式作法可以让网站的雏型在很短的时间就产生出来,因此Mason也就以类似的作法诞生了。因此在这一,两年来,Mason已经成为非常重要的模块,尤其在作为网站的工具时。根据job.perl.org(一个专门张贴Perl相关工作机会的网站)上的资讯,Mason已经是许多国外企业在徵求网站相关程序开发人员时需求度很高的技术了。而且许多大型网站现在也都使用了Mason来产生他们的网页内容,例如亚马逊书店()就是一例。
Mason基本的操作原理是在你的Apache中加上一个控制器(Handler),让使用者的要求全部送给Mason处理,这样一来,Mason就可以把各式各样的使用者需求都预先处理好,然后送出合适的内容。使用Mason,你除了装上HTML::Mason这个模块之外,你的Apache还必须支援mod_perl,在一切准备就绪之后,你可以在你的Apache中像这样的进行设定:
PerlModule HTML::Mason::ApacheHandler
SetHandler perl-script
PerlHandler HTML::Mason::ApacheHandler
在Mason中,你可以使用百分比(%)符号作为Perl程序码的引导符号,或是<% ... $>。如果是一行的开始是由%引导,那么表示这一行是Perl程序码。或是利用<% ... %>来设定一个区块的perl程序码。所以你的网页可以像这样子:
<%perl>
my $num = 1;
my $sum = 0;
while ($num <= 10) {
$sum+=$num;
}
%perl>
总和是: <% $sum %>
那么结果就会在网页上呈现出计算后的总和,因此他已经把网页的HTML跟perl程序码作了紧密的结合。尤其如果我们使用%作为程序行的起始,就更能表达出其中不可切割的关系了:
% $foo = 70;
% if ($foo >= 60) {
你的成绩及格了
% } else {
你挂了
% }
我们常使用这样的方式来把条件判断穿插在HTML里面,所以你的内容已经是夹杂各种语法的一个综合体了。而且不像Template使用自己定义的特殊语法,你在Mason中使用的大多是标准的HTML跟Perl语法(虽然他们总是夹杂在一起)。所以你当然可以这样写:
% my @array = (67, 43, 98, 72, 87);
% for my $grade (@array) {
你的成绩是: <% $grade %>
% }
接下来比较特殊的是一些Maon专用的元件,利用这些元件,你可以很容易的处理一些资料。例如使用者传来的需求就是其中一个很好的例子。在Mason中最基本的两个全局变量(每次使用者发出各种要求时,就会产生的两个变量)分别是$r跟$m。其中$r是Apache传来的需求内容,至于$m则是负责Mason自己的API。所以你可以藉由$r来取得由Apache传来的资料,例如:
$r->uri
$r->content_type
不过我们暂时先不管$m这个负责处理Mason API的变量,因为我们还有更有趣的东西要玩。也就是在Mason页面中常常会被使用的<%args>...%args>区块。这个区块可以用来取得由使用者藉由POST/GET传来的参数,一个很简单的例子当然就是像这样:
于是我们就在mason.html里面加上args
区块,利用ARGS取得这些变量之后,我们就可以在页面中自由使用了。
<%args>
$arg1
$arg2
%args>
所以刚刚累加的程序,我们可以由使用者输入想要累加的数字,这时候,我们只要把while的结束条件利用使用者输入的变量来替换就可以了。
<%perl>
my $sum = 0;
while ($end > 0) {
$sum+=$end;
}
%perl>
总和: <% $sum %>
<%args>
$end
%args>
看来应该不是太困难,只是有点杂乱。如果我们每次都想要在页面开始之前,就先用perl进行一堆运算,判断的时候,可以把<%perl> ... %perl>这一大段的区块搬离开应该属于HTML的位置吗?其实在Mason也替你想到了这个问题,所以在Mason中你可以使用<%init> ... %init>这个区块,也就是进行初始化的工作。我们把刚刚的那一段页面的程序码重新排列组合一下。
总和: <% $sum %>
<%init>
my $sum = 0;
while ($end > 0) {
$sum+=$end;
}
%init>
<%args>
$end
%args>
这样看起来显然干净多了,不过记得我们在使用Template::Toolkit的时候有一个很不错的概念,也就是[% INSERT %]/[% INCLUDE %]的方式,在Mason中也有类似,也就是利用<& ... &>的方式来载入你的自订元件。用个简单的例子来看:
<% $grade %> |
<%args>
@grades
%args>
这时候,我们可以把前、后的HTML分别放到header跟footer两个地方,然后利用<& ... &>来载入,所以这个内容就会被改写为:
<& header &>
<% $grade %> |
<%args>
@grades
%args>
这对于一整个网站维持所有网页中部份元素的统一是一种非常方便而且有用的方式。而且任何在独立的元素中被修改的部份也会在所有的页面一次更新,这绝对比起你一个一个文件修改要来得经济实惠许多。尤其当你所进行的是一个非常庞大的网站时,更能了解这种用法的重要性。
不可否认,我们花了这么多的页面来讲这三个目前在Perl社群中最被常用来进行网站程序的工具模块,却只能对每一个部份做非常入门的介绍。毕竟这三个模块都是非常复杂而且功能强大的。另外的特点就是他们都可以详细到各自出版一本完整的使用手册。不过对于一开始想要尝试使用这几个模块的人来说,事实上也能用阳春的功能帮你进行许多繁复的工作了。别忘了,大多数的Perl模块或语法,你只要了解他们的百分之二十,就可以处理百分之八十的日常工作。
习题:
1. 以下是一个HTML页面的原始码,试着写出action中指定的print.pl,并且印出所有栏位中,使用者填入的值。
2. 承上题,试着修改刚刚的print.pl,并且利用Template模块搭配以下的模板来进行输出。
姓名: | [% name %] |
地址: | [% address %] |
电话: | [% tel %] |
3. 承上题,将利用Template输出的部份改为HTML::Mason。