催化剂

Web框架是目前一个重要的研究领域。现在我们都已经掌握了Web编程的基础,我们准备将常用的功能去除,集中精力处理当前的任务;没有人愿意花时间去重写处理参数处理、请求分配等相同的粘合代码。

目前Web应用程序中流行的模型是MVC,即模型-视图-控制器。这种设计模式最初来自Smalltalk,支持将应用程序的三个主要领域分开——处理应用流程(控制器)、处理信息(模型)和输出结果(视图),这样就可以在不影响其他方面的情况下改变或替换任何一个。

Catalyst是一个新的Perl MVC框架。它目前正在快速发展,但核心API现在已经稳定,越来越多的项目正在使用它。Catalyst借鉴了其他框架,如Ruby on Rails和Apache Struts,但其主要目标是成为一个灵活、强大且快速的框架,用于在Perl中开发任何类型的Web项目。本文是系列文章的第一篇,介绍了Catalyst并展示了一个简单的应用;后续的文章将展示如何编写更复杂的项目。

灵感来源

Catalyst是从Maypole演变而来的,Maypole是一个由Simon Cozens开发的MVC框架(去年在Perl.com上讨论过;例如,参见“使用Maypole快速开发Web应用程序”)。Maypole在处理Web上的典型CRUD(创建、检索、更新、删除)数据库方面工作得很好。它包含各种有用的方法和预写模板和模板宏,使得设置功能强大的Web数据库变得非常容易。然而,它过于专注于CRUD,对于其他任务来说不太灵活。Catalyst的目标之一是提供一个适合任何Web相关项目的框架。

Ruby on Rails是另一个灵感来源;这个流行的系统对Ruby编程语言产生了很大的兴趣。我们从RoR借鉴的功能包括使用辅助脚本生成应用组件以及在单个应用中拥有多个控制器的功能。RoR和Struts都允许在应用中使用转发,这也被证明对Catalyst很有用。

特性

速度

我们将Catalyst规划为面向企业级的框架,能够处理大量的负载。它大量使用缓存。Catalyst应用在编译时将其动作注册在分发器中,这使得处理运行时请求变得非常快速,无需进行复杂的检查。正则表达式分发都是预先编译的。Catalyst仅构建所需的架构,因此不会产生(例如)未使用的数据库关系生成的延迟。

简单性

组件

Catalyst为常见的模块和任务提供了许多预构建的组件和插件。例如,有适用于Template Toolkit、HTML::Template、Mason、Petal和PSP的View类。插件适用于数十个应用程序和功能,包括Data::FormValidator、基于LDAP或Class::DBI的身份验证、几个缓存模块、HTML::FillInForm和XML-RPC。

催化剂支持组件自动发现;如果你将组件放在正确的位置,催化剂将自动找到并加载它。只需将目录控制器放在 /AppName/Controller/Catalog.pm(或在实际应用中,在缩短的 /AppName/C/Catalog.pm)中;无需对每个项目使用 use。您还可以在应用程序类中使用简短名称声明插件,以便一次性加载 Catalyst::Plugin::EmailCatalyst::Plugin::PrototypeCatalyst::Plugin::Textile

use Catalyst qw/Email Prototype Textile/;

开发

催化剂自带了一个轻量级的HTTP服务器,用于开发目的。它可以在任何平台上运行;您可以快速重新启动它以重新加载任何更改。此服务器与生产级服务器功能相似,因此您可以在整个测试过程中使用它—甚至更长;如果您想交付一个独立的桌面应用程序,这是一个不错的选择。可伸缩性很简单:当您想要继续前进时,将引擎切换到使用纯CGI、mod_perl1mod_perl2、FastCGI,甚至是Zeus网络服务器都是微不足道的。

调试(图1)和记录(图2)支持也已内置。启用调试后,催化剂将非常详细的报告发送到错误日志,包括加载组件的摘要、每个操作和请求的细粒度计时、请求的参数列表等。日志是通过使用 Catalyst::Log 类来实现的;您可以通过添加类似以下行来记录任何操作以进行调试或信息目的:

$c->log->info("We made it past the for loop");
$c->log->debug( $sql_query );

日志截图 图1. 日志

崩溃将显示一个具有相关数据结构、软件和操作系统版本以及错误行号的闪亮调试屏幕。

调试截图 图2. 调试

