结合 Apache 和 Perl
CGI(通用网关接口)标准已经存在多年,并且开始显现其老态。CGI 很棒,因为所有 Web 服务器都支持它,程序员可以使用任何语言编写程序,并且程序可以在大量平台上移植。Netscape 的 NSAPI 和 Microsoft 的 ISAPI 与它们各自的 Web 服务器结合得更紧密,但是对使用这些 API 感兴趣的程序员比使用 CGI 受到的限制要多得多。
CGI 的一个特别大的问题是其效率低下。每次调用 CGI 程序都会在服务器上创建一个新进程。如果您用 Perl 编写 CGI 程序,那么每次 CGI 程序运行时,您都在启动 Perl 的新副本,从而占用额外的内存和处理器时间。如果我们能够拥有 CGI 程序的灵活性,而又不必使用所有这些系统资源,那岂不是很好吗?更好的是,如果我们能够在这样的框架中使用我们现有的 CGI 程序,而几乎不需要或根本不需要修改,那岂不是更好吗?答案当然是“是”;即使硬件继续变得更便宜、更强大,不必要地浪费内存和 CPU 时间似乎也很愚蠢。
本月,我们将关注 mod_perl——针对此问题提出的解决方案之一。mod_perl 是流行且强大的 Apache Web 服务器的一个模块,它可以在包括 Linux 在内的许多操作系统上运行。在最基本的层面上,mod_perl 使服务器端 Perl 程序的运行效率高于使用 CGI 协议时。然而,正如我们将看到的,mod_perl 提供的不仅仅是效率。它还提供了 Apache 内部结构的完整接口,让 Perl 程序员有机会修改 Web 服务器本身。
Apache 模块在编译时配置和安装。如果您有兴趣安装 mod_perl,您必须下载并重新编译 Apache 中的源代码。幸运的是,这相当容易做到。请注意,虽然任何人都可以下载、配置和编译 Apache,但只有具有 root 访问权限的人才能将 Apache 安装到其默认位置。如果您没有 root 访问权限,您仍然可以运行,但只能在不受限制的端口号上运行,即 1024 以上的端口号。
mod_perl 的最新版本始终可以从 CPAN(综合 Perl 存档网络)获得。目前,mod_perl 的最新版本是 1.10,这意味着您可以从 https://perldotcom.perl5.cn/CPAN/modules/by-module/Apache/mod_perl-1.10.tar.gz 获取它。以后的版本将具有相同的 URL,但版本号不同。此外,尽量使用离您较近的 CPAN 镜像,而不是给 www.perl.com 增加负担;请访问 https://perldotcom.perl5.cn/CPAN/ 以获得查找镜像的帮助。
下载 mod_perl 后,您还需要从 https://apache.ac.cn/ 或其镜像之一下载最新版本的 Apache,1.2.6。在同一目录下解压 Apache 和 mod_perl 发行版。在我的系统上,我执行了以下操作
cd /downloads tar -zxvf apache_1.2.6.tar.gz tar -zxvf mod_perl-1.10.tar.gz
如果您想修改默认的 Apache 模块集,现在是修改 /src/Configuration 的时候了。如果您不熟悉 Apache 配置,请不要担心——即使不自定义模块集,一切也会正常工作。
其余的 Apache 配置和编译都在 mod_perl 目录中完成。移动到 mod_perl 目录(可能名为 mod_perl-1.10 之类的名称)并键入
perl Makefile.PL
在我的系统上,mod_perl 问了我两个问题
Configure mod_perl with ../apache_1.2.6/src ? [y]我按了回车键,以及
Shall I build httpd in ../apache_1.2.6/src for you? [y]我再次按了回车键。这配置了构建 mod_perl 和 Apache 所需的所有文件。当 UNIX shell 提示符返回时,只需键入 make 并按回车键。生成的 Apache 二进制文件 (httpd) 将位于 Apache 目录下的 src 子目录中。在我的系统上,httpd 位于 /usr/sbin/httpd 中,因此复制生成的二进制文件将用新 Apache 替换旧 Apache。
以 root 身份登录并键入以下命令来重启 Apache
killall -1 -v httpd
现在,您可以使用新版本的 Apache 了。如果您不确定是否已安装新版本,请连接到 Web 服务器并询问其版本信息
telnet localhost 80连接后,键入
HEAD / HTTP/1.0在我的系统上,我得到以下响应
HTTP/1.1 200 OK Date: Sun, 12 Apr 1998 19:02:41 GMT Server: Apache/1.2.6 mod_perl/1.10 Connection: close Content-Type: text/html换句话说,在端口 80(HTTP 流量的默认端口)上运行的 Web 服务器正在运行 Apache 1.2.6,并编译了 mod_perl 1.10。
mod_perl 最流行的用途之一是作为 CGI 的快速替代品。为了以这种方式使用它,我们需要修改 Apache 的配置文件,以便它知道如何处理使用 mod_perl 的程序。
为什么 Apache 必须知道如何处理这些程序?考虑 CGI 程序应该可以清楚地说明这一点。浏览器请求 CGI 程序的方式与请求静态文档的方式完全相同。浏览器不知道给定的 URL 是指向程序还是静态文档;该决定由服务器做出。如果请求的是静态文档,服务器会将文档逐字返回给用户的浏览器。如果请求的是程序,服务器会执行该程序并将任何输出返回给用户的浏览器。
在这两种情况下,浏览器的行为都是相同的:它将请求发送到服务器并显示收到的任何响应的内容。这使得服务器有责任识别哪些文件要逐字传输,哪些文件是要将其输出作为响应发送的程序。Apache 允许我们选择允许 CGI 程序位于系统上的任何位置(只要它们以约定的后缀结尾,例如 .pl 或 .cgi),并要求它们位于一个或多个指定的目录中。这是使用 Apache 配置文件中的指令完成的。
现在我们已经将 mod_perl 添加到我们的服务器,我们必须告诉 Apache 如何处理三种类型的 URL:静态文档、CGI 程序和 mod_perl 程序。将 mod_perl 添加到组合中不必更改系统上的现有配置。我在我的 Web 根目录 (/home/httpd/perl-bin) 下创建了一个名为 perl-bin 的目录,并决定所有 mod_perl 程序都将驻留在那里,就像所有 CGI 程序都驻留在 cgi-bin 中一样。然后,我在服务器的 srm.conf 文件中添加了以下行
<Location /perl-bin> SetHandler perl-script PerlHandler Apache::Registry Options ExecCGI </Location>
<Location> 和 </Location> 标签表明我们希望我们的设置对特定目录而不是整个 Apache 服务器生效。然后,我们告诉 Apache 将 perl-bin 目录中的文档视为 Perl 脚本,而不是静态文档或其他内容。如果您好奇,Apache 手册有一个完整的章节描述处理程序,包括 AddHandler 和 SetHandler 指令,这些指令允许我们根据位置或文件扩展名配置文件类型。例如,其他处理程序包括 cgi-script(用于 CGI 程序)、server-info(用于服务器信息)和 imap-file(用于图像映射)。
现在 Apache 知道 /perl-bin 中的哪些文件应被视为 mod_perl 程序,我们必须告诉 mod_perl 如何处理这些 Perl 文档。我们将使用 Apache::Registry 模块,该模块允许我们运行 CGI 程序。最后,我们将使用 Options 指令来允许 CGI 程序在此目录中运行。
最后,我们对 srm.conf 进行最后一次修改,告诉 mod_perl 生成 HTTP 标头。我们在 <Location> 指令之外执行此操作,因为我们始终希望 mod_perl 返回完整的标头。要添加的行是
PerlSendHeader On
添加 PerlSendHeader 指令并不能免除我们指示我们正在返回的内容类型的责任。换句话说,我们仍然必须将“Content-type”标头添加到输出的顶部,就像我们在编写 CGI 程序时所做的那样。
现在所有部分都已就位,可以使用 mod_perl 代替 CGI 程序了。让我们尝试一个简单的程序,该程序打印出当前的环境状态。将以下内容复制到 perl-bin 目录中名为 test.pl 的文件中
use strict; print "Content-type: text/html\n\n"; foreach my $key (sort keys %ENV) { print "\"$key\" = \"$ENV{$key}\"<BR>\n"; }
设置权限,使该文件可执行,并要求您的浏览器检索 /perl-bin/test.pl。如果一切顺利,您将在浏览器中看到环境变量列表。
如果您一直在编写 CGI 程序(或使用 Perl 已经有一段时间了),那么以上内容可能看起来很奇怪。例如,指示 Perl 解释器位置及其开关的初始行在哪里?我们如此习惯的初始哈希-感叹号 (#!) 语法缺失了,因为它是不必要的。这两个字符的代码告诉 UNIX shell,它不应该尝试解释程序(即,作为 shell 脚本),而是应该将责任交给另一个程序。这就是为什么 Perl 程序通常以以下行开头的原因
#!/usr/bin/perl
而 Tcl 程序以以下行开头
#!/usr/bin/tclsh等等。因为我们的程序由 mod_perl 运行,并且 mod_perl 理解 Perl 程序,所以我们不需要程序顶部的哈希-感叹号语法。
命令行开关提出了一个更微妙的问题,这个问题触及了 mod_perl 相对于标准 CGI 程序的优势的核心。程序在 mod_perl 下运行得更快的原因有很多,但两个主要原因是 Perl 嵌入在 Apache 中(节省了每次调用都启动 Perl 的开销),并且程序编译一次,然后缓存(节省了每次调用都编译的开销)。将 Perl 嵌入 Apache 中并缓存编译后的程序的组合可能意味着执行速度的巨大提升,通常在 400% 到 2000% 之间。
这些速度提升是有权衡的,其中之一是命令行开关不再像预期的那样工作。开关在编译时处理,因此如果您期望开关在每次程序运行时都起作用,您将会失望。但是,并非一切都丢失了。有兴趣从 mod_perl 程序内部打开 Perl 的警告(-w 标志)和安全检查(-T 标志,用于污点检查)的程序员可以使用 srm.conf 文件中的指令来做到这一点。要打开警告,您只需添加以下行
PerlWarn On
这具有从您的程序内部打开警告的效果。与往常一样,警告消息将发送到 Apache 错误日志。
同样,您可以通过在 srm.conf 中添加 PerlTaintCheck 指令来激活 Perl 的安全检查(通常称为“污点检查”)
PerlTaintCheck On
当您在 Perl 中编写 CGI 程序(或任何其他程序,就此而言)时,通常最好包含 use strict 指令,正如我们在上面的示例中看到的那样。但是,在使用 mod_perl 编程时,use strict 非常重要。否则,变量定义可能会在您的程序退出后保留在内存中,从而为将来调用此程序或其他程序创建问题。
同样,不要使用 exit 函数过早退出程序。通常,从 CGI 程序内部调用 exit 将结束程序——如果它已经生成了所有输出,这不是一件坏事。如果您从 mod_perl 程序内部调用 exit,该程序会将 Perl 一起带走;并且由于 Perl 嵌入在 Apache 副本中,因此杀死 Perl 实际上也会杀死该特定的服务器进程。如果您绝对必须从程序内部调用 exit,请改用 Apache::exit;它将实现您想要的功能,而不会产生意外的副作用。
现在我们已经完成了 mod_perl 和使用 Apache::Registry 编写 CGI 风格程序的基本介绍,让我们看一下 mod_perl 下 CGI 程序的示例——一个简单的留言簿程序,它接收表单参数并将其内容附加到系统上的文件中。表单如 清单 1 所示。请注意,该表单看起来与我们过去见过的表单完全一样。唯一的区别是我们表单的 action,它位于 perl-bin 而不是通常的 cgi-bin 中。
该程序如 清单 2 所示。如果我们在此程序的第一行添加“哈希-感叹号”,它将在 CGI 或 mod_perl 环境下同样良好地运行。我们使用 CGI.pm 来检索有关提交的表单的信息。虽然这对于最新版本的 CGI.pm 来说效果很好,但早期版本与 Apache::Registry 不完全兼容。
清单 2 中的程序与其 CGI 对应程序之间的主要区别是速度。虽然我无法给出确切的数字,但我的主观测试表明,mod_perl 的响应几乎是瞬间的,而 CGI 版本则明显需要更长的时间——可能长达一秒钟。这看起来可能不多,但是缓存的 CGI 程序与已经运行的 Perl 版本的组合令人印象深刻,即使是对于快速编译的短程序也是如此。正如您所看到的,它不需要对您的原始程序进行太多更改。
到目前为止,我仅将 mod_perl 视为 CGI 的替代品。但是,mod_perl 远不止于此;它为您提供了 Apache 内核的 Perl 接口。如果您已正确配置服务器,则可以使用 Perl 程序修改 Apache 的每个方面。更好的是,一些有进取心的人已经花费时间编写了模块来做到这一点。例如,Apache::Status 模块允许您查看服务器上运行的 mod_perl 的当前状态。Apache::Status 随 mod_perl 一起提供,并且是使用此软件包可以完成的工作的一个很好的例子。
与 Apache::Registry 的情况一样,我们将不得不为特定目录设置处理程序。在这种情况下,该目录不必在磁盘上物理存在,因为 URL 是在文件打开之前解释的。您必须将以下行添加到您的 srm.conf 文件中才能获得 Perl 状态
<Location /perl-status> SetHandler perl-script PerlHandler Apache::Status </Location>
与 Apache::Registry 的情况一样,我们将 Apache 处理程序设置为 perl-script。由于我们希望 Apache::Status 处理 perl-status 目录,因此我们将其指向我们的 PerlHandler。
如果您将上述行放在服务器的 srm.conf 文件中并重启服务器,则任何从您的服务器请求 /perl-status 的人都将有权访问有关您的服务器的信息。如果您希望对此类信息保密,则必须使用访问控制,如下例所示
<Location /perl-status> SetHandler perl-script PerlHandler Apache::Status order deny,allow deny from all allow from 127. </Location>
这允许您从服务器计算机本身检索状态信息;尝试从另一台计算机检索 /perl-status 将会收到“未经授权的访问”消息。
我对 mod_perl 的速度和灵活性感到惊讶和印象深刻,我希望随着时间的推移越来越多地使用它。它可以运行大多数现有 CGI 程序而无需修改这一事实,对于我们这些已经拥有大量此类程序的人来说是一个巨大的福音。
mod_perl 当然不是万能药。它的速度是有代价的;即,对系统内存的更大需求。在 Apache 内部包含 Perl(一个已知的内存消耗大户)意味着服务器上的 httpd 进程将比其他情况更大。随着时间的推移,每个服务器进程都会增长,因为编译后的 Perl 程序会缓存在内存中。在您的系统上使用 mod_perl 之前,您应该计算一下 Apache 正在使用的内存量;这可能会影响您要在系统上运行的服务器进程数。
尽管如此,mod_perl 对于 Apache 和 Perl 来说都是一个巨大的进步,并且有望随着时间的推移变得更好。下个月,我们将研究 mod_perl 可以加速我们的数据库连接的一些方法,从而使 Apache 成为依赖关系数据库的动态站点的更好服务器。
