自动更新页面

作者:Reuven M. Lerner

我的网络浏览器主页设置为 http://www.dilbert.com/,著名的搞笑漫画《呆伯特》的家。感谢互联网的神奇之处,我每天早上都能欣赏到《呆伯特》的悲喜剧幽默,就在我开始工作之前。

如果不是斯科特·亚当斯,《呆伯特》的创作者的创造性才能,《呆伯特》网站就不会那么有用或有趣。从技术角度来看,有趣的是漫画每天自动更新的方式。每天早上,最新的漫画会自动放在《呆伯特》主页上,让数百万粉丝有机会看到最新一期。

本月,我们将研究几种创建自动更新页面的方法,以便用户每天可以在同一个 URL 上发现新内容。我们将研究几种达到相同目的的不同方法,从 CGI 程序到 cron 作业,甚至会简要了解如何在发布新内容时使用数据库。

使用 CGI 指向

首先,假设我们的网站由七个不同的页面组成,一周中的每一天一个页面(例如,周日的 file-0.html 到周六的 file-6.html)。我们如何配置网站,以便请求 today.html(或 today.pl)的人将看到今天的 file?换句话说,周三的访问者在请求 today.html 时应该看到 file-3.html。这样的系统可能适用于学校食堂,那里的食物往往每周的每一天都相同。

也许最简单的解决方案是 CGI 程序,我们将其称为 today.pl。如果我们用 Perl 编写程序,我们可以使用 localtime 函数轻松确定星期几,该函数返回描述当前日期和时间的元素列表。使用该列表的第六个元素(指示当前的星期几),我们可以为当天创建正确的 URL。最后,我们可以使用 HTTP “Location” 标头将用户的浏览器重定向到正确的位置。

列表 1 显示了该程序的简单实现。对于任何编写过 CGI 程序的人来说,该程序应该看起来很熟悉。它启用了 Perl 的所有警告系统:-w 用于可选警告,-T 用于额外的安全性,strict 用于额外的编译时检查,以及 diagnostics 用于在发生故障时提供更完整的文档。

通过使用 CGI.pm,用于编写 CGI 程序的标准 Perl 模块,我们可以轻松访问服务器传递的任何输入,以及 CGI 程序可能使用的各种输出方法。大多数 CGI 程序使用旨在向用户浏览器返回 HTML 的输出方法,包括发送 MIME “Content-type” 标头,指示即将发送的内容类型——在我们的例子中,我们返回一个 “Location” 标头,这消除了对 “Content-type” 标头的需求。

如果上述程序安装在我们的服务器上的 /cgi-bin/today.pl 中,访问者将始终看到当天的相应文件。

上述程序虽然简单,但存在一些缺陷。最重要的是,CGI 速度慢且效率低下;使用它将用户的浏览器重定向到另一个文件会减慢用户的体验,并增加服务器的负载。每次调用 CGI 程序时,服务器都必须创建一个新进程。如果程序是用 Perl 编写的,这意味着必须启动 Perl 二进制文件,这可能需要一些时间。

一种解决方案可能是使用 mod_perl,它将 Perl 的完整工作版本插入到 Apache Web 服务器中。使用 mod_perl 意味着 Apache 不再需要创建新进程、执行 Perl 二进制文件或编译 Perl 程序,这将减少服务器资源的使用。但是,这仍然意味着每次用户请求主页时,服务器都必须执行一个程序。如果该页面在一天内被请求 1,000 次,则该程序将运行 1,000 次。这听起来可能不多,但想象一下,当您的网站越来越受欢迎,每天获得 1,000,000 次点击时会发生什么。

即使是这种解决方案也没有解决并非所有用户都运行处理重定向的浏览器这一事实。如果浏览器不处理通知,用户将无法看到当天的文件。这个问题越来越少见,但如果您希望网站拥有尽可能多的受众,请记住这一点。

使用 cron 自动复制页面

现在让我们研究一种策略,其中程序每天只运行一次,而不管有多少人要求查看今天的页面。这种方法减少了服务器的负载,并允许使用旧浏览器的用户毫无问题地访问我们的网站。最简单的策略是使用 Linux 的 cron 实用程序,它允许我们在任何时间自动运行程序。使用 cron,我们可以每天运行一次程序,将相应的文件复制到 today.html。在星期日,file-0.html 将被复制到 today.html,而在星期四,file-4.html 将被复制到 today.html。

列表 2 是这样一个程序的示例。如果该程序每天运行一次,那么 today.html 将始终包含当天的相应文件。此外,服务器将能够响应文档请求,而无需创建新的 CGI 进程或使用 Perl。

上面的程序 不是 通过 CGI 运行的,而是通过 cron 运行的。为了通过 cron 运行程序,您必须在您的 crontab 中添加一个条目,这是一个特殊格式的文本文件,用于描述程序应何时运行。每个用户都有一个单独的 crontab 文件;也就是说,每个用户都可以安排在不同的日期和时间运行不同的 cron 作业。

