将 SQL 与 CGI 集成,第 1 部分

作者:Reuven M. Lerner

上个月,我们开始了探索将关系数据库集成到我们的 CGI 程序中。CGI 程序经常需要保存和检索信息。它们通常使用服务器文件系统上的文本文件来完成这项工作。然而,通过使用关系数据库,我们可以使我们的程序灵活、强大和健壮,同时减少我们必须编写的代码量。

与 Web 一样,关系数据库使用客户端-服务器模型,将世界划分为数据库客户端(发出请求)和服务器(响应这些请求)。请求通常用 SQL(结构化查询语言)编写,SQL 可以嵌入到程序中。

本月,我们将探讨一个简单的项目,该项目使用关系数据库来允许用户相互发送电子明信片。下个月,我们将花费更多时间在这个项目上,改进我们最初的数据库设计,并使程序更加有用。

创建明信片系统

在过去的几年里,“明信片”网站变得越来越普遍。这些网站通常在重大事件或节假日之前出现,允许人们向他们的朋友和家人发送电子明信片。

完成这项任务的一种方法是通过电子邮件发送明信片,可能作为 MIME 附件。这相对容易做到,并使系统相对简单。然而,这样的系统需要我们发送整个明信片,如果我们的网站有大量访问者,这个操作可能会占用大量的带宽。

此外,许多用户仍然需要为他们的互联网连接付费,并且可能不希望被发送未经请求的 100 千字节邮件消息,即使它包含美丽的图片和温暖的祝福。在垃圾邮件盛行的现代,这可能是一个特别的问题,人们互相发送大量的邮件,而不顾及接收者最终可能不得不支付电话和网络费用的事实。

再加上并非每个人都使用 MIME 兼容的邮件阅读器这一事实,很明显,至少在目前,发送大型、复杂的电子邮件消息不是交朋友的好方法。

因此,我们将使用不同的方法。明信片将存储在我们服务器上的数据库中,并将通过 CGI 程序访问。当创建明信片时,将向收件人发送一封简短的电子邮件消息,指示她可以从中检索明信片的 URL,并使用 Web 浏览器显示任何图形。

我们将编写两个 CGI 程序:一个用于创建明信片并通过电子邮件发送通知,另一个用于允许收件人检索明信片。

创建数据库

在我们编写程序之前,我们需要在关系数据库中创建一个表。为了演示的目的,我使用的是 MySQL 6.3,这是一个 SQL 数据库,在我的 Red Hat Linux 系统上运行良好。您可以在 http://www.tcx.se/ 获取有关 MySQL 的更多信息。

为了创建数据库表,我们需要确定我们希望存储关于每张明信片的哪些信息。为了做到这一点,我们需要知道我们希望明信片看起来是什么样子。

假设明信片是由我们网站的访问者动态构建的网页。当 CGI 程序接收到一个 URL,其中包含一个问号后面的唯一标识符时,就会创建一个特定的明信片,在 Web 术语中,这被称为“查询字符串”。因此,一张明信片将通过 URL http://www.oursitename.com/cgi-bin/show-postcard.pl?12345 获得,而另一张明信片将通过 URL http://www.oursitename.com/cgi-bin/show-postcard.pl?67890 获得。

CGI 程序 show-postcard.pl 获取查询字符串的值(在上面的例子中,是 12345 或 67890),并将其用作我们数据库表中的索引。该表包含来自发送者的简短消息,以及发送者选择的图片。

为了使这项工作正常进行,我们需要一个包含七列的表

  1. 明信片 ID

  2. 发送者的姓名

  3. 发送者的电子邮件地址

  4. 接收者的姓名

  5. 接收者的电子邮件地址

  6. 明信片的图形

  7. 明信片的文本

为了避免处理 MySQL 的安全系统,我们将所有的表信息都放在名为“test”的数据库中。我们可以通过在命令行输入以下内容来进入 MySQL 命令行界面

mysql test

在我的系统上输入该命令后,我收到了以下消息

Welcome to the mysql monitor. Commands ends with ; or \g.
Type 'help' for help.
mysql>
mysql> 提示符是 MySQL 向我发出信号的方式,表明它正在等待 SQL 命令。

就像 C 和其他具有类型变量的编程语言一样,SQL 表中的列必须具有与之关联的数据类型。我们将使用的主要数据类型是 mediumint(中等大小的整数)、varchar(可变长度的字符串)和 blob(接受大量数据的无类型存储元素)。

