FastCGI:用于您的 Web 服务器的持久应用程序
通用网关接口 (Common Gateway Interface) 几乎与 Web 服务器本身一样古老。NCSA 将 CGI 1.0 规范添加到所有 Web 服务器的鼻祖 httpd 的 1.0 版本中。CGI 1.1 是当前的规范,被添加到 1.2 版本中。此后开发的每个流行的 Web 服务器软件包都将 CGI 纳入其中,作为一种方式——通常是唯一的方式——让通过 Web 访问的访客访问基于服务器的可执行文件。
CGI 非常适合小型或不常用的程序,它们唯一的职能是响应一次性请求,例如处理来自 HTML 表单的简单信息。用每小时只调用几次的小型应用程序来阻塞您的内存或进程表是没有意义的。
然而,对于复杂或常用的程序,情况则恰恰相反。如果您的站点依赖于一个具有较长初始化过程的脚本,您的 Web 服务器可能会慢如蜗牛;特别是那些涉及连接到数据库或从大型文本文件中读取和构建信息的脚本。对于也处理邮件、FTP 或 DNS 请求的小型站点来说,速度问题甚至更加关键。
CGI 应用程序必须在每次调用时重新启动,这种限制导致了两个问题。首先,硬件和操作系统必须处理为每个 CGI 请求创建进程的开销。其次,CGI 脚本无法处理持久变量或数据结构;它们必须在每次调用时重建。
Open Market 的 FastCGI 接口是克服 CGI 限制的一种方法。FastCGI 应用程序通过 URL 调用,就像它们的 CGI 同行一样。不同之处在于它们是持久的;它们的功能类似于服务器内的服务器。FastCGI 在三个方面提供了优势:
速度:FastCGI 脚本只经历一个进程创建周期。数据和数据库连接的初始化只进行一次。FastCGI 的另一个好处是它可以连接到远程机器上运行的进程,从而减轻主服务器的处理负担。
持久性:即使您无法访问 SQL 服务器,FastCGI 也能通过将您的复杂方法、对象和变量存储在 RAM 中来实现许多类似数据库的功能。数据可以跨会话存储,为 HTTP 连接的无状态性提供了一种解决方法。
进程管理:Apache 对 FastCGI 的实现使服务器守护进程能够管理 FastCGI 应用程序,并在它们死掉时自动重启它们。其他服务器可能也具有这种能力;我的经验仅限于 Apache。
FastCGI 不是第一个——我确信也不会是最后一个——超越 CGI 的方法。大多数 Web 服务器都有 API,允许开发人员将新功能直接写入服务器。Doug MacEachern 的 mod_perl 模块允许将 Perl 运行时库直接编译到 Apache 中,使黑客能够完全用 Perl 编写服务器模块。
出于以下四个原因,我更喜欢 FastCGI 而不是其替代方案:
它不是特定于语言的。mod_perl 和专有 API 都规定开发人员必须使用某种编程语言。FastCGI 应用程序目前可以用 Perl、C/C++、Java 或 Python 编写,并且该标准足够灵活,将来可以添加其他语言。
它不是特定于服务器的。实际上,FastCGI 的实现是特定于服务器的,但该标准不与某个软件包绑定。FastCGI 目前在 Apache、Roxen、Stronghold 和 Zeus 上受支持;Fast Engines (http://www.fastserv.com/) 为 Netscape 和 Microsoft 服务器提供商业变体。
FastCGI 应用程序不在服务器的命名空间中运行。如果 FastCGI 应用程序崩溃,它不会使服务器崩溃。此外,由于 FastCGI 脚本作为单独的进程运行,它们不会增加服务器可执行文件的大小。
它是可扩展的。FastCGI 脚本可以配置为通过 TCP/IP 连接远程运行,从而提供一种负载分担的方法。
我的服务器平台是一个相当标准的 Linux 机器:一台 120MHz Pentium,配备 64MB RAM,运行 2.0.27 内核。如果您已经从您的硬件获得了不错的性能,那么您使用 FastCGI 也不会有任何问题。
至于软件,我使用 Apache 和 Perl;以下内容明显偏向于它们。如果您想用 C/C++、tcl、Java 或 Python 进行编码,或者如果您想使用不同的服务器软件,我建议您访问 http://fastcgi.idle.com/ 以获取更多信息。另一方面,我将提供的大多数编码提示都适用于任何语言。
要使用 Perl 和 Apache,您需要进行一些重新编译。Apache 需要使用 FastCGI 模块重建。您还需要编译一个 Perl 模块。几个月前,您需要重建 Perl——如果需要或想要这样做,我将提供说明——但现在您可能可以使用当前的 Perl 构建。即使您不是一位有成就的 C 程序员,编译过程也相当轻松。以下是您需要的:
最近版本的 C 编译器:我使用 gcc 2.7.2.1,没有任何问题。
Apache 源代码:我使用 Apache 1.3.0(1.3.1 是当前版本)。Apache 随附在大多数 Linux 发行版中,或者您可以从 https://apache.ac.cn/ 下载它。
Perl 5.004 或 5.005 源代码:我强烈建议您升级,如果您使用的是旧版本。如果还有其他原因,这也是一个很好的机会,可以看看 https://perldotcom.perl5.cn/ 上新的和改进的 Perl 主页。
mod_fastcgi 源代码:当前版本(截至 1998 年 9 月 3 日)是 2.0.17。它与 Apache 1.3.1 兼容,可在 http://fastcgi.idle.com/fastcgi.tar.gz 获取。
文档和示例脚本:这些都包含在 FastCGI 开发人员工具包中,其链接在 http://fastcgi.idle.com/。
Sven Verdoolaege 的 FCGI.pm (https://perldotcom.perl5.cn/CPAN/modules/by-module/FCGI/) 处理 Perl 到 FastCGI 的交互;这是我的示例所基于的模块。或者,您可以使用标准 Perl 发行版中包含的 Leonard Stein 的 CGI::Fast 模块(在这种情况下,您需要稍微调整我的示例代码)。
您可能需要 AT&T 免费发布的 Safe/Fast I/O (sfio) 库,可从 http://www.research.att.com/sw/tools/sfio/ 获取。直到去年 6 月,Perl 需要使用 sfio 重建才能处理 FastCGI I/O 流。新版本的 FCGI.pm 在没有 sfio 的情况下也能工作(至少我没有遇到任何问题),但 fastcgi-developers 邮件列表的一些帖子表明,新模块可能仍然存在一些问题。我的建议是,在尝试使用 sfio 构建 Perl 之前,先在标准的 Perl 构建上尝试 FastCGI。
一旦您收集了必要的源代码,您就可以花一些高质量的 make 时间了。编译分为两个部分:Perl 和 Apache。两者都可以先完成,但在每个部分中,步骤都必须按特定顺序完成。
除非您知道需要重新编译 Perl 二进制文件,否则您可以跳到“编译 FCGI.pm”部分。但是,如果您确实需要重新编译 Perl,了解一些事项会很有帮助。
要开始 Perl 编译过程,请解压缩并构建 sfio;README 文件会告诉您如何操作。您还需要更新您的 $PATH 以包含新创建的子目录之一;这有点不寻常,但却是必需的。
其次,构建、测试和安装 Perl。完成 Larry Wall 的 Configure 脚本可能需要一段时间,但有一些项目您不应选择默认答案:
对于问题“用于库搜索的目录”,回答 $* $sfio/lib(其中 $sfio 是您解压缩 sfio 的目录)。下一个问题“任何其他库?”的默认答案现在应包括 -lsfio。
对于问题“任何其他 cc 标志?”,回答 $* -I$sfio/include。
对于问题“任何其他 ld 标志(不包括库)?”,回答 $* -L$sfio/lib。
对于问题“使用实验性的 PerlIO 抽象层?”,回答 是。
对于问题“perl5 可以使用 sfio 库,但它是实验性的。您似乎有 sfio 可用,您想尝试使用它吗?”,回答 是。
我从未遇到过以这种方式重新编译 Perl 的问题,但 fastcgi-developers 邮件列表存档 (可从 http://www.findmail.com/list/fastcgi-developers/) 中有很多来自遇到问题的人的消息。有关使用 sfio 重新编译 Perl 的一套相当完整的说明,可在 http://fastcgi.idle.com/fcgi2.0b2.1/doc/fcgi-perl.htm 找到。
无论您是否需要重新编译 Perl,您都需要按照源代码提供的说明解压缩、构建、测试和安装 FCGI.pm。如果 FCGI.pm 通过了 make test,您可以相当肯定您走对了路。
在处理 Apache 发行版之前,请解压缩 mod_fastcgi 源代码。阅读 INSTALL 文件,其中详细介绍了配置、编译和安装 Apache 的两种方法。第一种是 Apache Autoconf 样式接口 (APACI),是 1.3 版本的新功能。第二种是我们都熟悉和喜爱的久经考验的手动配置。然后解压缩 Apache 的源代码,并为 FastCGI 接口配置它:
将 mod_fastcgi 发行版目录复制或移动到 <apache_dir>/src/modules/fastcgi。
使用 APACI 或 mod_fastcgi INSTALL 文件中详细介绍的手动方法配置 Apache。我非常习惯处理 Configuration 文件,所以我通常采用旧方法。
如果您使用的是 Apache 1.2.x 或 1.3.x 并且没有耗尽 RAM,请尝试取消注释包含 mod_rewrite 的行。这是 Apache 的一个巨大扩展,允许它将传入的 URL 解析为正则表达式。有关这方面的推理,请参阅侧边栏 “以 Rewrite 方式实现健康和美观”。
运行 make。
Apache 从 $apache/conf 中的三个文件获取其所有运行时指令:access.conf、httpd.conf 和 srm.conf。按照 Ben 和 Peter Laurie 在 Apache: 权威指南 中给出的建议,我将所有指令放在 httpd.conf 中,而不使用其他两个文件。如果您使用所有三个文件,则配置更改将发生在 srm.conf 中。
在进行任何配置之前,您需要阅读 mod_fastcgi 源代码中包含的文档。docs/mod_fastcgi.html 文档有些过时,但对于入门仍然非常有用。没有列出作者,但我很乐意请他或她喝杯啤酒,因为他或她整理了一个真正优秀的资源,从而使我的工作变得简单得多。
还请允许我说,在更改 httpd.conf 之前,您应该对其有相当的了解。仔细查看源代码随附的文档,或者购买一本 Lauries 的书。
FastCGI 配置指令(请参阅侧边栏 “为 FastCGI 配置 Apache”)允许您完成两个基本任务。
首先,使用 AppClass 指令定义 FastCGI 应用程序的本地路径,和/或通过 ExternalAppClass 定义远程连接主机和端口号。AppClass 负责启动和管理本地运行的进程。
其次,将您的 FastCGI 应用程序与适当的处理程序或 MIME 类型关联,以便 mod_fastcgi 处理这些文件。根据位置 (SetHandler) 或文件扩展名 (AddHandler) 将处理程序“fastcgi-script”与一个或多个文件关联。或者,您可以将 MIME 类型
application/x-httpd-fcgi
根据位置 (ForceType) 或文件扩展名 (AddType) 与一个或多个文件关联。
请注意,您的 FastCGI 应用程序不能进入 ScriptAlias 指定的普通 CGI 目录。Apache 分配优先级的方式导致它尝试使用标准 CGI 模块处理 CGI 目录中的任何和所有文件,这不适用于 FastCGI 应用程序。
在许多方面,编写 FastCGI 脚本与传统的 CGI 编程并没有什么不同。如果您提供内容,则必须指定 Content-type(通常为“text/html”)。您可以使用 Location 和 Status 来指定重定向或其他 HTTP 消息。此外,您可以正常访问 %ENV 哈希。
在脚本中,可以访问 STDIN 和 STDOUT,但只能以标准方式访问。FastCGI 库对这些数据流进行了大量操作;您可以毫无问题地使用 print,但更高级的操作将会失败。例如,您不能将 STDOUT 的类型通配符(符号表条目)的引用(\*STDOUT)发送到 fork 进程。实际上,FastCGI 非常鄙视 fork,而且我还没有听说有人尝试在启用线程的 Perl 5.005 版本上运行它。
从结构上讲,CGI 和 FastCGI 脚本之间的主要区别在于,代码的主体放置在一个 while 循环中,这个循环希望永远不会结束。FastCGI 脚本的基本结构几乎相同,无论其任务如何:
初始化变量和与数据库、守护进程等的连接。
执行循环。
提供清理,以便您可以在需要时优雅地退出。
虽然 FastCGI 对您的代码几乎不会强制进行实质性更改,但它可能会改变您对什么是好的脚本的看法。我在开发 FastCGI 应用程序时学到的一些经验教训是:
考虑简洁。典型的 CGI 脚本不需要过度关注内存泄漏或草率的变量作用域。FastCGI 脚本由于是持久的,因此必须更严格地控制这些方面。
考虑大型。我们习惯于将 CGI 脚本视为快速的一次性脚本,它们应该定义完成工作所需的最少函数。使用 FastCGI,通常最好在一个脚本中包含大量功能;您可以更轻松地访问共享数据,并且进程表中杂乱的 PID 更少。我尝试使用主脚本(在 httpd.conf 中指定的脚本)作为分发中心,将所有实际工作外包给模块。这样做可以轻松地通过添加额外的一两行代码来扩展主脚本的功能;您的所有调整都可以在模块上完成。
考虑长期。您希望您的进程保持运行,因此明智的做法是不要让您的脚本 die() 或 croak()。捕获任何可能被证明是致命的语句(例如 open())的返回值,并依靠错误消息和流程控制来保持循环运行。
商业网站的网站管理员不愿承认这一点,但在线投放广告是这项工作中日益不可避免的事实。如果您有多个轮播赞助商,或者如果您的赞助商各自有多个广告,则无法将广告硬编码到存储在磁盘上的页面中。当然,对于任何可能以轮播方式呈现的信息来说,情况都是如此:新闻、当前特价或随机链接。
列表 1 中显示的 rotate.fcg 脚本提供了一种满足该需求的简陋方法。它提供了一个持久的广告信息数组,可以将其插入到您选择的任何基于磁盘的文档中的任何位置。它还允许更新广告数组,而无需重新启动脚本(尽管如果您运行脚本的多个实例,此技术将不起作用)。
基于 Apache 侧边栏中显示的 Apache 配置,调用脚本的 URL 是 http://www.yoursite.com/fastcgi-bin/rotate.fcg?page.html,其中“page.html”是您希望插入广告的文档的名称。page.html 可以包含一个或多个 HTML 注释实例,这些注释充当广告的占位符:
<!-- Ad Here -->
在这种情况下使用 HTML 注释意味着即使您还没有要放置的广告,文档也会正确显示。
脚本的开头部分限定了作用域并初始化了将在进程生命周期内使用的所有变量。本节中有三件事值得注意。首先,由于我们在循环外初始化 @ads,因此它将在脚本的生命周期内保持持久性。其次,我们需要自己初始化 %ENV 数组,以免稍后发现它是空的。第三,我们将 $| 设置为非零数字,因为我们希望每次调用脚本时都刷新 STDOUT。
在脚本进入主循环之前,它通过调用 initialize 例程来初始化广告数组。此例程读取 列表 2 中显示的文本文件类型。每个赞助商的数据都临时放入 %sponsor 哈希中,格式化为 HTML 并 push 到 @ads 数组中。如果无法打开文本文件,则例程返回一个空数组,允许脚本继续运行。
主要操作发生在标记为 REQUEST 的循环中。while 命令是脚本与 FCGI.pm 显式交互的唯一位置。这也是 FastCGI 脚本和传统脚本之间唯一的实质性区别。无论您使用哪种语言进行 FastCGI 编程,像这样的循环都将是您构建脚本主要过程的结构。
进入循环后,首要任务是允许网站管理员动态地重新初始化广告数组。在示例脚本中,这是通过向 http://www.yoursite.com/fastcgi-bin/rotate.pl?reload 发出请求来完成的。为了提供一点安全性,脚本仅允许从 Web 服务器重新初始化。如果您运行脚本的多个实例,则必须通过其他方式完成此操作:使用 kill -USR1 重新启动 Apache,重新加载数据文件(如果其时间戳已更改),等等。
如果您使用类似这样的脚本来运行当前的新闻标题,则可以通过将新更新添加到文本文件并重新初始化数组,每天多次将新更新发布到您的站点。
循环的第二个任务是确保可以打开请求的文件。如果无法打开,脚本会调用一个例程(未包含在我的示例中),该例程将发送“文件未找到”消息。通过提供自己的错误消息,脚本可以从错误的请求中优雅地恢复,而无需死掉。如果请求的文档可用,则将其分配给 @doc 数组。
接下来,从 @ads 数组的前面取出一个广告,分配给 $ad,然后推送到数组的后面。脚本保留了广告的副本,即使它已被放回数组中。
第四,脚本循环遍历文档,查找 <!-- Ad Here --> 的任何实例。当找到一个时,它会将 $ad 替换为它。如果包含广告的文本文件为空或无法打开,或者如果请求的页面没有放置广告的位置,则不会进行任何替换。
最后,脚本打印适当的 HTTP 标头,发送文档,然后返回到循环的前面,等待下一个请求。
我的示例脚本没有解决 FastCGI 擅长的许多任务:持久数据库连接、格式转换(例如,SGML 到 HTML)或提供通用的 HTML 页面标头和页脚。在我管理的站点上,我使用 FastCGI 来完成所有这些事情以及更多。
我发现 FastCGI 应用程序可以执行其职责,包括多个 SQL 查询,并动态交付页面,其速度仅比服务器交付静态文档的速度稍慢。在 10Mbps LAN 连接上,速度差异是可感知的,但只是勉强,而且只有在我寻找它时才感知得到。在 128Kbps 或更慢的连接上,我没有注意到任何差异。
我仍然使用 CGI 来执行简单、不常用的任务。CGI 脚本不会长时间占用系统资源。对于复杂、频繁调用的任务,FastCGI 提供了灵活性和速度的完美结合。
本文中提到的两个列表可通过匿名下载文件 ftp.linuxjournal.com/pub/lj/listings/issue55/2607.tgz 获取。
Paul Heinlein (heinlein@teleport.com) 与家人住在俄勒冈州波特兰附近,是 http://www.computerbits.com/ 的网站管理员。当他和女儿不玩基于 CD-ROM 的游戏时,Paul 会沉迷于他对路德宗神学和赞美诗的奇怪爱好。