Embperl: 现代模板

作者:Reuven M. Lerner

今年早些时候,我介绍了 mod_perl,这是一个 Apache Web 服务器的模块,它在 Apache 内部嵌入了完整版本的 Perl。这不仅允许您编写可以克服 CGI 瓶颈问题的 CGI 风格的程序,而且还使您可以访问 Apache 的内部结构,从而以许多新的方式配置您的服务器。许多开发人员已经开始利用这种灵活性,以新的和巧妙的方式配置 Apache。

Gerald Richter (richter@dev.ecos.de) 编写的 Embperl 就是这样一个巧妙的想法。Embperl 允许您创建 HTML 和 Perl 的混合页面。正如我们在之前的几个专栏中看到的那样,模板允许设计师和程序员修改网站的各个部分,而不会互相妨碍。如果程序员想要修改逻辑,他或她可以通过修改模板的 Perl 部分来做到这一点。同样,设计师可以修改页面的外观,而不必要求程序员更改 CGI 程序中的几个 print 语句。

Embperl 只是 mod_perl 可用的几个模板系统之一。另一个竞争者是 ePerl,我读了很多关于它的文章,但还没有机会尝试。另一种解决方案是 Text::Template,它使用 Perl 但不依赖于 mod_perl 或 Apache,这是一个我在以前的专栏中讨论模板时使用过的模块。最后,PHP 是一种嵌入式脚本语言,它在许多方面类似于 C 和 Perl,旨在与文档内部的 HTML 交错使用。要查找有关所有这些的更多信息,包括 URL,请参阅资源。

Embperl 如何工作?

在我们使用 Embperl 之前,重要的是要了解 HTTP 请求和响应是如何形成的,以及 Web 服务器如何执行其工作。当您单击网页链接时,您的浏览器会连接到 URL 中的主机名,并向服务器发送一个简短的请求。该请求由一个动词(通常为 GET 或 POST)、所请求文档的名称以及浏览器支持的 HTTP 版本组成。例如,要从 Web 服务器请求根文档,浏览器通常会发送

GET / HTTP/1.0

到服务器。服务器的责任是处理请求,并以错误消息或文档进行响应。根据浏览器运行的 HTTP 版本,服务器可能会在同一响应中返回多个文档,要求某种用户身份验证才能继续,或者将用户的浏览器重定向到不同的 URL。

但在许多情况下,服务器根本不会返回文档。相反,它将运行一个程序,返回程序的输出,而不是其内容。这就是 CGI 程序的工作方式:服务器配置为将某个目录中的所有文件都视为程序,而不是要逐字检索的文档。(事实上,当用户可以检索程序的内容而不是看到它们的输出时,就会出现安全问题。)就浏览器而言,它请求了一个文档并收到了一个文档作为响应。魔术发生在服务器端,程序在那里执行并产生其输出。

除了 CGI 程序的执行时间之外,还需要为其付出代价:因为 Web 服务器为每个 CGI 程序派生一个单独的进程,并且 Perl(和其他流行的脚本语言)可能需要很长的启动时间,所以程序启动所需的时间通常比实际运行所需的时间更长。

因此,每个 Web 服务器都开发了自己的本机 API,允许程序比 CGI 更紧密地绑定到服务器的内部代码。Netscape 的 NSAPI 和 Microsoft 的 ISAPI 是此类专有系统的两个示例,而 Apache 的 mod_perl 是如何为 Perl 程序员提供类似功能的示例。在您的服务器中安装了 mod_perl 后,操作速度会大大提高,因为服务器会编译程序一次,而不是每次运行时都编译。此外,由于程序永远不需要创建单独的进程,因此与执行此类程序相关的开销相对较低。

Mod_perl 也许最广为人知的是它允许程序员编写非常快速的类似 CGI 的程序。但是,由于可以通过 mod_perl 访问 Apache 的内部结构,因此可以编写 Perl 程序来更改 Apache 处理传出文档的一个或多个步骤。这些可以从普通到花哨不等;在 Embperl 的情况下,我们正在为特定文档设置一个特殊的 PerlHandler。在 Apache 世界中,“handler”是一个程序,它在将目录中的文件返回给 HTTP 客户端之前,对这些文件进行一些特殊处理。您可以将 handler 视为 Apache 和文件之间的中间人;handler 抓取文件并根据需要进行修改,然后将完成的产品交给 Apache。然后,Apache 将此完成的产品获取并将其返回到用户浏览器中的 HTTP 响应中。

安装 Embperl