首先,我们将创建一个包含七列的表,每列对应于我们希望跟踪的数据,使用以下 SQL 查询

create table postcards (
        id_number mediumint not null primary key,
        sender_name varchar(60) not null,
        sender_email varchar(50) not null,
        recipient_name varchar(60) not null,
        recipient_email varchar(50) not null,
        graphic_name varchar(100) not null,
        postcard_text blob);

当我在 MySQL 提示符下输入此内容时,我得到以下输出

mysql>     create table postcards (
   ->   id_number mediumint not null primary key,
   ->   sender_name varchar(60) not null,
   ->   sender_email varchar(50) not null,
   ->   recipient_name varchar(60) not null,
   ->   recipient_email varchar(50) not null,
   ->   graphic_name varchar(100) not null,
   ->   postcard_text blob);
Query OK, 0 rows affected (0.02 sec)
我们的数据库现在包含一个名为“postcards”的表,其中包含适当的七列。请注意,除了 postcard_text 之外,每列都被定义为“not null”。这表明该字段必须包含一个值。通过以这种方式定义表,数据库服务器可以强制执行一些关于数据存储方式的约定,从而避免潜在的问题。

第一列 id_number 将用于标识特定的明信片。为了确保只有一张明信片具有此特定的 ID 号,我们使用关键字“primary key”创建 id_number 列,这是另一种说法,表示其值在所有行中必须是唯一的。通过将 id_number 设置为主键,我们可以通过请求所有具有特定 id_number 值的行来检索明信片——结果将是单行(具有匹配的 id_number 值)或没有行(表示不存在具有该 ID 号的明信片)。

第二到第六列的类型为 varchar,这仅仅意味着它们的长度根据需要而变化,最多为括号中指示的最大字符数。我选择这些数字有些随意;如果您怀疑电子邮件地址总是少于 50 个字符,那么您可能希望缩短相应的字段。同样,如果您怀疑用户的姓名可能超过 60 个字符,则应延长这些字段的长度。

第六列 graphic_name 包含要插入到明信片中的图形的路径名。

最后一列定义为类型“blob”,包含发送者希望发送给接收者的文本。由于此消息是可选的,因此我们已指示此列可以包含空值。MySQL 允许我们在 postcard_text 中输入一个值,但如果我们未能提供这样的值,则该列将只包含一个空值。

对于我们表的摘要,我们可以使用 MySQL describe 命令

mysql> describe postcards;

此命令的输出如表 1 所示。

表 1. describe 命令的输出

存储和检索明信片

现在我们已经创建了“postcards”表,让我们直接在 MySQL 提示符下插入一些虚拟数据。然后我们将编写程序 show-postcard.pl 来显示虚拟明信片。最后,我们将编写一个程序,允许用户通过 HTML 表单输入明信片。

我们可以使用 SQL insert 命令将数据插入到表中。假设我们希望插入一张包含以下信息的明信片

ID: 12345
Sender name: Reuven Lerner
Sender e-mail: reuven@netvision.net.il
Recipient name: Bill Clinton
Recipient e-mail: president@whitehouse.gov
Graphic: smile.gif
Text: Hey there, Mr. President!

要插入此信息,请使用以下 SQL 命令

insert into postcards
  (id_number, sender_name, sender_email,
   recipient_name, recipient_email, graphic_name,
   postcard_text)
values
  (12345, "Reuven Lerner",
   "reuven@netvision.net.il",
   "Bill Clinton",
   "president@whitehouse.gov",
   "smile.gif",
   "Hey there, Mr. President!");
在 MySQL 提示符下输入此命令会产生以下响应
Query OK, 1 rows affected (0.34 sec)
换句话说,已成功向表中添加了一个新行。要检索它,请使用 SQL 命令
select * from postcards where id_number = 12345;
它产生的结果如表 2 所示。

表 2. 新数据库条目

虽然它看起来很丑陋,但此输出实际上是有道理的。问题在于大多数 CRT 只有 80 列宽,而该表几乎是其两倍宽。(而这本杂志甚至更窄,使其更糟。)幸运的是,当我们的程序检索一行时,它不必担心格式化。

如果您只对某些列感兴趣,则可以指定整个行的子集,如下所示