您可以使用 crontab 程序编辑 crontab 文件,该程序通常位于 /usr/bin/crontab 中。要修改您的 crontab 文件,请使用 crontab -e,这将调出在 EDITOR 环境变量中定义的编辑器。crontab 的格式过于复杂,我无法在此处解释;在 Linux 命令行中键入 man 5 crontab 将调出描述该格式的手册页。(仅键入 man crontab 将调出 crontab 程序的描述,而不是 crontab 文件格式,这对于新用户来说可能会感到困惑。)

假设我们希望在午夜后一分钟运行上述程序(我将其称为 cron-today.pl),我们可以将以下条目添加到我们的 crontab 中

1 0 * * * /usr/local/bin/cron-today.pl

换句话说,我们希望在午夜后一分钟 (1 0) 运行 /usr/local/bin/cron-today.pl,每月 (*) 每天,每月 (*),以及每周 (*) 每天。

来自每个 cron 的输出都通过电子邮件发送给拥有该作业的用户。在我的 crontab 中安装上述行后,我每天大约在凌晨 12:01 收到来自 cron 作业的电子邮件。而且每天,任何访问我们网站的人都会看到 today.html 的正确文件。

使用符号链接

上述基于 cron 的技术可行,但有一些烦人的副作用。例如,如果您决定在星期二早上更改星期二的菜单会发生什么?更改将不会反映到下个星期二,因为 today.html 包含 12:01 a.m. 从 file-2.html 中获取的内容,当时拍摄了快照。

为了解决这个问题,并减少程序两个副本使用的磁盘空间,我们可以使用符号链接。这些链接看起来像文件,但实际上是指向文件的指针,类似于 Macintosh “别名”或 Windows “快捷方式”。如果我们从 today.html 创建一个指向 file-0.html 的符号链接,那么这两个文件名在大多数情况下是等效的。(Linux 下也提供其他“硬”链接,但限制更多。)

如果我们想创建一个名为 today.html 的符号链接,指向 file-0.html,我们可以说

ln -s file-0.html today.html

如果您想更改链接,使其指向 file-1.html,请删除旧链接并创建一个新链接,如下所示

rm -fv today.html
ln -s file-1.html today.html
或者,我们可以使用 ln-f(“强制”)选项,即使链接以前链接到其他位置,也强制执行链接分配
ln -sf file-0.html today.html
如果我们每天都这样做,删除旧链接并创建一个新链接,那么我们所做的实际上与 cron-today.pl 中的操作相同,但增加了等同于两个文件的优势。此外,我们将通过指向原始文件而不是复制它来节省文件系统上的空间。

列表 3 包含一个简短的 Perl 程序,旨在通过 cron 运行,它创建了这样一个链接。通过 “print” 语句发送到标准输出 (STDOUT) 的任何内容都会发送给 cron 作业的所有者。该程序假定 cron 作业的所有者(程序在其用户 ID 下运行)有权删除现有文件,并在目录中创建新的符号链接。可以创建指向任何文件的符号链接,包括不存在的文件;只有当您尝试访问该文件时,才会检查权限。

发布每日项目

到目前为止,我们研究的技术在每周或每月出现相同项目时最有用。但是,在许多情况下,在 Web 上发布内容涉及每天创建一个新文件并使其可用。首先,我们将研究如何每天创建一个新文件(格式为 file-1.html,如前所述),以便通过查看 today.html 可以获得最新的文件。

同样,我们可以使用 CGI 程序或 cron 作业来完成此操作,您可以在列表 4列表 5 中分别看到示例。这两个程序都使用相同的基本算法来查找格式为 file-n.html 的编号最高的文件,其中 n 是文件的顺序号。

这两个程序的关键在于以下几行

if (opendir(DIR, $directory))
{
@files = sort by_number
   grep {/^file-[0-9]+\.html$/} readdir(DIR);
closedir DIR;
}

首先,我们打开 $directory,文件所在的目录。(如果程序无法打开目录,它会记录错误。)然后,我们读取目录 DIR 的内容,使用 Perl 的 grep 函数过滤掉任何不符合 file-n.html 模式的文件。最后,我们使用我们自己的 by_number 例程对这些文件进行排序,该例程比较顺序号而不是完整的文件名。

获得文件列表后,我们挑出 @files 的最后一个元素,它具有最高的顺序号。然后,我们可以使用 CGI.pm 的 redirect 方法将用户的浏览器重定向到该文件。

如果我们想每天发布项目,我们应该尝试比此系统更好的系统,该系统依赖于顺序号。首先,处理文件名更容易,文件名中提到了主题(例如,menu.html)或日期(例如,file-1998-06-01),而不是像 file-3023.html 这样的顺序编号。

