CGI 脚本如何工作

文章《网页如何工作》讨论了 HTML 的基本特性,并向您展示了如何创建包含文本和图形的网页。它还向您展示了如何通过托管服务让您的页面“上线”。新的网站设计师一旦他们的网站上线,经常会问的一个问题是:“CGI 脚本是什么?我如何在我的网站上使用它?”或者,“我如何在我的网站上创建交互式表单?”

在本文中,我们将回答您关于 CGI 脚本的问题,并向您展示如何创建自己的脚本。在此过程中,您还将学到一些关于 Web 服务器的知识。让我们开始吧!

广告

Web 服务器

正如文章《Web 服务器如何工作》中所述,Web 服务器可以非常简单。它们最基本的功能是简单地从磁盘检索文件,并通过网络将其发送到请求的浏览器。假设您输入 URL http://www.bygpub.com/books/tg2rw/author.htm。服务器会收到对文件 /books/tg2rw/author.htm 的请求。如果您查看下图,您可以看到服务器如何解析该请求

在设置过程中,Web 服务器已被指示理解 c:\My Documents\www 是服务器的根目录。然后它在该根目录下查找 /books/tg2rw/author.htm。当您请求 URL http://www.bygpub.com/books/tg2rw/ 时,服务器理解您正在寻找该目录的默认文件。它会查找几个不同的文件名以尝试找到默认文件:index.html、index.htm、default.html、default.htm。根据服务器的不同,它可能还会查找其他文件。因此,服务器将 http://www.bygpub.com/books/tg2rw/ 转换为 http://www.bygpub.com/books/tg2rw/index.htm 并提供该文件。所有其他文件都必须通过明确命名文件来指定。

广告

这就是所有 Web 服务器处理静态文件的方式。大多数 Web 服务器也通过一种称为通用网关接口(Common Gateway Interface,简称 CGI)的机制来处理动态文件。您在网络上各种地方都见过 CGI,尽管当时您可能并不知道。例如

  • 任何留言本都允许您在 HTML 表单中输入消息,然后,下次查看留言本时,页面将包含您的新条目。
  • Network Solutions 的 WHOIS 表单允许您在表单上输入域名,返回的页面会根据输入的域名而不同。
  • 任何搜索引擎都允许您在 HTML 表单上输入关键字,然后它会根据您输入的关键字动态创建页面。

所有这些动态页面都使用 CGI。

CGI 机制

在大多数 Web 服务器上,CGI 机制已按以下方式标准化。在服务器视为根目录的常规目录树中,您创建一个名为 cgi-bin 的子目录。(您可以在前一页的图中看到此目录。)服务器随后理解,从特殊 cgi-bin 目录请求的任何文件都不应简单地读取和发送,而应执行。执行程序的输出才是实际发送给请求页面的浏览器的内容。可执行文件通常要么是纯粹的可执行文件,例如 C 编译器的输出,要么是 PERL 脚本。PERL 是一种非常流行的 CGI 脚本语言。

想象一下,您在浏览器中输入以下 URL:https://www.十万个为什么.com/cgi-bin/search.pl。服务器识别到 search.pl 位于 cgi-bin 目录中,因此它执行 search.pl(这是一个 PERL 脚本)并将执行结果发送到您的浏览器。

广告

您可以自己编写脚本并尝试 CGI,前提是

  • 您了解 CPERL 等编程语言。
  • 您可以访问处理 CGI 脚本的 Web 服务器。如果您已经付费给 Web 托管服务来托管您的网站,那么您很有可能通过您的主机访问 CGI 脚本。请咨询托管服务以获取详细信息。如果不能,那么您可以通过在您的家用机器上安装 Web 服务器并学习使用它来进行实验。第二个选项有点复杂,但您保证会在此过程中学到很多!

简单的 CGI 脚本

假设您有权访问 cgi-bin 目录(请参阅上一节),并且假设您了解 C 编程语言PERL,您可以对 CGI 进行大量有趣的实验以初试牛刀。让我们从创建最简单的 CGI 脚本开始。

在文章《网页如何工作》中,我们研究了最简单的 HTML 网页。它看起来像这样

广告

<html>
  <body>
     <h1>Hello there!</h1>
  </body>
</html>

最简单的 CGI 脚本,执行后,会生成这个简单的静态页面作为其输出。如果您用 C 语言编写,这个 CGI 程序会是这样

#include <stdio.h>

int main()
{
  printf("Content-type: text/html\n\n");
  printf("<html>\n");
  printf("<body>\n");
  printf("<h1>Hello there!</h1>\n");
  printf("</body>\n");
  printf("</html>\n");
  return 0;
}

在我的 Web 服务器上,我将此程序输入到文件 simplest.c 中,然后通过以下命令对其进行编译

gcc simplest.c -o simplest.cgi