select sender_name,graphic_name,postcard_text
 from postcards where id_number = 12345;

提交此查询会产生表 3 中所示的结果。输出行仅包含与我们的明信片对应的行,以及我们请求的列。作为额外的优势,它比我们之前检索的整行更易于阅读。

表 3. select 查询的输出

现在我们已经看到了从数据库检索信息所需的 SQL 查询类型,编写我们的 CGI 程序应该很容易了。您可以在 列表 1 中看到一个初步的尝试。

此版本的 show-postcard.pl 期望使用查询字符串中的单个参数(明信片的 ID 号)来调用,正如我们之前提到的。如果我们转到 URL /cgi-bin/show-postcard.pl?12345,我们应该看到一张寄给 Bill Clinton 的明信片,其中包含我们手动在 MySQL 提示符下插入的消息。

该程序以相对直接的方式工作。首先,它创建一个 CGI 实例,这是一个 Perl 对象(可从 CPAN https://perldotcom.perl5.cn/CPAN/ 获得),它使 CGI 编程更容易。在发送 MIME 标头指示程序将以 HTML 格式的文本发送其输出之后,我们使用 param 方法检索查询字符串的值,如下所示

my $id = $query->param("keywords");

如果查询字符串为空,我们打印出一条错误消息,并使用鲜为人知的 <isindex> 标签给用户另一次输入明信片 ID 号的机会。请注意,我们使用 == 运算符检查 $id 的数值,而不是简单地检查 $id 是否等于(使用 “eq”)空字符串。这可以防止有人使用包含数字以外字符的参数调用 show-postcard.pl 时出现问题。

假设查询字符串中确实到达了一个数字,我们连接到 test 数据库,然后在变量 $command 中构建我们的 SQL 命令。当然,我们可以简单地将命令字符串插入到对 $dbh->query 的调用中。但是,在单独的字符串中构建命令使其更容易理解。它还具有允许我们调试程序的额外好处。我们可以通过取消注释以下调试行来打印文字查询

# Uncomment for debugging
# print "<P>SQL command: \"$command\
        "</P>\n";

当取消注释上述行时,我们可以准确地看到传递给 MySQL 的内容,并更容易地找到程序的问题。

然后我们的程序使用 “query” 方法将请求发送到 MySQL。返回的值是指向数据库的句柄,它可以包含一个或多个与我们的查询匹配的行。由于我们请求所有与特定 ID 匹配的行,因此我们可以确定最多返回一行。这是因为我们将 id_number 设置为主键,这使其成为唯一的。

如果用户使用不对应于任何明信片的 ID 号调用 show-postcard.pl 会发生什么?如果我们忽略这个可能的错误,输入不存在的 ID 号的用户将看到明信片的轮廓,但没有实际内容。这对我们的用户来说不是很友好,他们希望知道他们何时犯了错误,并有机会纠正它。因此,在我们从返回的行中检索信息之前,我们确保确实返回了一行。如果没有,那么我们可以安全地假设这是因为用户提交的 ID 号与数据库中任何行的 id_number 列都不匹配。

我们的代码通过在语句句柄 ($sth) 上使用 “numrows” 方法来完成此检查——该对象允许我们读取查询结果。如果 numrows 等于 0,我们没有从服务器收到任何行,并且我们会向用户抱怨他输入的 ID 号与我们服务器上的任何 ID 号都不匹配。

显然,要显示图形,具有指定名称的图形必须存在于服务器上的 /tmp(或您在程序最后一部分中命名的任何目录)中。如果图形的名称拼写错误,或者如果它放置在服务器上的错误目录中,程序将无法显示它,因此请小心。(下个月,当我们查看图形的多个表的使用时,我们将更仔细地研究这个问题。)

最后,show-postcard.pl 将行转换为可以发送给用户的网页。实际上,由于 “明信片” 实际上是一个网页,您可以争辩说,虽然我已经讨论过这个项目,就好像它只对发送明信片有用一样,但您可以轻松地调整此策略,以便在您的系统上创建个性化主页,每个用户获得不同的页面。

创建明信片

现在我们可以毫无困难地检索明信片了,我们必须处理这个项目的最后一部分:允许用户使用 HTML 表单创建明信片。

基本思想如下:发送者在 HTML 表单中输入所有必要的信息。接收提交表单的 CGI 程序将数据保存到 “postcards” 表中,向接收者发送电子邮件,指示如何检索明信片,并感谢发送者使用我们的服务。

我们已经看到了如何使用 SQL 查询将数据插入到表中。我们现在要做的就是创建一个 CGI 程序,将表单的内容转换为这样的查询,以及一个 HTML 表单,将其数据提交给我们的程序。您可以在列表 2 中看到这样一个程序的示例 send-postcard.pl。

列表 2. 程序 send-postcard.pl

在许多方面,send-postcard.pl 的作用与 show-postcard.pl 相同。它从 HTML 表单中获取变量值,并将这些值插入到预先编写好的 SQL 查询中。然后将该查询发送到数据库服务器,数据库服务器会处理它——在这种情况下,是通过在数据库中插入一个新行。

正如您从列表中看到的那样,我们首先抓取每个 HTML 表单元素的内容。在此特定版本的程序中,我们不检查每个字段的长度。考虑到数据库已被指示接受具有特定最大长度的名称和地址,在生产版本中这样做无疑是一个好主意。

接下来,我们为明信片创建一个 ID 号

my $id_number = time & 0xFFFFF & $$;

为什么我们不采用一个简单的值,例如 time(自 1970 年 1 月 1 日以来的秒数)或 $$(当前进程 ID)?为什么我们对这些值执行按位 “与” 运算?因为 ID 号必须是唯一的;否则数据库将不接受新行。我们还希望避免连续的数字,以便用户无法轻易猜到这些数字。这远非随机,并且可以被有兴趣这样做的人猜到;但是,它总比没有好,并且使生活更有趣。

最后,我们在表中为此明信片创建条目,一点一点地构建 SQL 命令

my $command = "";
$command = "insert into postcards ";
$command .= "  (id_number, sender_name,
        sender_email, recipient_name, ";
$command .= "   recipient_email, graphic_name,
        postcard_text) ";