辅助脚本,使用模板工具包生成,可用于主要应用程序和大多数组件。这些允许您快速生成应用程序框架的启动代码(包括基本单元测试)。您只需一行代码就可以创建一个基于 Class::DBIModel 类,它引入适当的Catalyst基模型类,设置CDBI配置哈希的模式,并生成 perldoc 框架。

灵活性

催化剂允许您使用多个模型、视图和控制台—不仅作为设置应用程序时的选项,而且作为应用程序流程的完全灵活部分。您可以在同一应用程序或甚至在同一方法内混合和匹配不同的元素。想用 Class::DBI 存储数据库并使用 LDAP 进行认证吗?您有两个模型。想用 Template Toolkit 进行网络显示并使用 PDF::Template 进行打印输出吗?没问题。Catalyst使用简单的积木方法来构建其插件:如果您想使用组件,您就说出来,如果您不说,Catalyst就不会使用它。基于CPAN模块的如此多的组件和插件可供使用,您可以很容易地使用您想要的,但不必使用您不需要的。Catalyst具有高级URL到动作的调度。有多种方法将URL映射到动作(即Catalyst方法),具体取决于您的需求。首先,有直接调度,它将匹配特定的路径:package MyApp::C::Quux;

# matches only http://localhost:3000/foo/bar/yada
sub baz : Path('foo/bar/yada') { }

顶级或全局调度将直接匹配应用程序基目录的方法名

package MyApp::C::Foo;

# matches only http://localhost:3000/bar
sub bar : Global { }

本地或命名空间前缀调度仅在从您的控制台类名称派生的命名空间内执行

package MyApp::C::Catalog::Product;

# matches http://localhost:3000/catalog/product/buy
sub buy : Local { }

package MyApp::C::Catalog::Order;

# matches http://localhost:3000/catalog/order/review
sub review : Local { }

最灵活的是正则表达式调度,它作用于与键中模式匹配的URL。如果您使用捕获圆括号,则匹配的值将在 $c->request->snippets 数组中可用。

package MyApp::C::Catalog;

# will match http://localhost:3000/item23/order189
sub bar : Regex('^item(\d+)/order(\d+)$') { 
   my ( $self, $c ) = @_;
   my $item_number  = $c->request->snippets->[0];
   my $order_number = $c->request->snippets->[1];
   # ...    
}

正则表达式将在全局范围内起作用;如果您想让它只在命名空间内起作用,请在正则表达式的正文中使用命名空间名称