(有关编译 C 程序的详细信息,请参阅《C 编程如何工作》。)

通过将 simplest.cgi 放置在 cgi-bin 目录中,它可以被执行。正如您所看到的,该脚本所做的只是生成一个显示“Hello there!”的页面。唯一出乎意料的部分是那一行

printf("Content-type: text/html\n\n");

行“Content-type: text/html\n\n”是必须由任何 CGI 脚本首先发送到浏览器的特殊文本。只要您记住这样做,一切都会正常。如果您忘记了,浏览器将拒绝脚本的输出。

您可以在 PERL 中做同样的事情。将此 PERL 代码输入到名为 simplest.pl 的文件中

#! /usr/bin/perl
print "Content-type: text/html\n\n";
print "<html><body><h1>Hello World!";
print "</h1></body></html>\n";

将文件放入您的 cgi-bin 目录。在 UNIX 机器上,输入以下命令也可能会有帮助

chmod 755 simplest.pl

这会告诉 UNIX 脚本是可执行的。

您刚刚看到了 CGI 脚本的基本思想。它确实就是这么简单!程序执行,其输出被发送到调用该脚本的浏览器。发送到标准输出(stdout)的普通输出就是发送到浏览器的内容。

然而,CGI 脚本的全部意义在于创建动态内容——每次脚本执行时,输出都应该不同。毕竟,如果每次运行脚本时输出都相同,那么您还不如使用静态页面。以下 C 程序演示了非常简单的动态内容

#include <stdio.h>

int incrementcount()
{
  FILE *f;
  int i;

  f=fopen("count.txt", "r+");
  if (!f)
  {
     sleep(1);
     f=fopen("count.txt", "r+");
     if (!f)
       return -1;
  }

  fscanf(f, "%d", &i);
  i++;
  fseek(f,0,SEEK_SET);
  fprintf(f, "%d", i);
  fclose(f);
  return i;
}

int main()
{
  printf("Content-type: text/html\n\n");
  printf("<html>\n");
  printf("<body>\n");
  printf("<h1>The current count is: ")
  printf("%d</h1>\n", incrementcount());
  printf("</body>\n");
  printf("</html>\n");
  return 0;
}

使用文本编辑器,将此程序输入到名为 count.c 的文件中。通过输入以下命令对其进行编译

gcc count.c -o count.cgi

创建另一个名为 count.txt 的文本文件,并在其中放置一个零。通过将 counter.cgicount.txt 放置在 cgi-bin 目录中,您可以运行该脚本。该脚本所做的只是生成一个页面,显示“当前计数是:X”,其中 X 每次运行脚本时都会增加一次。尝试运行几次,看看页面内容的变化!

count.txt 文件保存当前计数,而小的 incrementcount() 函数是用于增加 count.txt 文件中计数的函数。此函数打开 count.txt 文件,从中读取数字,增加数字并将其写回文件。该函数实际上尝试打开文件两次。这样做是为了防止两个人同时尝试访问文件。这当然不是一个万无一失的技术,但对于如此简单的功能来说它是有效的。如果文件在第二次尝试时仍无法打开,则返回给调用者错误值 -1。更复杂的程序会识别 -1 返回值并生成适当的错误消息。

表单:发送输入

我们已经看到 CGI 脚本的创建相当容易。Web 服务器执行放置在 cgi-bin 目录中的任何可执行文件,并且可执行文件发送到标准输出(stdout)的任何输出都会出现在调用该脚本的浏览器中。现在我们需要一种将输入发送到脚本的方法。发送输入的通常方法是使用 HTML 表单

您在网络上随处可见表单。任何您能够输入内容的页面都是一个表单。您在搜索引擎、留言本、问卷等中都能看到它们。十万个为什么.com 的主页至少包含两个迷你表单,一个用于“您是如何来到这里的?”侧边栏,一个用于建议侧边栏(是的,一个 HTML 页面可以包含多个表单)。您在 HTML 页面上创建表单,并在表单的 HTML 标签中指定当用户单击表单上的提交按钮时要调用的 CGI 脚本的名称。用户输入到表单中的值会被打包并发送到脚本,脚本随后可以随意使用它们。

广告

您实际上一直在不断地看到这类事情,但可能并不知道它正在发生。例如,访问 http://www.lycos.com,在“搜索:”框中输入“test”字样,然后按下“Go Get It!”按钮。结果页面的 URL 将如下所示

http://www.lycos.com/cgi-bin/pursuit?matchmode=and
                    &cat=lycos&query=test&x=10&y=9

您可以看到 Lycos 主页是一个表单。Lycos 在 cgi-bin 目录中有一个名为 pursuit 的脚本。该表单向脚本发送五个参数

  1. matchmode=and
  2. cat=lycos
  3. query=test
  4. x=10
  5. y=9

