使用 CGI::Application
为什么使用 CGI::Application?
目录 |
•为什么使用 CGI::Application? |
通用网关接口(CGI)可能被一些人看作“不够华丽”,但它却是基于Web的应用程序开发的“工作马”。在CGI缺少的术语符合度方面,它通过可靠性、灵活性、可移植性(也许最重要的是)熟悉性来弥补!
CGI::Application 建立在CGI的基础上,增加了一个编写真正可重用Web应用程序的结构。《code>CGI::Application 保留CGI中有效的东西,并提供一个结构来消除一些使CGI蒙受不利影响的复杂编程技术。
CGI::Application
代码非常通用且非专有,因此它可以在支持Perl和CGI的任何操作系统和Web服务器上出色地工作。正如您将看到的,《code>CGI::Application 结构甚至使作者能够通过CPAN首次分发功能完整且复杂的Web应用程序。
理解 CGI::Application
运行模式
CGI::Application
的最大贡献是“运行模式”的正式结构。运行模式通常指应用程序的单个屏幕。所有复杂的Web应用程序都包含多个屏幕(或“页面”)。例如,一个用于搜索数据库的应用程序可能包含搜索表单、结果列表和单个记录的详细信息。这三个屏幕中的每一个都是整个应用程序的一部分。
不同的程序员已经设计了不同的系统来管理这些运行模式。太多的Web应用程序仍然看起来像巨大的 IF-THEN-ELSE 块,每个运行模式都在一个条件状态下封装。通常,这些条件试图通过查找各种表单变量的存在来划分应用程序状态。例如,如果存在搜索字段,则显示结果列表 - 否则,显示搜索表单。
my $query = CGI->new();
print $query->header();
if (my $search_term = $query->param("search_term")) {
# ...30 lines of code to run a search
# and print the results as HTML
} else {
# ...15 lines of code to display
# the search form
}
正是这种代码给CGI带来了坏名声!它结构薄弱,即使是功能上的微小变化也很容易出错。
最精明的程序员很快意识到运行模式是必须直接管理的东西,而确定运行模式的简洁方式是显式地设置它。一些系统,如ASP、HTML::Mason
、Cold Fusion和JSP,试图通过为每个运行模式提供一个物理文档来管理这些运行模式。这导致单个应用程序的代码分散在至少与运行模式数量一样多的文件中!通过将运行模式从上下文中分离出来并分别放入文件中,以解决状态管理问题,但同时也创造了许多新的问题,其中最不重要的是代码资产的管理。毕竟,这些运行模式都是同一应用程序的一部分。
应用程序模块
CGI::Application
通过提供两个核心设施来解决运行模式管理问题。首先,CGI::Application
指定一个特定的HTML表单输入为“模式参数”。这个模式参数用于存储(和检索)应用程序当前的运行模式。运行模式的值是一个简单的文本标量。CGI::Application
读取这个模式参数的值,并根据情况充当交通警察,指导应用程序的操作。
其次,CGI::Application
将每个运行模式映射到特定的 Perl 子程序。每个子程序,被称为“运行模式方法”,实现了单个运行模式的行为。你所有的代码,包括所有的运行模式方法和运行模式与子程序之间的映射表,都存储在一个文件中。这个文件是一个 Perl 模块,被称为你的“应用程序模块”。
你的应用程序模块是 CGI::Application
的子类。实际上,CGI::Application
从不打算直接使用。CGI::Application
被面向对象爱好者称为“抽象类”,并且只能通过继承来使用。要实现从 CGI::Application
的继承,请在你的应用程序模块顶部放置以下代码
package Your::Web::Application;
use base 'CGI::Application';
这段代码给你的应用程序命名(在这种情况下,“Your::Web::Application
”),并使 CGI::Application
成为父类。这个父类实现了许多方法,将为你的应用程序提供必要的基础设施。其中一些方法可能需要你的代码来调用以执行函数或设置属性。其他继承的方法预期将在你的代码中实现,以提供特定于你应用程序的功能。
定义你的运行模式映射
运行模式与运行模式方法之间的映射在 setup() 方法中定义。setup() 方法是一个你预期将在你的应用程序模块中通过实现 setup() 子程序来覆盖的方法。在你的 setup() 子程序中,你定义了运行模式与运行模式方法之间的映射。想想这个映射就像是你应用程序可以做的 definitive 列表。如果你在你的 Web 应用程序中添加了函数,那么你将修改这个映射以包括你的新运行模式。
这个运行模式映射是通过使用 CGI::Application
提供的 run_modes() 方法在 setup() 方法中定义的。run_modes() 方法是一个实例方法,它接受作为参数的运行模式作为键和运行模式方法名称作为值的关联数组(注意:CGI::Application
版本 1.3 用于我们所有的示例)。为了设置具有三个运行模式的典型数据库搜索应用程序,我们的代码可能看起来像这样
package WidgetView;
use base 'CGI::Application';
sub setup {
my $self = shift;
$self->run_modes(
'mode_1' => 'show_search_form',
'mode_2' => 'show_results_list',
'mode_3' => 'show_widget_detail'
);
$self->start_mode('mode_1');
$self->mode_param('rm');
}
这就完成了!setup 方法接收你的应用程序类的一个实例 ($self) 作为参数。当你调用 run_modes() 时,你正在设置此实例的运行模式,所以你使用面向对象的间接(”->
”)运算符。继承的 start_mode() 方法告诉 CGI::Application
如果没有指定模式(当应用程序首次调用时的情况),则默认使用哪个模式。继承的 mode_param() 方法指定了将保存应用程序从请求到请求的运行模式状态的 HTML 表单参数的名称。
我们在这里设置了一个名为“WidgetView”的应用程序,具有三个运行模式,分别命名为“mode_1”、“mode_2”和“mode_3”。这些运行模式分别映射到尚未编写的三个子程序 show_search_form()、show_results_list() 和 show_widget_detail()。模式参数设置为“rm”(默认值),第一次运行模式将是“mode_1”。
创建运行模式方法
运行模式方法子程序将包含你代码的大部分。这些运行模式方法每个实现特定运行模式的功能。如我们之前提到的,运行模式大致对应于屏幕。因此,你的运行模式方法将负责设置 HTTP 和 HTML 输出以发送回请求的 Web 浏览器。
关于运行模式方法,最重要的记住的事情是它们不应该向STDOUT打印任何内容。《CGI::Application》继承的run()方法负责将所有HTTP头和HTML内容实际发送到Web浏览器。您的运行模式方法由run()方法调用,您的代码应返回包含所有HTML内容的标量。如果您向STDOUT发送任何内容,将导致您的应用程序发生故障。这种错误的症状通常是出现在HTTP头之前的HTML内容,或者输出中出现多次HTTP头。如果您看到这种情况,那么您可能尝试向STDOUT发送输出。
您的运行模式方法不可避免地需要与CGI查询交互以检索(并设置)表单参数。《CGI::Application》并不尝试提供这种基本功能。相反,《CGI::Application》利用Lincoln D. Stein的出色CGI.pm模块进行所有与CGI查询的交互。精通《CGI.pm》将极大地增强您对《CGI::Application》的掌握。《CGI::Application》通过继承的query()方法为您提供访问《CGI.pm》查询对象的方式。一旦通过query()方法检索到《CGI.pm》查询对象,您就可以按要求与之交互。
对于我们的第一个运行模式(“mode_1”),我们已经指定了run-mode方法show_search_form()应该被调用的方式。这个运行模式的目的是在用户首次进入应用程序时显示搜索表单。我们的运行模式方法可能看起来像这样
sub show_search_form {
my $self = shift;
# Get the CGI.pm query object
my $q = $self->query();
my $output = "";
$output .= $q->start_html(-title => "Search Form");
$output .= $q->start_form();
# Build up our HTML form
$output .= "Search for Widgets: ";
$output .= $q->textfield(-name => 'search_term');
$output .= $q->submit();
# Set the new run-mode, when the user hits "submit"
$output .= $q->hidden(-name => 'rm', -value => 'mode_2');
$output .= $q->end_form();
$output .= $q->end_html();
return $output;
}
如您所见,这个子例程很简单。指定的运行模式方法在面向对象的环境中($self)被调用。我们通过应用程序模块的query()方法(从《CGI::Application》继承)检索《CGI.pm》查询对象。我们创建的HTML表单应该对使用过《CGI.pm》的任何人来说都很熟悉。当我们的$output完全构建完成后,我们返回它(而不是将其打印到STDOUT)。
这里只有一个“魔法”,那就是我们隐藏的表单变量“rm”。这是《CGI::Application》从一个运行模式跳转到另一个运行模式的方法。在这个运行模式的情况下(基于我们应用程序期望的功能),我们只能去一个地方,那就是“mode_2”——匹配结果的列表。如果“mode_1”运行模式允许我们做更多的事情(例如,添加新的Widget),那么我们在这个屏幕上就需要有两个按钮,每个按钮集有不同的表单变量“rm”的值。
您如何设置这个变量取决于您自己。例如,您可以使用多个HTML表单,或者您可以使用JavaScript。《CGI::Application》对如何设置运行模式参数没有任何限制。它只关心它被设置了,并将细节留给应用程序开发者。一旦运行模式参数被设置,《CGI::Application》提供所有必要的运行模式状态管理来指导您的应用程序到正确的子例程。
HTTP头
默认情况下,《CGI::Application》会将所有内容作为MIME类型“text/html”返回。这是通过HTTP头设置的。如果您想设置不同的MIME类型,操作cookie或执行HTTP重定向,那么您需要更改默认的HTTP头。这是通过使用两个继承的《CGI::Application》方法:header_type()和header_props()来完成的。有关它们的使用详情,请参阅《CGI::Application》的perldoc。
实例脚本
在《CGI::Application》架构中还有最后一部分,那就是“实例脚本”。到目前为止,我们已经广泛地讨论了应用程序模块,但我们还没有解释应用程序模块是如何被使用的!这就是实例脚本发挥作用的地方。
在传统的CGI编程中,我们可能有一个文件,myapp.cgi
,它被Web浏览器请求。根据其配置,Web服务器将把这个文件当作一个程序,并返回其执行结果(而不是内容)。在传统的CGI中,这个文件将包含所有您的应用程序代码,因此可能会相当长。使用CGI::Application
,我们已将所有代码放入应用程序模块中。这意味着由Web服务器实际执行的文件可以完全不包含特定应用程序的代码!事实上,对于我们的典型“WidgetView”应用程序,下面是整个widgetview.cgi
的内容。
#!/usr/bin/perl -w
use WidgetView;
my $app = WidgetView->new();
$app->run();
就这么简单!文件widgetview.cgi
被称为“实例脚本”,因为它管理着应用程序模块的一个“实例”。只要WidgetView.pm
位于Perl的搜索路径(@INC)中,这个实例脚本就会运行整个Web应用程序。
整合所有内容
在我们的典型WidgetView应用程序中,我们拥有一个完整的CGI::Application
的所有基本组件。
我们的应用程序模块WidgetView.pm
可以位于服务器文件系统的任何位置,只要它在Perl的搜索路径中。建议将应用程序模块放置在Web服务器公共文档空间之外,这样其内容就不能通过Web服务器直接访问。
我们的示例中的应用程序模块包含四个子程序
setup()
配置我们的运行模式映射和其他应用程序设置。
show_search_form()
运行模式“mode_1”。返回HTML搜索表单。
show_results_list()
运行模式“mode_2”。根据搜索表单的内容,在数据库中查找匹配项。结果以HTML格式返回。为每个匹配项提供一个按钮,允许Web用户通过点击选择一个项目。点击项目将表单参数“rm”的值设置为“mode_3”,并将另一个表单参数(例如:“item_id”)的值设置为所选项目的唯一标识符。
show_widget_detail()
运行模式“mode_3”。根据表单参数“item_id”的值,此方法从数据库中检索有关指定项目的所有详细信息。这些详细信息以HTML格式返回。
WidgetView应用程序的更完整的源代码列表可以在这里找到。
我们的实例脚本widgetview.cgi
位于Web服务器公共文档空间内。它被配置为作为CGI应用程序处理。只要您的Web服务器支持CGI和Perl,基于CGI::Application
的Web应用程序将按预期运行。WidgetView.pm
不需要Apache Web服务器——实际上,它可以在任何CGI兼容的服务器上运行得同样好,包括Microsoft的“IIS”或Netscape的“iPlanet”服务器,无论操作系统如何。
当然,WidgetView在Apache/mod_perl
服务器上运行得非常好,因为CGI::Application
遵循非常干净的Perl编程标准。CGI::Application
从一开始就被设计成在完全严格的模式下运行,而不抛出任何警告。
结论与高级概念:下一步该怎么做
本文中提出的概念应为您使用CGI::Application
作为Web应用程序开发的基础提供起点。有许多高级概念可以完善CGI::Application
的画面,其中一些我将尽力在此总结。
代码重用
通过实例脚本的架构,产生了巨大的可重用性潜力。单个应用程序模块可以被多个实例脚本使用。考虑一下在单个项目、跨项目甚至跨组织中多次使用Perl模块的潜力!首次,可以将Web的高级功能封装在单个Perl模块中并分发。如果你是CPAN作者(或者有兴趣成为作者),你可以创建一个Web应用程序,并通过CPAN以与CGI::Application
本身相同的方式分发!
实例脚本还具有设置实例特定属性的 capability。因此,实例脚本成为你的应用程序模块的“配置文件”的一种。从CGI::Application
继承的新()方法具有通过继承的param()方法设置你可能在应用程序模块中使用的变量的 capability。作为一个简单的例子,你可以编写一个邮件表单应用程序,该应用程序接受实例参数,例如应将表单内容发送到的地址。多个实例脚本,所有这些脚本都引用同一个应用程序模块,可以指定不同的电子邮件接收者或不同的表单。请参阅CGI::Application
的perldoc以获取new()和param()方法的用法细节。
CGI::Application
旨在通过继承支持代码重用。可以设计应用程序模块以提供项目范围内的功能。然后,特定应用程序可以继承自你自定义的父类,而不是直接从CGI::Application
继承。例如,考虑每个应用程序从数据库加载配置数据或设置特定运行时属性的可行性。父类应用程序模块可能会实现cgiapp_init()方法,这允许这些类型的继承行为。请参阅CGI::Application
的perldoc以获取cgiapp_init()方法的具体用法。
使用HTML::Template
将HTML GUI与应用程序代码分离
在我公司Vanguard Media,CGI::Application
是更大开发策略的一部分。我们应用策略的一个主要指导原则是将HTML GUI(图形用户界面)与底层应用程序代码的最大分离。我们发现,最好的Perl程序员很少是最好的HTML设计师,而最好的HTML设计师很少是最好的Perl程序员。这就是为什么在构建应用程序架构时,这两个元素的分离可能是最有益的设计决策。
为此,我们使用了Sam Tregar的优秀的HTML::Template
模块。HTML::Template
允许为我们的应用程序中的每个屏幕创建外部“模板文件”。这些模板文件包含99%的纯HTML,以及由调用运行模式方法设置的少量附加语法,用于包含标量变量、循环和条件数据块。
HTML::Template
对我们开发策略至关重要,以至于已经在CGI::Application
中构建了特殊的钩子来支持其使用!请参阅CGI::Application
的perldoc以获取继承的tmpl_path()和load_tmpl()方法的用法细节。
关于会话和安全的思考
在CGI::Application
邮件列表中经常出现的一个问题是关于如何最好地实现登录安全和会话管理。经验告诉我,这些最好是排除在你的应用程序代码之外,并推入你的Web服务器的较低层。
如果你使用Apache Web服务器,并且对实现登录安全和会话管理感兴趣,我鼓励你查看CPAN上的各种Apache::Auth*
模块。这些模块与请求的“认证”和“授权”阶段绑定。此代码在调用CGI应用程序之前运行很长时间。
将您的会话和安全代码放在这一层有两个主要优势。首先,您的安全措施将适用于所有文档,而不仅仅是Perl应用程序。即使是静态的HTML文档也将受到该系统的保护。其次,将会话和安全放在这一层将避免程序员需要在应用程序开头包含特殊代码才能参与会话和安全系统的架构。
资源
希望您喜欢阅读这篇文章。以下参考可以帮助您进一步探索使用CGI::Application
。
标签
反馈
这篇文章有问题?请通过GitHub提交问题或拉取请求以帮助我们。