其次,按日期排列文章为用户提供了一种自然的未来导航存档方式,而无需依赖网站的导航方案。此外,根据日期而不是顺序号创建文件名减少了出错的可能性。

如果您选择在文件名中使用日期,如 file-1998-06-01 中所示,请尝试将日期元素保持在年-月-日的顺序,以便按字母数字排序文件名也将按时间顺序对其进行排序。然后,我们可以编写一个小程序,根据日期选择当天的文件,并在每天使用 cron 运行它。列表 6 中显示了一个示例。程序逻辑非常简单,从我们对 localtime 的调用中获取日期信息,并将这些元素拼凑在一起以创建文件名。

但是,如果今天的文件不存在,可能会出现问题。正如我之前提到的,符号链接不必指向文件;它们可以指向任何有效的文件名,即使该名称的文件不存在也是如此。但是,如果符号链接指向不存在的文件,用户在从我们的网站加载 today.html 时会看到可怕的“404--找不到文件”错误。该程序的更复杂版本将检查网站上是否存在与今天日期对应的文件。然后,这样的程序将按时间顺序向后(或向前,如果您愿意)搜索,以找到 today.html 符号链接的最佳匹配项。它甚至可以向网站管理员发送电子邮件,指示存在这样的问题。

使用数据库

在互联网上定期发布材料的另一种方法是使用数据库。我们可以创建一个表格,在文件名和日期之间建立对应关系,而不是依赖于以特定日期为键的文件名。然后,我们可以编写一个 CGI 程序来检索当前文件,或者编写一个旨在通过 cron 运行的程序来创建指向当前文件的符号链接。

另一种选择是将文件存储在数据库中。但是,如果我们这样做,我们还必须使网站的编辑和设计人员能够存储、检索和编辑数据库内部的信息。为了我们的目的,我们将假设文件存在于服务器的文件系统上,并且我们正在尝试指向它们,而不是以不同的方式存储它们的内容。这些示例在 Red Hat 5.1、Perl 5.004_04、Perl 的数据库接口 (DBI) 库和 MySQL(一个主要免费的关系数据库系统,可从 https://mysqlserver.cn/ 获得)下进行了测试。

在我们可以做任何其他事情之前,我们必须创建一个表来保存信息。该表将相对简单,仅包含文件名和日期。我们将假设每篇文章只能在一个日期发布,但每个日期可以包含多篇文章,这使得我们的表创建命令如下所示

CREATE TABLE Articles
 (filename  VARCHAR(100) NOT NULL PRIMARY KEY,
 date    DATE NOT NULL);

在上面,我们将 filename 定义为 100 个字符的文本字段,该字段必须填写(NOT NULL)并且不能与任何其他文件名相同(PRIMARY KEY)。如果我们尝试在两个不同的日期插入相同的文件名,数据库将阻止我们。相比之下,因为我们希望允许在给定日期有多个文件,所以 date 字段(类型为 DATE)定义为 NOT NULL,这意味着我们必须为每个文件名指示一个日期。

为了向我们的数据库添加文件,我们可以使用以下 SQL 命令

INSERT INTO Articles (filename, date)
VALUES ("foobar.html", "1998-06-05");

如果您使用的是 MySQL,则必须在日期周围加上引号,否则将插入默认日期 0000-00-00

除了提交上述查询后收到的确认消息(1 row affected)外,我们还可以检查表的内容

mysql> SELECT * FROM Articles;
+-------------+------------+
| filename    | date       |
+-------------+------------+
| foobar.html | 1998-06-05 |
+-------------+------------+
1 row in set (0.08 sec)

使用原始 SQL 将信息输入数据库效率低下、容易出错,并且对不熟悉或不习惯 SQL 的用户没有帮助。列表 7 包含一个 HTML 表单,可用于使用 列表 8 中的程序将新文章输入数据库。

最后,我们将需要一个 today.pl 版本来检索当天的文件。列表 9 中是一个 CGI 版本的程序;重写它以使其使用 cron 应该相当简单。该程序的更复杂版本甚至会检查指定的文件是否存在,并向后搜索。

在 Web 上定期发布文章远没有出版日报或周报那么复杂,但仍然需要一些计划和编程。此外,无论您选择哪种方法,您仍然必须在性能和灵活性之间做出一些权衡。尽管如此,创建一个每天都在变化的页面并提供对网站存档的访问并不特别困难,并且可以提供足够的多样性来吸引人们。

本文中提及的所有列表均可通过匿名下载文件 ftp://ftp.linuxjournal.com/pub/lj/listings/issue53/3060.tgz 获取。

Updating Pages Automatically
Reuven M. Lerner (reuven@netvision.net.il) 是一位居住在以色列海法的互联网和 Web 顾问,自 1993 年初开始使用 Web。业余时间,他喜欢烹饪、阅读,并在社区的教育项目中做志愿者。
加载 Disqus 评论