$command .= "values ";
$command .= "  ($id_number, \"$sender_name\",
\"$sender_email\", ";
$command .= "   \"$recipient_name\", \"$recipient_email\", ";
$command .= "   \"$graphic_name\", \"$postcard_text\") ";

请注意,我们必须用引号将除一个值之外的所有值都括起来。这是因为它们是字符值和 blob(与整数相反),因此在 SQL 查询中传递时必须用引号引起来。

一旦 SQL 查询返回,我们就知道明信片已插入到数据库中。当然,除非 $sth 未定义,在这种情况下,我们会以错误消息不优雅地终止。

# Make sure that $sth returned reasonably
die "Error with command \"$command\""
         unless (defined $sth);

最后,我们向接收者发送电子邮件,指示有一张明信片正在等待她,以及检索明信片的 URL。只要存储在数据库中的 ID 号与我们程序中的 $id_number 值匹配,我们就不会有任何问题。最后,我们感谢发送者使用我们的系统。

发送明信片

现在我们来到了使我们的用户能够互相发送明信片的部分:HTML 表单,信息从该表单提交到 send-postcard.pl 程序。

正如您可能期望的那样,此表单相对简单。它包含五个文本字段,每个字段对应于我们期望从用户那里获得的字段,以及一个文本区域,用户可以在其中输入任意文本。您可以在列表 3 中亲自查看 HTML 页面。

列表 3. 用于提交明信片的 HTML 表单

这个系统虽然有点粗糙,但确实演示了如何在您的网站上通过一些工作创建一个明信片系统。此外,通过利用 SQL 的强大功能和关系数据库的功能,我们创建了一个相对健壮的系统,而无需大量工作,也无需调试大量代码。

您可以轻松地向 postcard.html 添加另外几个 HTML 表单元素,使明信片的发送者可以设置特定明信片的背景颜色、文本样式和字体。可能性确实是无限的,尽管您应该避免使这样的 HTML 表单看起来像大型喷气式飞机的驾驶舱。

当然,这个项目还有许多遗留问题。其中一个问题与图形有关,我们上面简要地提到了。此外,如果 ID 号丢失会发生什么?目前,任何人都没有办法来到我们的网站并检索他们可能发送或接收的任何明信片。我们将在下个月处理这个问题,因为我们将继续查看并在我们的 CGI 程序中使用 SQL。

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

加载 Disqus 评论