第三个是我们输入的搜索字符串。其他四个对脚本也有意义。CGI 脚本查询 Lycos 数据库以查找“test”一词,然后返回结果。这就是任何搜索引擎的核心!

让我们创建一个简单的表单来尝试一下。创建一个名为 simpleform.htm 的文件,并将以下 HTML 代码输入其中

<html>
<body>
  <h1>A super-simple form<h1>
  <FORM METHOD=GET ACTION="https://www.十万个为什么.com/
cgi-bin/simpleform.cgi">
  Enter Your Name:
  <input name="Name" size=20 maxlength=50>
  <P>
  <INPUT TYPE=submit value="Submit">
  <INPUT TYPE=reset value="Reset">
  </FORM>
</body>
</html>

HTML 代码指定创建一个使用 GET 方法发送到 CGI 脚本 https://www.十万个为什么.com/cgi-bin/simpleform.cgi 的表单。表单内部是一个文本输入区域以及标准的提交(Submit)和重置(Reset)按钮。

表单引用的文件 https://www.十万个为什么.com/cgi-bin/simpleform.cgi 是一个 C 程序。它最初是放置在名为 simpleform.c 的文件中的这段 C 代码

#include <stdio.h>
#include <stdlib.h>

int main()
{
  printf("Content-type: text/html\n\n");
  printf("<html>\n");
  printf("<body>\n");
  printf("<h1>The value entered was: ")
  printf("%s</h1>\n", getenv("QUERY_STRING"));
  printf("</body>\n");
  printf("</html>\n");
  return 0;
}

它通过以下命令进行编译

gcc simpleform.c -o simpleform.cgi

然后它被放置在 cgi-bin 目录中。此程序只是获取表单发送的值并显示它。例如,您可能会看到以下内容

The value entered was: Name=John+Smith

Name 是表单中文本输入字段的标识符(表单上的每个输入字段都应具有唯一的标识符),而 John+Smith 是可能在表单中输入的典型姓名。请注意,“+”替换了空格字符。

从这个例子中,您可以看到设置表单并将数据从表单获取到 CGI 脚本的基本过程相当简单。以下是一些需要记住的细节

  • 表单上的每个输入字段都应具有唯一的标识符。
  • 表单需要使用 GET 或 POST 方法。GET 方法的优点是您可以在发送到脚本的 URL 中看到表单的值,这使得调试更容易。
  • 通过 GET 方法发送的字符数量有明确限制,因此对于大型表单更推荐使用 POST。
  • 通过 GET 方法传入的数据是通过查看 QUERY_STRING 环境变量(通常在 C 中使用 getenv 函数或在 PERL 中使用 $ENV 工具读取)接收的。通过 POST 方法传入的数据可以通过标准输入(STDIN)获得,在 C 中使用 gets,在 PERL 中使用 read
  • 传入的数据将把所有字段连接成一个字符串,并且许多字符将被替换,因此需要转换。例如,所有空格都将替换为加号。

QUERY_STRING 环境变量引出了通用环境变量的话题。您可以在 CGI 脚本中检查许多环境变量,包括

  • AUTH_TYPE
  • CONTENT_LENGTH
  • CONTENT_TYPE
  • GATEWAY_INTERFACE
  • HTTP_ACCEPT
  • HTTP_USER_AGENT
  • PATH_INFO
  • PATH_TRANSLATED
  • QUERY_STRING
  • REMOTE_ADDR
  • REMOTE_HOST
  • REMOTE_IDENT
  • REMOTE_USER
  • REQUEST_METHOD
  • SCRIPT_NAME
  • SERVER_NAME
  • SERVER_PORT
  • SERVER_PROTOCOL
  • SERVER_SOFTWARE

这些环境变量中包含了各种有趣的信息,包括输入字符串的长度(CONTENT_LENGTH)、使用的 METHOD(GET 或 POST——REQUEST_METHOD 让您判断是在 STDIN 还是 QUERY_STRING 中查找输入)、用户机器的 IP 地址(REMOTE_ADDR)等等。有关这些变量的完整说明,请参阅《CGI 环境变量》

创建真实表单

一个真实的表单将由各种输入区域组成,并且需要在脚本中编写一些代码来撤销字符映射并解析出单个字符串。让我们首先看看表单上的标准输入控件。它们包括

  • 单行文本输入
  • 多行文本输入
  • 选择列表
  • 复选框
  • 单选按钮
  • 用于提交或清除表单的专用按钮

您可以将这些控件与任何其他页面上的静态文本和图形结合使用。

广告

以下是演示不同控件标签用法的几个示例

单行编辑

“input”一词标识一个单行编辑区域。“name”字段为 CGI 脚本提供控件标识符,并且对于表单上的每个控件都应该是唯一的。“size”字段表示表单上输入区域的宽度(以字符为单位)。“Maxlength”限制输入区域中的最大字符数。“Value”设置初始值。

