Catalyst 和 Perl 应用开发简介
Catalyst 是开源 Web 开发框架演进中的最新成果。它使用现代 Perl 编写,并受到之前许多项目(包括 Ruby on Rails)的启发,Catalyst 优雅、强大且精refined。它是创建从简单到非常复杂的任何基于 Web 的应用程序的绝佳选择。
与许多其他流行的基于 Perl 的项目一样,Catalyst 非常注重灵活性和选择。Catalyst 特别强大,因为它提供了丰富的功能以及核心环境、结构和接口,几乎可以在其上构建任何东西,而不会强迫您以任何特定的方式做事。
使用 Catalyst 编写应用程序也很快捷。仅仅因为您可以自己处理应用程序设计的任何方面,并不意味着您必须这样做。Catalyst 为各种问题和需求提供了各种精refined、高级、即插即用的解决方案,而不会限制对底层细节的访问。模板、ORM、身份验证、自动会话管理以及您希望从 Web 框架获得的所有其他高级功能都可以在 Catalyst 中找到,甚至更多。
Catalyst 的方法是将这些高级功能作为可选插件和模块提供。这是 Perl 最伟大的优势之一——提供了大量的精refined模块和库。因此,Catalyst 没有重新发明所有这些功能,而是提供了一个框架,将已经存在的东西无缝地整合在一起。
Catalyst 比它本身更大——它也是 CPAN 中可用的所有内容。仅此一项就使其成为功能最丰富的框架之一。
在本文中,我将介绍 Catalyst 以及如何使用它进行快速应用程序开发。我将介绍如何创建和布局新应用程序的基础知识,以及如何编写处理请求的操作。我将解释如何定义灵活的 URL 调度逻辑以及一些可用的 API。我将重点介绍基本原理,但我也将介绍一些流行的可用组件,例如 Template::Toolkit。我还将讨论如何扩展 Catalyst 本身,以及如何使用 Apache 部署应用程序。
背景知识和 MVC 架构Catalyst 和 Catalyst 应用程序是用 Perl 编写的,因此要有效地使用 Catalyst,需要一些基本的 Perl 知识。您还应该具备一些面向对象编程概念的经验,例如类、方法、继承等等。
与 Rails、Django、CakePHP 和许多其他 Web 框架一样,Catalyst 遵循历史悠久的 Model-View-Controller 架构模式。MVC 是一种经过验证的方法,用于构建和分割应用程序代码,以提高效率、灵活性和可维护性。
有大量的 MVC 教程和资源可用,因此我不会花太多时间在这里介绍它。如果您使用过其他 Web 框架,那么您很可能已经熟悉 MVC。如果不是,最重要的是要理解它更多的是关于最佳实践,而不是其他任何东西。
本文的重点是解释 Catalyst 运行方式的核心细节,但由于 Catalyst 根据 MVC 做出了大部分布局决策,您仍然会在沿途看到它。
获取 Catalyst在您的系统上安装 Catalyst 之前,您显然需要 Perl。大多数 Linux 发行版已经开箱即用地安装了 Perl,但如果没有,请使用您的软件包管理器安装它。
Catalyst 本身是一个 Perl 库,您可以使用 cpan 安装
cpan Catalyst::Devel
之前的命令安装了 Catalyst 和开发工具以及它的许多依赖项。对于将仅运行应用程序而无需开发工具的生产/托管系统,您可以安装较小的 Catalyst::Runtime 捆绑包。
由于 Catalyst 有很多依赖项,因此在全新的系统上安装可能需要相当长的时间。默认情况下,CPAN 会询问是否应单独安装每个依赖项,这可能会变得非常冗余。您可以配置 CPAN 不询问,但相反,我通常只是通过按住 Enter 几秒钟来排队一堆默认的(“是,安装模块!”)击键/答案来作弊。
如果首次尝试安装失败,请不要担心。无论问题是什么,它都可能会在回滚中解释,以及如何解决它。通常,这仅仅涉及安装/升级另一个模块,由于某种原因,该模块未自动包含在依赖项树中,或者只是第二次运行 cpan
命令。
每个 Catalyst 应用程序都是一个 Perl 模块/库/捆绑包——与 CPAN 上的模块完全一样。这包括包/类命名空间以及标准的文件和目录结构。Catalyst::Devel 包附带一个 helper 脚本,用于创建新的“骨架”应用程序并为您初始化文件和目录。例如,要创建一个名为 KillerApp 的新应用程序,请运行以下命令
catalyst.pl KillerApp
这会在 KillerApp/ 中创建一个新的应用程序结构,其中包含以下子目录
lib/: 这是 Perl 包含目录,用于存储应用程序的所有 Perl 类(又名包或模块)。它在运行时添加到 Perl lib 路径,并且目录结构对应于包/类命名空间。例如,最初创建的两个类具有以下命名空间和相应的文件路径
-
KillerApp — lib/KillerApp.pm
-
KillerApp::Controller::Root — lib/KillerApp/Controller/Root.pm
这些目录也被创建,但最初是空的
-
lib/KillerApp/Model/
-
lib/KillerApp/View/
root/: 这是存储其他类型应用程序特定文件的位置。静态 Web 文件(例如图像、CSS 和 JavaScript)位于子目录 static 中,该子目录通常作为 URL /static 公开。其他类型的文件也放在这里,例如模板。
script/: 这包含应用程序特定的脚本,包括开发服务器 (killerapp_server.pl),您可以使用它在自己的独立 Web 服务器中运行应用程序,以及用于在“真实”Web 服务器中部署应用程序的脚本。helper 脚本 killerapp_create.pl 创建新的模型、视图和控制器组件类。
t/: 这是“测试”的去处。如果您遵循测试驱动的开发过程,那么对于您编写的每个新功能,您还将编写一个自动化测试用例。测试使您可以快速捕获将来可能引入的回归。养成编写测试的习惯是一个好习惯,但这超出了本文的范围。
创建的骨架应用程序已经完全可以运行,您可以使用内置的测试服务器运行它
cd KillerApp/
script/killerapp_server.pl
这会在端口 3000 上在其自己的专用 Web 服务器中启动应用程序。打开 http://localhost:3000/ 以查看默认的首页,该首页最初显示 Catalyst 欢迎消息。
请求/响应周期所有 Web 应用程序都处理请求并生成响应。任何 Web 框架/平台/环境的基本工作是提供一个有用的结构来管理此过程。尽管有不同的方法可以做到这一点——从优雅的 MVC 应用程序到丑陋的、单体的 CGI 脚本——但最终,它们都在做相同的基本事情
决定在请求到达时调用什么。
提供一个用于生成响应的 API。
在 Catalyst 中,这发生在称为“操作”的特殊方法中。在每个请求上,Catalyst 识别一个或多个操作,并使用特殊参数调用它们,包括对“上下文”对象的引用,该对象提供了一个方便实用的 API,通过该 API 可以完成所有其他操作。
操作包含在称为“控制器”的类中,这些类位于应用程序中的特殊路径/命名空间中 (lib/KillerApp/Controller/)。骨架应用程序设置了一个控制器(“Root”),但您可以使用 helper 脚本创建更多控制器。例如,这会创建一个新的控制器类 KillerApp::Controller::Something
script/killerapp_create.pl controller Something
拥有多个控制器的唯一原因是出于组织目的;您可以将所有操作都放在 Root 控制器中,而不会损失功能或能力。控制器只是操作的容器。
在以下部分中,我将描述 Catalyst 如何决定在每个请求上调用哪些操作(“调度”),然后解释如何在其中使用提供的上下文对象。
调度Catalyst 提供了一种特别灵活和强大的机制来配置调度规则。Catalyst 没有单独的配置来将 URL 分配给特定操作,而是使用操作本身来动态确定 URL 映射。
每个操作定义(它只是一个 Perl 子例程)不仅表示一个代码块,还表示哪些 URL 路径适用于它。这是在子例程属性中指定的——一个鲜为人知的 Perl 功能,它提供了可用于内省的任意标签。
Catalyst 支持一些参数化的属性,以各种方式确定操作映射的 URL 路径。例如,以下操作使用 :Path
属性设置了绝对路径
sub myaction :Path('/some/place') {
my ( $self, $c, @args ) = @_;
# do stuff...
}
无论您将其放在哪个控制器中,上述操作都将映射到以 /some/place 开头的所有 URL(使用开发服务器的 http://localhost:3000/some/place)。
如果您省略了起始斜杠并使用 :Path('some/place')
,则该操作将映射到相对于控制器命名空间的路径。例如,如果它在 KillerApp::Controller::Foobar
中,它将映射到以 /foobar/some/place 开头的 URL 路径。
您可以设置 :Local
以使用控制器和方法的名称,而不是使用 :Path
显式设置路径。例如,如果以下操作包含在控制器 KillerApp::Controller::Some
中,它也将映射到 /some/place
sub place :Local {
my ( $self, $c, @args ) = @_;
# do stuff...
}
如果它包含在控制器 KillerApp::Controller::Some::Other
中,它将映射到 /some/other/place。
操作默认包含子路径,因此上述操作也将匹配 /some/other/place/blah/foo/1。发生这种情况时,路径的剩余部分将作为参数提供给操作方法 ('blah','foo','1')。如果完全不匹配子路径,则可以使用 :Args
属性来限制操作将匹配子路径的深度。Args 值为 0 时,此操作将仅匹配 /some/place,但不匹配其下的任何内容
sub myaction :Path('/some/place') :Args(0) {
my ( $self, $c ) = @_;
# do stuff...
}
还有其他属性可用。:Global
的工作方式类似于 :Local
,但忽略控制器名称,路径模式匹配可以使用 :Regex
和 :LocalRegex
完成。
当 URL 匹配多个操作时,Catalyst 会选择最匹配的操作。但是,有一些内置操作(方法名称为“begin”、“end”和“auto”),如果定义了这些操作,则会在每个请求的各个阶段以及匹配的操作之外调用这些操作。使用高级 :Chained
属性类型,您可以配置其他/多个操作,以便使用单个请求以您喜欢的任何顺序调用。
您还可以从操作代码本身以编程方式调度到其他操作/路径
sub myaction :Path('/some/place') {
my ( $self, $c, @args ) = @_;
$c->forward('/some/other/place');
}
上下文对象 ($c
)
控制器操作充当应用程序代码的入口点。一个特殊的按请求对象(称为“上下文”)在调度程序调用每个操作时作为参数提供给每个操作。上下文对象通常读入名为 $c
的变量,但它可以称为任何名称。
上下文提供有关应用程序及其当前状态的接口和信息。它包含当前正在处理的请求的详细信息 ($c->request
) 和对将成为响应的内容 ($c->response
) 的访问。
在请求开始时,在调用任何操作之前,将使用空/默认数据创建响应对象。然后,调用的每个操作都有机会操作响应。在请求结束时,其最终状态将发送回客户端。这种生成响应的迭代方法有助于模块化和动态结构。
以下操作说明了一些可用的简单 API,例如检查请求中的 User-Agent 和查询/post 参数,以及设置响应的正文和标头
sub myaction :Path('/some/place') {
my ( $self, $c, @args ) = @_;
my $myparam = $c->request->params->{myparam};
if(defined $myparam) {
$c->response->body("myparam is $myparam");
}
else {
$c->response->body("myparam was not supplied!!");
}
$c->response->body(
$c->response->body .
"\n\nExtra path args: " . join('/',@args)
) if (@args > 0);
$c->response->headers->header( 'Content-Type' => 'text/plain' );
$c->response->body("Bad command or file name")
if ($c->request->user_agent =~ /MSIE/);
}
访问 URL http://localhost:3000/some/place/boo/baz?myparam=foo 将显示以下文本(除非在使用 IE 时,在这种情况下,将显示“Bad command or file name”取而代之)
myparam is foo
Extra path args: boo/baz
在操作代码中,您可以编写任何您喜欢的逻辑来构建您的应用程序。由于上下文对象只是一个变量,因此您可以将其作为参数传递给其他函数。遵循正常的 Perl 编程规则,您可以使用其他类和库,实例化对象等等。
这就是您必须做的事情的程度——编写控制器操作并使用上下文对象——但这仅仅是您可以做的开始。
Catalyst 组件除了核心功能之外,Catalyst 还提供了强大的 MVC 结构来构建您的应用程序。这包括有用的基类、合理的默认行为和有用的 sugar 函数。您可以通过在提供的框架中将类创建为模型、视图和控制器来利用大量交钥匙功能。
所有这些都被认为是 Catalyst 中的“组件”,并且它们之间实际上没有太多功能差异。Model-View-Controller 别名主要用于分类目的。模型旨在包含数据和业务逻辑;视图应该处理渲染和显示;控制器将所有内容联系在一起。
在操作上,组件本质上是具有一些额外的 Catalyst 特定功能的应用程序类。它们在启动时作为静态对象实例自动加载。可以通过上下文对象在整个应用程序中访问任何组件
sub myaction :Path('/some/place') {
my ( $self, $c, @args ) = @_;
$c->model('MyModel')->do_something;
$c->forward( $c->view('MyView') );
}
在上面的示例操作中,您只是在名为 MyModel
(KillerApp::Model::MyModel) 的模型中调用方法 do_something
,然后 forward
到名为 MyView
(KillerApp::View::MyView) 的视图。
之前,我展示了如何通过提供路径来使用 forward
调度到另一个操作。当您将组件传递给 forward
时,将调用所提供组件的 process
方法,就像它是一个控制器操作一样,这大致相当于
$c->view('MyView')->process($c,@args);
这些只是可用约定和快捷方式的一些示例。重要的是要理解,所有这些 sugar 函数都归结为调用方法和正常的程序流程。
Template::Toolkit 视图Web 应用程序最常见的需求之一是用于渲染内容的模板系统。模板可能是渲染基于文本的内容(尤其是像 HTML 这样的标记)的最佳全方位方法。
Catalyst 可以使用多个 Perl 模板系统,但最流行的是 Template::Toolkit——一个功能强大、通用的模板处理系统,它快速且功能丰富。它具有通用且强大的语法,简单易用,但它也支持高级功能,例如控制结构、流处理和可扩展性。Template::Toolkit 本身就是一种完整的编程语言。
Catalyst 通过视图/组件类 Catalyst::View::TT 提供到 Template::Toolkit 的即插即用接口。您可以使用 helper 脚本在您的应用程序中创建一个扩展此类别的视图。运行此命令以创建一个名为“HTML”的新视图
script/killerapp_create.pl view HTML TT
新视图开箱即可使用。作为 Template::Toolkit 的通用包装器,它提供了一个简单的 API 来选择模板并提供输入数据。其余的视图特定代码都放在应用程序目录中“root”内的模板中。
这是一个渲染 HTML 页面的简单 Template::Toolkit 模板的示例
<html><head>
<h3>[% title %]</h3>
</head><body>
<h1>Message: [% message %]</h1>
</body>
</html>
[% %] 中的字符序列是“指令”——在处理模板时将被替换的代码片段。上面的指令是简单的变量替换,这是最基本的一种。在这种情况下,为 title
和 message
提供的值将在渲染模板时插入。
例如,如果您将上面的模板保存在 root/templates 中名为 main.tt 的文件中,则可以将其与如下操作一起使用
sub myaction :Path('/some/place') :Args(0) {
my ( $self, $c ) = @_;
$c->stash->{template} = 'templates/main.tt';
$c->stash->{data}->{title} = 'TT rendered page';
$c->stash->{data}->{message} = 'A cool message!';
$c->forward( $c->view('HTML') );
}
上面的 stash
对象是 Catalyst 的另一个内置功能,我到目前为止还没有介绍它。它不是很复杂;它只是上下文对象中的一个哈希引用。它提供了一个标准的按请求位置来跨组件共享数据,类似于请求和响应,但用于通用用途。
基于 Catalyst::View::TT 的视图使用 stash 的内容来确定操作。template
的值标识要调用的模板,而整个 stash 用作输入数据——stash 中的每个键都成为模板中的一个变量。从处理模板生成的内容用于设置响应的正文。
真实应用程序中的数据可能比上一个示例中的简单键/值更复杂。Template::Toolkit 的强大功能之一是它能够直接处理 Perl 数据结构。考虑以下操作
sub myaction :Path('/some/place') :Args(0) {
my ( $self, $c ) = @_;
$c->stash->{template} = 'templates/main.tt';
$c->stash->{data} = {
title => 'TT rendered page',
subhash => {
alpha => 'foo',
bravo => 'abdc',
charlie => 'xyz'
},
thinglist => [
'Thing 1',
'Thing 2',
'Big thing',
'Small thing'
]
};
$c->forward( $c->view('HTML') );
}
这将适用于如下模板
<html>
<h3>[% data.title %]</h3>
</head><body>
<b>Alpha:</b> [% data.subhash.alpha %]<br>
<b>Bravo:</b> [% data.subhash.bravo %]<br>
<b>Charlie:</b> [% data.subhash.charlie %]<br>
<br>
<b>List of Things:</b><br>
[% FOREACH item IN data.thinglist %]
[% item %]<br>
[% END %]
</body>
</html>
对象也可以像哈希一样提供和访问。实际上,上下文对象会自动在“c”中提供。例如,如果您想显示客户端的 IP 地址,而不是单独将 $c->request->address
放在 stash 中,您可以直接在模板中访问它,如下所示
[% c.request.address %]
Template::Toolkit 具有更多功能和能力,包括包装器、条件语句、过滤器、函数调用等等。Catalyst::View::TT 还有我在此处未介绍的其他默认值和配置选项(有关更多详细信息,请参阅文档)。
在模板和应用程序的其余部分之间如何平衡逻辑完全取决于您。根据您尝试实现的目标,您的应用程序很容易更多地用 Template::Toolkit 而不是 Perl 编写!
DBIx::Class 模型应用程序的另一个最常见需求之一是数据库。DBIx::Class(通常缩短为 DBIC)已成为 Perl 最流行的 ORM(对象关系映射器)库。它是许多关系数据库服务器(包括 MySQL、PostgreSQL、Oracle、MSSQL 和许多其他服务器)的异常强大、健壮、面向对象的接口。
与 Template::Toolkit 类似,但程度更高,Catalyst 提供了精refined的、即插即用组件包装器来与 DBIx::Class (Catalyst::Model::DBIC::Schema) 接口。
使用 DBIx::Class 本身就是一个主题,我没有空间在这里介绍,但如果您计划将应用程序与数据库集成,则它是必备的。有关从哪里开始学习这个出色的库的信息,请参阅资源。
插件和应用程序范围的设置除了用于即插即用功能的预构建组件类之外,还有许多插件可用于修改行为和扩展 Catalyst 本身的功能。一些最常见的可选插件是身份验证、授权和会话插件。
这些插件为处理这些任务提供了一致的 API,并具有各种可用的后端。与核心请求/响应对象接口一样,它们作为应用程序范围的功能提供,这些功能通过上下文对象中的方法访问和控制,一旦加载这些插件,这些方法就可以使用。
您可以像这样验证用户身份(例如,在处理登录表单 post 的操作中)
$c->authenticate({
username => $c->request->params->{username},
password => $c->request->params->{password}
});
如果成功,则 $c->user
对象在后续请求中可用,以根据经过身份验证的用户控制和定向应用程序流程。这是通过自动为您处理的会话(通常是基于 cookie 的)完成的。您还可以访问 $c->session
以跨请求持久化任何其他按会话数据。
此框架的 API 与后端无关,并且有许多后端可用。您可以通过数据库 (DBIC)、系统帐户、PAM 和 LDAP 来处理身份验证和用户存储,仅举几例。还有多种方法可以处理会话数据以支持不同的应用程序需求,例如分布式服务器部署等等。(有关更多信息,请参阅 Catalyst::Plugin::Authentication、Catalyst::Plugin::Authorization 和 Catalyst::Plugin::Session 的文档。)
插件和应用程序范围的设置在主/核心类 (lib/KillerApp.pm) 中配置。在此文件中,您可以指定全局配置参数、加载插件,甚至添加您自己的代码以覆盖和扩展核心功能。
顶层“KillerApp”类实际上是应用程序——它以编程方式加载和集成整个系统中的其他组件和类。像任何派生类一样,它的行为可以有选择地从其父类(“Catalyst”)的行为中更改。由于它使用强大的“Moose”对象系统,除了添加和替换方法之外,您还可以利用其他强大的功能,例如方法修饰符和 Roles(实际上,插件本质上是应用于此类别的 Moose Roles)。
Catalyst 的编写考虑了自定义和可扩展性。它的结构允许以细粒度的方式轻松修改其功能和行为。
例如,您可以通过将如下方法修饰符添加到 lib/KillerApp.pm,简单地配置每个响应在整个应用程序中都设置为“no-cache”
before 'finalize_headers' => sub {
my $c = shift;
$c->response->headers->header( 'Cache-control' => 'no-cache' );
};
Catalyst 在处理的各个阶段调用具有有意义名称(例如 'finalize_headers')的方法,您可以自由地挂钩或覆盖这些方法。
部署您的应用程序与 Catalyst 中的大多数事物一样,当您准备好将应用程序部署到真正的 Web 服务器时,有很多选项可用——Apache/FastCGI 是可用的最佳选择之一。我在下面简要介绍一下。
例如,如果您将应用程序放在 /var/www 中,则可以使用如下 Apache 虚拟主机配置进行部署
<VirtualHost *:80>
ServerName www.example.com
ServerAdmin webmaster@example.com
Alias /static/ /var/www/KillerApp/root/static/
FastCgiServer /var/www/KillerApp/script/killerapp_fastcgi.pl \
-processes 5
Alias / /var/www/KillerApp/script/killerapp_fastcgi.pl/
ErrorLog /var/www/logs/error_log
CustomLog /var/www/logs/access_log combined
</VirtualHost>
FastCGI 是一个经过验证的、与语言无关的接口,用于运行 Web 应用程序。它本质上只是普通的 CGI,但它使程序在后台运行,而不是为每个请求启动它们。这是 FastCGI 克服的 CGI 的主要限制。FastCGI 已经存在很长时间了。这种高效协议的使用是 Catalyst 如何利用现有解决方案的另一个示例。
FastCGI 允许您指定要运行的进程数(在上面的示例中为五个),使您的应用程序成为多线程的。传入的请求在进程之间均匀分布,这些进程维护在一个池中。
上面 <code>/static/</code> 的别名告诉 Apache 直接在此目录中提供文件(图像、CSS、JavaScript 文件等等)。这比通过应用程序提供这些文件更有效,这是不必要的。
结论本文旨在仅提供 Catalyst 及其功能的浅尝辄止的介绍。正如我希望您所看到的,Catalyst 是任何 Web 开发项目的可行平台。凭借灵活的设计和许多可用的成熟功能,您可以使用 Catalyst 快速方便地构建健壮的应用程序。
Catalyst 正在积极开发中,并且一直在变得更好,包括其不断提高的质量文档。它还有一个非常活跃的用户社区,顶级专家可以通过 IRC 获得。
当您准备好开始编写应用程序时,您应该能够找到您需要的信息和支持,以便快速入门。有关重要链接以及从哪里开始,请参阅本文的资源。
资源Catalyst 主页:http://www.catalystframework.org
Catalyst::Manual:http://search.cpan.org/perldoc?Catalyst::Manual
Template Toolkit 主页:http://www.template-toolkit.org
DBIx::Class::Manual:http://search.cpan.org/perldoc?DBIx::Class::Manual
Catalyst IRC 频道:#catalyst 在 http://irc.perl.org 上
Henry Van Styn 的“Moose”,LJ,2011 年 9 月:https://linuxjournal.cn/content/moose
Catalyst 权威指南 (c) 2009 ISBN:978-1-4302-2365-8(印刷版)和 978-1-4302-2366-5(在线版)。