sub foo : Regex('^catalog/item(\d+)$') { # ...

最后,您还可以有私有方法,这些方法永远不会通过URL提供。您只能从应用程序内部通过命名空间前缀路径访问它们

package MyApp::C::Foo;
# matches nothing, and is only available via $c->forward('/foo/bar').
sub bar : Private { }

应用程序中始终可用单个 Context 对象($context,或更常见的是其别名 $c),这是与其他元素交互的主要方式。通过此对象,您可以访问请求对象($c->request->params 将返回或设置参数,$c->request->cookies 将返回或设置 cookie),在组件之间共享数据,并控制应用程序的流程。响应对象包含特定于响应的信息($c->response->status(404))并且直接提供了 Catalyst::Log 类,如上所示。 stash 是在应用程序组件之间共享数据的一个通用哈希表。

$c->stash->{error_message} = "You must select an entry";

# then, in a TT template:
[% IF error_message %]
   <h3>[% error_message %]</h3>
[% END %]

Stash 值直接进入模板,但整个上下文对象也可用。

<h1>[% c.config.name %]</h1>

为了展示 Mason 示例,如果您想使用 Catalyst::View::Mason

% foreach my $k (keys $c->req->params) {
  param: <% $k %>: value: <% $c->req->params->{$k} %>
% }

示例应用程序:MiniMojo,一个基于 Ajax 的 30 行代码的 Wiki

现在您对 Catalyst 有了一定的了解,是时候看看它能做什么了。示例应用程序是 MiniMojo,它是一个基于 Ajax 的 wiki,Ajax 是一个使用 XMLHttpRequest 对象创建高度动态网页的 JavaScript 框架,无需在服务器和客户端之间来回发送整个页面。

请记住,从 Catalyst 的角度来看,Ajax 只是在浏览器中发送更多文本的情况,只是这些文本是客户端 JavaScript 的形式,与服务器通信,而不是样板版权声明或导航侧边栏。这不会对 Catalyst 产生影响。

安装

Catalyst 有相当多的要求;然而,大多数以及它们的依赖关系都很容易从 CPAN 安装。以下列表应包括您为此项目所需的一切。

生成应用程序骨架

运行此命令

$ catalyst.pl MiniMojo
$ cd MiniMojo

您已经为您整个应用程序创建了骨架,包括一个用于生成单个类、基本测试脚本等的关键脚本。

运行内置服务器

$ script/minimojo_server.pl

MiniMojo 已经启动,尽管它目前还没有做什么。(您应该收到了一个只包含文字“恭喜,MiniMojo 已在 Catalyst 上!”的网页。)按 Ctrl-C 停止服务器。

向您的应用程序类添加基本方法

通过编辑新文件向 lib/MiniMojo.pm 中的应用程序类添加一个私有的 end 动作

sub end : Private {
    my ( $self, $c ) = @_;
    $c->forward('MiniMojo::V::TT') unless $c->res->output;
}

Catalyst 在请求周期结束时自动调用 end 动作。这是四个内置私有动作之一。在 Catalyst 中使用 end 将应用程序转发到视图组件进行渲染是典型模式,尽管如果需要,您也可以自己执行(例如,如果您想在同一个应用程序中使用不同的视图——可能是一个使用 Template Toolkit 生成网页的视图,另一个使用 PDF::Template 生成 PDF 的视图)。

用以下内容替换同一类中的现有、由助手生成的 default 动作

sub default : Private {
    my ( $self, $c ) = @_;
    $c->forward('/page/show');
}

如果客户端指定了其他合适的动作,这将转发到页面控制器的 show 方法。作为私有动作,任何外部应用程序都无法调用它们。应用程序内的任何方法都可以调用它们。与 beginautoend 一样,default 动作是另一个内置私有动作。同样,Catalyst 在请求周期的相关点上自动调用它们。

设置模型(SQLite 数据库)并使用助手创建模型类

接下来,创建一个文件,minimojo.sql,其中包含在 SQLite 中设置 page 表的 SQL。

-- minimojo.sql
CREATE TABLE page (
    id INTEGER PRIMARY KEY,
    title TEXT,
    body TEXT
);

使用 sqlite 命令行程序从它创建一个数据库

$ sqlite minimojo.db < minimojo.sql

根据您的设置,可能需要将其作为 sqlite3 调用。

使用辅助工具创建模型类和基本单元测试(图3显示了结果)

$ script/minimojo_create.pl model CDBI CDBI dbi:SQLite:/path/to/minimojo.db

模型创建截图 图3. 创建模型

minimojo_create.pl 脚本是使用模板工具包来自动化创建特定模块的辅助工具。上一个命令使用 CDBI 辅助工具创建了一个名为 CDBI.pm 的模型(与控制器或视图不同),设置连接字符串为 dbi:SQLite:/path/to/minimojo.db,即您刚刚创建的数据库。(使用适合您系统的路径。)辅助工具将模型写入 lib/MiniMojo/M/ 目录下。辅助脚本有多种选项;唯一的要求是类型和名称。(您也可以从头开始创建自己的模块,而不使用辅助工具。)

设置视图(Template::Toolkit)并使用辅助工具创建视图类

使用辅助工具创建视图类

$ script/minimojo_create.pl view TT TT

视图类放入 lib/MiniMojo/V/

使用辅助工具设置控制器类

使用辅助工具创建一个名为 Page 的控制器类

$ script/minimojo_create.pl controller Page

控制器类位于 lib/MiniMojo/C/

lib/MiniMojo/C/Page.pm 添加一个 show 动作

sub show : Regex('^(\w+)\.html$') {
    my ( $self, $c ) = @_;
    $c->stash->{template} = 'view.tt';
    # $c->forward('page');
}

Regex 分发匹配 foo.html 中的页面,其中 foo 是任意单词字符序列。这个序列在 $context->request->snippets 数组中可用,其中 page 动作使用它来显示现有页面或创建新页面。此动作的其余部分设置适当的模板并将应用程序发送到 page 动作。(在您编写了 page 动作之前,请注释掉 forward 命令。)

使用 $ script/minimojo_server.pl 重新启动服务器,并将网页浏览器指向 http://localhost:3000/show/ 以查看调试屏幕(您还没有 show 尝试发送人的模板)。

创建 root/view.tt

<html>
    <head><title>MiniMojo</title></head>
    <body>
        <h1>MiniMojo is set up!</h1>
    </body>
</html>

通过按 Ctrl-C 杀死服务器并重新启动它,再次进行测试,并转到 http://localhost:3000/show/。您应该看到您刚刚定义的页面。

添加显示和编辑代码

修改应用程序类 lib/MiniMojo.pm 以包含 PrototypeTextile 插件

use Catalyst qw/-Debug Prototype Textile/;

请注意,您可以通过指定其基本名称来使用插件;Catalyst 可以确定您的意图,而无需使用 Catalyst::Plugin::Prototype

修改 page 控制器,lib/MiniMojo/C/Page.pm,以添加页面视图和编辑代码

sub page : Private {
    my ( $self, $c, $title ) = @_;
    $title ||= $c->req->snippets->[0] || 'Frontpage';
    my $query = { title => $title };
    $c->stash->{page} = MiniMojo::M::CDBI::Page->find_or_create($query);
}

私有的 page 方法设置一个标题——要么传递给它,要么从 snippets 数组(与 show 中的正则表达式匹配)中获取,或者默认为“首页”。$query 变量包含一个用于 Class::DBIfind_or_create 方法的 hashref,将此 CDBI 查询的结果用于填充 page 变量的 stash。在方法末尾,控制流返回到调用方法。

现在取消注释 show 动作中的 $c->forward('page'); 行。

sub edit : Local {
    my ( $self, $c, $title ) = @_;
    $c->forward('page');
    $c->stash->{page}->body( $c->req->params->{body} )
      if $c->req->params->{body};
    my $body = $c->stash->{page}->body || 'Just type something...';
    my $html = $c->textile->process($body);

    my $base = $c->req->base;
    $html    =~ s{(?<![\?\\\/\[])(\b[A-Z][a-z]+[A-Z]\w*)}
                 {<a href="$base$1.html">$1</a>}g;

    $c->res->output($html);
}

edit 方法首先将动作转发到 page,以便 page 变量的 stash 包含 CDBI 查询的结果。如果存在 body 的值,它将使用此值;否则默认为“Just type something…”。然后,代码使用 Textile 处理正文,将纯文本转换为 HTML,然后通过正则表达式将驼峰式文本转换为链接,链接的基础 URL 来自 Catalyst 请求对象。最后,输出 HTML。

使用 Ajax 设置 Wiki

修改 root/view.tt 以包含 Ajax 代码

<html>
     <head><title>MiniMojo</title></head>
     [% c.prototype.define_javascript_functions %]
     [% url = base _ 'page/edit/' _ page.title %]
     <body Onload="new Ajax.Updater( 'view',  '[% url %]' )">
         <h1>[% page.title %]</h1>
         <div id="view"></div>
         <textarea id="editor" rows="24" cols="80">[% page.body %]</textarea>
         [% c.prototype.observe_field( 'editor', {
             url => url,
             with => "'body='+value",
             update => 'view' }
         ) %]
     </body>
</html>

以下行

[% c.prototype.define_javascript_functions %]

script 块中包含整个 prototype.js 库。请注意,prototype 插件在上下文对象中可用。

以下部分

[% url = base _ 'page/edit/' _ page.title %] 
<body Onload="new Ajax.Updater( 'view',  '[% url %]' )">
<h1>[% page.title %]</h1>
<div id="view"></div>

构建 Ajax URL 并在加载页面时更新视图 div

最后

<textarea id="editor" rows="24" cols="80">[% page.body %]</textarea>
    [% c.prototype.observe_field( 'editor', {
        url => url,
        with => "'body='+value",
        update => 'view' }
    ) %]

定期检查 textarea 中的更改,并在需要时发出 Ajax 请求。

这就完成了!现在你可以重新启动服务器,你的维基百科就绪并运行了(图4)。要使用维基百科,只需在textarea中开始输入。随着你输入,维基百科会定期回显你的输入,并通过格式化器进行处理。当你用驼峰式输入时,它会自动创建一个可以点击的链接,带你跳转到新页面。

运行中的维基百科截图 图4. 运行中的维基百科

享受你的新Catalyst驱动的Ajax维基百科吧!

资源

更多信息请参阅Catalyst文档,特别是Catalyst::Manual::Intro模块,它全面介绍了该框架。有两个Catalyst邮件列表,一个是通用列表,一个是开发者列表。不过,讨论Catalyst的最佳场所是irc.perl.org上的#catalyst IRC频道。目前,Catalyst主页只是一个链接集合,但我们将在不久的将来扩展它。

感谢Catalyst首席开发者Sebastian Riedel为此文章以及Catalyst本身提供的帮助。

标签

反馈

这篇文章有什么问题吗?请在GitHub上打开一个问题或拉取请求来帮助我们。