Enter Name: <input name="Name" size=30 maxlength=50
value="Sample">

通常,输入区域前面是一段静态文本,用于标识输入字段的用途。此处显示的是静态文本“输入姓名:”。

您可以添加值“type=int”将输入限制为整数值。默认情况下,类型为“text”,它接受任何字符。

多行编辑

多行编辑区域类似于输入区域。您为控件定义一个名称,并以行和列的形式定义其在表单上的大小。您在 </textarea> 标签之前放置的任何内容都将作为默认值出现在控件中。

<textarea name="Company Address" cols=30
rows=4></textarea>

复选框

复选框是输入区域的一种特殊形式,其类型设置为“checkbox”。

<input type=checkbox name="Include" value=1>

如果选中复选框,将返回该值。

单选按钮

单选按钮与复选框类似,但它们在视觉上是分组在一起的

Choose the search area:<br>
<input type=radio CHECKED name=universe value=US-STOCK>
Stocks
<input type=radio name=universe value=CA-STOCK>
Canadian Stocks
<input type=radio name=universe value=MMF>
Money Markets
<input type=radio name=universe value=MUTUAL>
Mutual Funds

请注意,默认单选按钮可以用 CHECKED 标记。另请注意,同一组中的所有单选按钮都具有相同的名称。

选择列表

选择列表为用户提供多个选项的选择。选择列表的标签允许您在“size”字段中指定可见行数,以及所有选项的值。

Select an Option<br>
<SELECT size=2 NAME="Option">
    <OPTION> Option 1
    <OPTION> Option 2
    <OPTION> Option 3
    <OPTION> Option 4
</SELECT>

MULTIPLE 单词创建多选功能。

专用按钮

以下标签创建两个专用按钮,一个用于将表单提交到服务器,一个用于重置表单

<INPUT TYPE=submit value="Submit">
<INPUT TYPE=reset value="Reset">

整合一切

假设您想为您的某个网页创建一个简单的问卷。例如,您想询问读者的姓名、性别、年龄和评论,然后在 CGI 脚本中处理它。HTML 表单可能位于名为 https://www.十万个为什么.com/survey.htm 的文件中,并看起来像这样

<html>
  <body>
    <h1>HSW Survey Form<h1>
    <FORM METHOD=POST ACTION="http:
//www.十万个为什么.com/cgi-bin/survey.cgi">
    Enter Your Name:
    <input name="Name" size=20 maxlength=50>
    <P>Enter your sex:
    <input type=radio CHECKED name=sex value=MALE>Male
    <input type=radio name=sex value=FEMALE>Female
    <P>Select your age<br>
    <SELECT size=2 NAME=age>
      <OPTION> 1-10
      <OPTION> 11-20
      <OPTION> 21-30
      <OPTION> 31-40
      <OPTION> 41-50
      <OPTION> 51-60
      <OPTION> 61 and up
    </SELECT>
    <P>Enter Your Comment:
    <input name="Name" size=40 maxlength=100>
    <P>
    <INPUT TYPE=submit value="Submit">
    <INPUT TYPE=reset value="Reset">
    </FORM>
  </body>
</html>

此表单引用的 CGI 脚本将接收四种不同的数据:提交表单的读者的姓名、年龄、性别和评论。该脚本将不得不解析出这四个值并处理所有字符转换。一个名为 https://www.十万个为什么.com/survey.c 的单独文件被用于创建脚本 survey.cgi,该脚本可能有 100 行长。

广告

总结

在 CGI 脚本的快速概览中,我们了解到

  • CGI 脚本是一个程序——通常是 C 程序或 PERL 脚本。
  • 在大多数服务器上,CGI 脚本位于名为 cgi-bin 的目录中。当浏览器请求脚本的 URL 时,脚本就会执行。
  • 脚本发送到标准输出(STDOUT)的任何内容都将发送到浏览器。字符串“Content-type: text/html\n\n”应该是第一个发送的内容。之后,任何内容都可以;但通常,会发送有效 HTML 文档的有效 HTML 标签。
  • 通过创建 HTML 表单并将 ACTION 指定为脚本的 URL,可以将输入发送到脚本。
  • 当脚本从表单接收数据时,它必须解析出不同的字符串并转换所有修改过的字符。我们看到了一个可以执行这些任务的简单 C 程序。PERL 的 CGI 库(请参阅下一页)使 PERL 脚本的转换变得容易。

如果您在一个真实的网站上这样做,您通常会将每个调查的结果存储到文本文件或数据库中,以便以后查看结果。这对于 C 程序或 PERL 脚本来说都很容易做到。

广告

欲了解更多信息,请查看下一页的链接。