请注意,安装 Embperl 可能有点棘手。该文档通常很好,并描述了在您自己的计算机上安装它所需的所有步骤。我已经安装了几次,发现每次都需要多次尝试才能正确地按照说明进行操作。我将在这里详细描述该过程,但您可能需要查看 Embperl 附带的 FAQ 文件以获取更多信息。(以下许多说明都基于该 FAQ。)

在您开始之前,您应该安装或更新几个软件包的最新版本:LWP(Web 客户端编程的库)、HTML::HeadParser(用于解析 HTML 文档头)、CGI.pm(处理与 CGI 相关的所有内容的超级模块)和 MIME::Base64(处理与 Base64 之间编码的信息,该信息用于 MIME 标准)。所有这些都可以从 CPAN 获得(请参阅资源)。

必须重新编译 mod_perl 和 Apache 才能使 Embperl 运行。Embperl 可以作为外部 CGI 程序运行,而不是在 mod_perl 中运行,但是您将失去 mod_perl 的速度优势。我强烈建议采用 mod_perl 路线,除非您使用的 Web 服务器不是 Apache,或者您现在不想重新编译任何东西。

因此,首先您需要 Apache 的源代码(来自 https://apache.ac.cn/)、mod_perl(来自 CPAN,位于 https://perldotcom.perl5.cn/CPAN/)和 Embperl(也来自 CPAN,作为 HTML-Embperl-1.0.0)。在我的机器上,这些包的命名如下

HTML-Embperl-1.0.0.tar.gz
apache_1.3.0.tar.gz
mod_perl-1.12.tar.gz

我相信当您阅读本文时,这些程序的更新版本将会可用。但是,您应该能够通过更新版本号来遵循此讨论。

首先,使用命令解压所有文件

for file in `ls *gz`; do tar -zxvf $file; done

在您可以开始编译组件之前,您必须设置一些配置并修改 Makefile。首先,进入 mod_perl 目录并编辑 src/modules/perl/Makefile

/downloads/mod_perl-1.12/src/modules/perl/Makefile
您需要对此文件进行三处更改。首先,将 HTML::Embperl 添加到 STATIC_EXTS 的定义中,该定义将被 mod_perl 配置系统获取。也就是说,编辑该行(mod_perl-1.12 中的第 98 行)
#STATIC_EXTS = Apache Apache::Constants
并将其更改为
#STATIC_EXTS = Apache Apache::Constants HTML::Embperl
接下来,查找以 OBJS= 开头的行(mod_perl-1.12 中的第 131 行)。在该行之前,定义变量 EPDIR,使其指向您的 Embperl 构建目录。例如,假设我们正在 /downloads/HTML-Embperl-1.0.0 中构建 Embperl,我们将将其设置为
EPDIR=/downloads/HTML-Embperl-1.0.0
我们现在将修改 OBJS 变量,以便它为 Embperl 以及 mod_perl 创建对象文件
OBJS=$(PERLSRC:.c=.o) $(EPDIR)/Embperl.o \
$(EPDIR)/epmain.o $(EPDIR)/epio.o \
$(EPDIR)/epeval.o $(EPDIR)/epcmd.o \
$(EPDIR)/epchar.o $(EPDIR)/eputil.o
不要忘记在每个连续行的末尾放反斜杠,以便 make 不会认为第二行和第三行应该独立存在。

最难的部分已经结束。我们现在要做的就是配置和编译各个组件。但是,请确保以正确的顺序执行它们,否则事情可能无法正常工作。

首先,进入 mod_perl 目录并使用标准 Perl 命令 perl Makefile.PL 创建 Makefile。

如果您想要 mod_perl 的某些或全部功能,那么现在是指定的时候。我倾向于激活所有这些功能(除了两个需要显式激活的功能),所以我输入 perl Makefile.PL EVERYTHING=1。这将开始 mod_perl 配置过程。系统将询问您是否要使用并行目录中的 Apache 源代码,以及是否要 mod_perl 为您构建 httpd。对两个问题都回答“是”。

配置脚本运行完毕后,进入 Embperl 目录(/downloads/HTML-Embperl-1.0.0)并使用相同的命令配置模块

perl Makefile.PL

再次,系统将执行各种配置。系统将询问您是否要 Embperl 支持 Apache,以及是否应该使用并行目录中的 Apache 源代码。同样,对两个问题都回答“是”。最后,系统将要求您提供用于测试的 httpd 副本的路径名。检查默认值是否正确,如有必要,请更正它。

现在我们可以通过在目录中键入 make 来实际创建 Embperl。编译完成后,切换回 mod_perl 目录,并通过键入 make 来创建 mod_perl 和 Apache。

恭喜。您现在应该拥有 Apache、mod_perl 和 Embperl 的工作副本。此时,我们可以在三个目录中的每一个目录中运行 make install 来安装该软件,或者我们可以测试 Embperl。如果您有兴趣在不安装的情况下测试您的 Embperl 编译,我建议您阅读 FAQ。这些说明并不难遵循,但它们比我可以在此处提供的空间中描述的要复杂。

配置 Apache 以允许 Embperl

现在 Embperl 是您的 Apache 副本的一部分,您可以用它做什么?目前还没有什么,因为我们还没有为我们的 Embperl 文件定义 handler。现在我们将必须修改 Apache 的配置文件,这些文件可能位于多个可能的位置。当我安装 Apache 时,我接受了默认的安装位置(在 /usr/local/apache 下),我的路径名将反映这一点。

为了让 Embperl 工作,我们需要修改 Apache 的两个配置文件。(这三个文件中的每一个实际上都可以包含任何配置指令,但某些项目传统上放在某些文件中。)我告诉服务器将以 /embperl 开头的 URL 重定向到 /usr/local/apache/share/embperl,方法是将以下行添加到 srm.conf 文件中

Alias /embperl /usr/local/apache/share/embperl

接下来,我告诉 Apache 安装 Embperl 作为该目录的 handler。正如我上面提到的,这意味着每次 Apache 被要求从 /embperl 中检索文档时,都会调用 HTML::Embperl。该文件将从磁盘读取,由 Embperl 处理,最后交给 Apache,后者将其返回给用户的计算机。我将以下内容添加到 access.conf 文件中

<Location /embperl>
 SetHandler perl-script
 PerlHandler HTML::Embperl
 Options ExecCGI
 </Location>
安装这些更改后,我们使用以下命令重新启动 Apache
/usr/local/apache/sbin/apachectl restart
Embperl 语法

Embperl 文件看起来就像 HTML 文件,只有一个很小的区别:方括号表示特殊的代码段,这些代码段会被单独解释。换句话说,您可以将标准的 HTML 文件放在 Embperl 目录中,尽管我倾向于不建议这样做,因为会涉及到额外的开销。为什么要强制 Embperl 不必要地查看一个文件呢?因此,一些站点决定使用一个特殊的后缀——例如,.htmpl——然后配置 Apache,以便所有带有该后缀的文件,无论位于哪个目录,都会被解释。这允许 HTML 文件与它们的 Embperl 文件副本混合在一起。

以下文件,如果从为 Embperl 定义的目录中检索,将打印当前时间

<HTML>
<Head><Title>Current time</Title></Head>
<Body>
<P>This is Embperl</P>
<!-- Below are the square brackets -->
<P>[+ localtime(time) +]</P>
</Body>
</HTML>

从 Embperl 目录中检索此文件将产生与以下简短 CGI 程序相同的输出

#!/usr/bin/perl -w
 use strict;
 use diagnostics;
 use CGI;
 # Create a new instance of CGI
 my $query = new CGI;
 # Send a MIME header
 print $query->header("text/html");
 # Send the HTML
 print $query->start_html(-title =>
        "This is Embperl");
 print scalar localtime(time);
 print $query->end_html;
然而,Embperl 相比 CGI 程序有几个优势。首先,在 mod_perl 下运行它具有明显的速度优势。当然,我们可以修改我们的 CGI 程序和/或 Apache 配置,以便该程序可以在 Apache::Registry 下运行,Apache::Registry 是处理类似 CGI 程序的 mod_perl 模块。

但最大的优势是静态内容和动态内容之间的清晰分离。程序员不再成为瓶颈,减缓设计和内容变更——现在网站的设计者和编辑可以修改 HTML,只要他们避开方括号内的 Perl 代码即可。当然,有时嵌入的 Perl 代码会影响设计,在这种情况下,程序员可以参与其中。但在大多数情况下,这种分离允许每个人都做他们最擅长的事情。

我们已经看到了 Embperl 括号的一种形式,即 [++]。方括号加号中的任何内容都会被评估为 Perl 代码,结果会被插入到 HTML 文档中并传递给浏览器。请记住,评估 Perl 变量或字符串的结果是该变量或字符串的值。通常,方括号加号包含一个变量名,其内容被插入到文档中的指定位置。不要使用 print 函数将内容插入到 Embperl 文档中,因为 print 将输出发送到 STDOUT,然后返回一个指示是否成功的状态。每组方括号加号可以包含您喜欢的任意多或少的 Perl 代码,尽管大多数 Embperl 程序员似乎更喜欢保持代码行简短。

方括号加号的输出直接放置到文件中,没有任何额外的格式。如果您希望某些内容位于段落标签、粗体、斜体或不同的字体中,您有责任确保实现这一点。通常,您需要用适当的 HTML 标签包围方括号加号,以便生成的输出能够被正确格式化。也就是说,您可以通过以下方式使变量的值变为斜体:

[+ "<i>$variable</i>" +]

但是,为了维护和分离静态内容和动态内容,最好这样说:

<i>[+ $variable +]</i>
如果您不希望将代码的结果插入到文档中该怎么办?您可以像这样用空字符串结束每组 Perl 表达式:
[+ $counter++; &get_user_info($id); "" +]
这会将空字符串插入到文档中,因为它最后的元素被评估。但更好的解决方案是方括号减号([- 和 -]),它可以自动为您完成此操作。例如:
<HTML>
<Head><Title>Current time</Title></Head>
<Body>
<P>This is Embperl</P>
<!-- Square-plus brackets -->
<P>[+ localtime(time) +]</P>
<!-- Square-minus brackets -->
<P>[- localtime(time) -]</P>
</Body>
</HTML>
上述 Embperl 的输出看起来与没有方括号减号的输出相同,因为方括号减号中执行的操作的输出不会插入到 HTML 中。这在进行变量赋值以及导入其他 Perl 模块时非常有用。例如:
<HTML>
<Head><Title>Print user information</Title></Head>
[- $user = $ENV{"REMOTE_USER"}; -]
<Body>
<P>This is Embperl</P>
<P>[+ &print_user_profile($user) +]</P>
</Body>
</HTML>
保持变量的值

正如您所看到的,变量赋值在方括号之间保持不变,这意味着您可以在一个块中赋值变量,然后在稍后引用它。默认情况下,变量是全局的,但您可以使用 Perl 的“my”约定来创建临时变量,这些变量在块的末尾超出范围。

mod_perl 的一个优点是它只编译程序一次,并将其缓存以供将来调用。您不仅节省了派生新进程的开销,而且程序运行得更快,因为它只需要被解释。在许多情况下,您希望变量值在程序的多次调用中保持不变。这种持久性允许您只登录一次数据库服务器,并在许多 HTTP 请求期间保持连接打开。

这就引出了一个问题:在 Embperl 文档中定义的变量会发生什么——它们也会在调用之间保持它们的值,还是它们会消失?答案是,每个 Embperl 文档都在它自己的包中处理,并且默认情况下,在该包中定义的变量在每次调用时都会被重置。但是,在其他包中定义的变量会在调用之间保持不变。以下 Embperl 文档演示了这是如何工作的:

<HTML>
<Head><Title>Current time</Title></Head>
[- $counter++; -]
[- $remain::counter++; -]
<Body>
<P>This is Embperl</P>
<P>Counter: [+ $counter; +]</P>
<P>remain::counter: [+ $remain::counter; +]</P>
</Body>
</HTML>

如果您在系统上尝试这样做,您可能会发现 $counter 始终保持为 1,而 $remain::counter 每次调用都会递增。但是,如果您运行多个 Apache 副本,$remain::counter 可能会跳来跳去,好像有几个不同的副本正在递增。确实如此,因为每个 Apache 副本都在运行它自己的 mod_perl 和 Embperl 副本。如果您依赖于调用之间的持久变量,请记住,给定的用户可能会连接到多个 Apache 副本,您不能依赖于始终有相同的副本可供同一用户使用。

但是,当与其他计算机(而非用户的计算机)建立连接时,持久变量可能会很有用。特别是,DBI(Perl 数据库接口)可以与 Apache::DBI 模块一起利用这一点。该模块在第一次调用时打开与数据库服务器的连接,然后在 Apache 进程的整个生命周期中继续使用该连接,立即将每个查询发送到数据库服务器。因为持久性是在 Apache 和数据库服务器之间,所以无论用户每次连接到相同的 httpd 进程与否,它都可以工作。

在 Embperl 文档中定义子程序时,最好使用另一种方括号,使用感叹号作为特殊字符。方括号感叹号([! !])与方括号减号相同,不同之处在于,包含在其中的 Perl 代码仅在文档的第一次调用时执行。如果您在 mod_perl 下运行 Embperl,则在方括号感叹号中定义子程序意味着它们将被定义和编译一次,从而进一步提高程序的速度。

Embperl 元命令

最后,我们来到了方括号美元符号([$ $]),它允许您输入 Embperl 元命令。正如您可能从名称中想象的那样,这些元命令实际上是小型编程语言的一部分,您可以用它们来告诉 Embperl 该做什么。

元命令允许您使 HTML 和 Perl 的某些部分变为条件性的,或者对它们进行给定次数的循环。相同的任务可以在正常的 Embperl 块中执行,因为 Perl 是一种成熟的编程语言,可以很好地处理条件和循环。但是,通过使用 Embperl 元命令,您可以将更多的 HTML 放置在 Perl 块之外,使 Perl 块更小更容易阅读。

例如,假设我们运行一个需要注册的网站。假设我们有一个名为 &is_registered 的函数,它根据用户是否在我们系统中注册而返回“true”或“false”,我们可以使用以下代码打印适当的问候语:

[+ &registered($user_id) ? "You are known" : "You are
unknown" +]

一旦您开始处理与这些字符串相关的格式、您可能想要为新用户显示的菜单以及注册用户应该看到的个性化主页,方括号加号内的 Perl 代码块就会变得非常大。因此,使用方括号美元符号和 Embperl 元命令更容易:

[$ if &registered($user_id) $]
You are known, your registered home page:
        [+ &output_home_page($user_id) +]
 [$ else $]
 Welcome, new user! We would like to ask you a few
 questions:
 [+ &output_questionnaire +]
[$ endif $]
以上,我按照编程语言的风格进行了缩进,比一大块 Perl 代码更容易理解。网站上的非程序员也更容易理解和修改它,他们可以清楚地看到 HTML 和其他项之间的区别。
自动生成表格

Embperl 有许多功能,这里无法全部描述。我最喜欢的功能是它自动创建 HTML 表格的能力,并在必要时填充它们。Embperl 在填充 HTML 表格之前,会寻找以 <TABLE> 标签标记的 HTML 表格的开头。为了做到这一点,Embperl 希望您在表格中使用许多“魔法”变量。您可以使用 Embperl 的 $tabmode 设置确切的行为,但基本思想是,在表格中,$row(一个魔法变量)从 0 开始递增,直到达到 $maxrows(另一个魔法变量)。当表格中的表达式返回“未定义”时,Embperl 会退出表格循环并停止递增 $row。因此,我们可以使用以下代码获得格式良好的环境变量列表:

<HTML>
<Head><Title>Environment</Title></Head>
<Body>
[- @keys = sort keys %ENV -]
<Table border=2>
<tr>
[- $index = $row -]
<td>[+ $keys[$row] +] </td>
<td>[+ $ENV{$keys[$index]} +] </td>
</tr>
</Table>
</Body>
</HTML>

请注意,我们首先在表格定义之外定义了每个数组。然后,我们使用 $row(由 Embperl 自动递增)从 @keys 中检索每个元素。

使用魔法表格填充过程可能非常强大,但它需要您稍微改变您的编程风格。尽管如此,它在数据库应用程序中的潜在用途是巨大的,因为它大大减少了必要的编码量。

如果您查看环境变量列表,您可能会注意到 QUERY_STRING 未设置。在调用程序时,QUERY_STRING 通常通过在 URL 后面附加一个问号 (?) 和一个字符串来设置,但没有理由我们不能在 Embperl 文档中使用相同的语法,例如 http://localhost/embperl/env.html?foo

如果上述环境变量打印 Embperl 文件名为 env.html,那么使用 foo 参数调用它应该给 QUERY_STRING 赋予一个值。

事实上,我们甚至可以将 Embperl 文档用作 CGI 程序的“action”。从 %fdat 哈希中获取值,文档中的 Perl 块可以检索表单值,使用它们,甚至基于它们构建文档。

Embperl 确实需要一种与 Perl 中通常使用的略有不同的编程风格。通常,Perl 是用代码块编写的,每个代码块都返回一个值。Embperl 更加简洁,方括号对的出现频率远高于 Perl 的花括号。当然,Embperl 文档和上述示例中呈现的样式不必是您自己的;您可以将整个 Perl 程序放在方括号之间。

趋势似乎是使用模板和数据库来设计网站,越来越多的产品出现在市场上,声称可以做到这些事情。Linux、Apache、mod_perl 和 Embperl 的结合不仅提供了一种具有成本效益的解决方案,而且还提供了一组强大的编程工具,难以被击败。下个月,我们将更仔细地研究 Embperl,并学习如何将它与数据库一起使用,以轻松创建个性化的主页。

资源

Embperl: Modern Templates
Reuven M. Lerner 是一位居住在以色列海法的互联网和 Web 顾问,自 1993 年初以来一直在使用 Web。在他的业余时间,他做饭、阅读,并为他所在社区的教育项目做志愿者。您可以通过 reuven@netvision.net.il 与他联系。
加载 Disqus 评论