使用 Python 编写 CGI 脚本

作者:Michel Vanaken

《Python 参考手册》摘要将 Python 描述为

一种简单而强大的解释型编程语言,弥合了 C 语言和 shell 编程之间的差距,因此非常适合“一次性编程”和快速原型设计。它的语法由从各种其他语言中借用的结构组成;最突出的影响来自 ABC、C Modula-3 和 Icon ... Python 可用于各种操作系统,其中包括多种 Unix 版本(包括 Linux)、Apple Macintosh O.S.、MS-DOS、MS Windows 3.1、Windows NT 和 OS/2。

还应该注意的是,Python 是一种面向对象的语言。您可以像在 C++ 或 Java 中一样编写类。每当普通人使用 Perl 时,我都会使用 Python [“普通人”?呸!—Ed,他是 Perl 的铁杆粉丝]。它具有大致相同的功能,但可读性更高。但它不应仅限于脚本语言——很多人都在将其用于完整的应用程序。它也是一种完美的胶水语言,例如 Tcl,因为可以轻松地向其中添加新模块(用 C 编写)。它也可以嵌入到 C 应用程序中。

列表 1 显示了我的第一个 Python 脚本。它仍然在办公室的文件服务器上使用。它会删除有缺陷的 MS Windows 应用程序到处留下的 ~*.tmp 文件。它不是真正的常见 Hello World 程序(我们稍后会看到一个),也许它不是完成这项工作的最有效方法,但它演示了该语言的几个特性

  • 变量:变量不需要声明。

  • 递归:请参阅 ScanDir() 函数。

  • 平台独立性:os 模块为当前目录、父目录等提供常量。请参阅 os.curdir、os.pardir...

  • for 语句和列表:在 Python 中,for 的工作方式与 C 中不同。os.listdir() 返回文件列表。例如,在列表 1 中,在每次迭代中

            for p in files
    

p 将成为列表中下一个元素的值。因此,如果 files 是一个包含值的元组

        ['lib', 'include', 'src' ]

第一次,p 将是 'lib'。在第二次迭代中,它将是 'include',依此类推,直到它遍历整个列表。

  • 数组和索引:可以使用一个或两个索引来引用数组。一个索引用于从数组中获取单个元素(如 C 中)。两个索引可用于获取数组的子集。第一个索引给出 from 元素,而第二个索引给出 to 元素。例如,如果 a 是一个包含值 'abcdef' 的数组,则 a[ 2 : 4 ] 将返回 'cd'。两个索引都有默认值:它们分别默认为 from the startto the end。示例:a[ 2 : ] 将返回 'cdef'。负索引可用于从末尾计数;a[ -2 ] 将返回 'e'。请参阅 Python 网站 (https://pythonlang.cn/) 上的 Python 教程 以了解有关数组的更多信息。

  • :与 C 或 Pascal 派生语言不同,没有 Start-BlockEnd-Block 分隔符。Python 仅使用缩进(和“:”字符)。

我在固件部门的一位同事最近在调试他正在编写的 TCP/IP 应用程序时遇到了一些问题。有一个服务器应用程序在嵌入式系统中运行,而客户端应用程序在 PC 上运行。他被一个协议问题困扰了两天,甚至不知道问题是来自客户端还是服务器。每个测试版本都意味着重新编译,最终将代码下载到嵌入式系统中,等等。此外,调试甚至没有屏幕的设备并不总是容易的——您明白我的意思。

因此,在讨论了他的问题之后,我决定编写小的 Python 测试程序来测试他的应用程序。在不到一刻钟的时间里,我就测试了他的服务器应用程序。这包括编写一个 Python 脚本并在 Linux 盒子的控制台上运行它,同时运行 tcpdump。由于问题不是来自服务器,我编写了另一个程序来测试他的客户端应用程序。这个脚本伪装成服务器,我们立即发现了问题。我的同事对我编写这两个脚本所用的时间之短印象深刻,所以我给了他一本《Python 教程》的副本。

一些使用套接字的简单脚本可以在 列表 2a 和 2b 中找到。它们来自 《Python 库参考》

我的公司在客户端/服务器环境中销售时间和考勤软件。支持的平台包括 Unix 和 NT。时间和考勤的最大问题是,尽管所有客户的一般功能都相同,但他们都有特殊的特定规则。这就是为什么软件部门正在考虑在其软件中包含 Python 解释器的原因。这将允许现场自定义,并且它在我们的所有平台上都可用。

文档和可用性
  • Python 主页位于 https://pythonlang.cn/。该网站上列出了镜像站点以及 Python 的当前发行版。

  • 教程和其他文档,包括《语言参考》、《库参考》、关于如何扩展和嵌入解释器的指南以及 FAQ,都可以在 Python 主页的 doc 目录 (https://pythonlang.cn/doc/) 中找到。

  • 关于 Python 的两本书即将出版

    • 《Python 编程》,作者 Mark Lutz,O'Reilly and Associates Publishers 出版。

    • 《使用 Python 进行互联网编程》,作者 Aaron Watters、Guido Van Rossum(该语言的作者)和 James Ahlstrom,来自 MIS Press/Henry Holt Publishers 出版。请参阅 https://pythonlang.cn/python/arwbook.html。

  • 最后,还有一个专门讨论 Python 的新闻组:comp.lang.python。

CGI 脚本

在下面的文本中,我将假设您在本地运行自己的 HTTP 守护程序。我的偏好是 Apache,但任何服务器都可以完成这项工作,只要配置正确。

当然,您应该在您的系统上安装 Python。您需要配置它以使用 gdbm 模块,因为它在 count.py 中使用。

对于与关系数据库接口的脚本示例,我使用了 PostGres95(及其贡献的 Python 模块 PyGres95)。PostGres95 可从 http://www.ki.net/postgres95/ 获取。PyGres95 可从 http://zen.via.ecp.fr/via_dvpt/products/pygres.html 获取。

要理解下面的文本,您应该知道如何编写 HTML 页面,对 CGI 的工作原理有一个大致的了解,并且有一些 C 编程的背景。

常用脚本

列表 3,helloworld.py,是我们的第一个脚本。它非常简单。从命令行运行,它将打印一个 HTML 文档。但是您应该将其复制到您的 cgi-bin 目录,然后使用 URL https:///cgi-bin/script.py 从浏览器调用它。

此脚本显示一条小消息和本地时间。在这里,您只需要注意一件事:脚本必须发送一个描述文档内容的标头。这是通过 Content-type 标头完成的。常见值包括 text/htmltext/plainimage/gifimage/jpeg。标头以空行结尾。它由客户端浏览器使用,不会出现在生成的页面中。而且,正如您将看到的,脚本是执行的,而不仅仅是显示在浏览器中。脚本打印到 sys.stdout 的所有内容都将发送到客户端,而错误消息将转到错误日志 (/usr/local/etc/httpd/logs/error_log,如果您使用的是 Apache)。

列表 4 是用 Python 编写的著名的 Count 脚本。这用于显示特定页面被访问次数的图形计数器。

此脚本导入一个名为 cgi 的模块,我稍后将对此进行描述。它用于检索传递给脚本的 URL 参数。此脚本与 gdbm(在配置 Python 时必须包含在模块列表中)接口,以存储 { URL ; 访问计数 } 对。

这是我们第一次介绍 Python 字典。字典在文献中通常被称为“关联数组”。这意味着您可以使用键而不是索引来访问数组。例如,如果您想处理电子邮件地址簿,其中包含如下对

"Michel", "Michel.Vanaken@ping.be"
"Veronique", "Vero@home.sweet.home"

以下是在 C 和 Python 中检索 Michel 地址的方法

struct {
        char    *key ;
        char    *addr ;
} email[ MAX ] ;
int     i ;
for( i=0 ; i<MAX ; i++ ) {
  if( strcmp( email[ i ].key, "Michel" ) = 0 ) {
            printf( "%s\n", email[ i ].addr ) ;
            break ;
  }
}
if( i = MAX ) {
        printf( "Not found\n" ) ;
}
if email.has_key( "Michel" ) :
        print email[ "Michel" ]
else :
        print "Not found"

使用 Python 添加条目也很容易

email[ "Homer" ] =  \
"HSimpson@Springfield.power_plant.com"

如果 Homer 不是有效的键,则添加一个条目,如果它已经存在,则覆盖旧值。

我们看到这里的 Content-typeimage/x-bitmap(因为浏览器正在等待 <img src=...>)。

当然,位图不是很漂亮(我用绘图软件包绘制它们,将它们保存为 xbm 文件,然后在 Emacs 中使用了大量的键盘宏和 M-Kill/Yank 矩形)。此脚本的目标不是重新发明轮子,而是允许读者将其与网上以不同语言广泛提供的其他版本进行比较。

为了使用此脚本,必须创建 gdbm 数据库。将当前目录更改为您的 cgi-bin 目录,运行 Python,然后键入

import gdbm
gdbm.open( "counters.gdbm", "n", 0666 )

然后使用 Ctrl-D 退出 Python。

还应该注意的是,此脚本创建的 xbm 文件是错误的。它包含一个额外的字节(在 print_footer() 函数中添加),以简化 print_digit_values() 函数(在此版本中,没有逗号的测试)。

调试

在将您的 CGI 脚本上线之前,您应该通过仔细测试它们来确保它们确实是干净的,尤其是在接近边界或超出边界的情况下。在作业中间崩溃的脚本可能会导致大问题,例如数据库应用程序中的数据不一致。您可以通过从命令行运行脚本来消除大多数问题;然后从您的 HTTP 守护程序测试它。

首先,您必须记住 Python 是一种解释型语言。这意味着许多语法错误直到运行时才会被发现。您必须确保您的脚本已在控制流的每个部分都经过测试。您可以通过生成参数集来做到这一点,您将在脚本的开头对其进行硬编码。

然后,确保不正确的输入不会导致脚本的不正确行为。不要期望您的脚本接收到的所有参数都有意义。它们可能在通信过程中损坏,或者某些黑客可能会尝试获取比通常允许的更多的数据。

列表 5 显示了我们的 Hello World 脚本的不同版本,并演示了以下功能

  • 元组:元组是由逗号分隔的多个值组成的数组。输出元组用括号括起来。localtime() 函数返回一个元组,该元组可以在一个变量中赋值(该变量成为一个元组)。或者,如本脚本中所示,元组的各个元素可以一次分配给多个变量。

  • elif (“else if”) 语句:列表 5 有两个语法错误,当解释器加载脚本时不会检测到这些错误,但在执行时会导致其崩溃。它会在圣诞节崩溃,因为调用了未定义的 Christmas() 函数,并且它会在元旦再次崩溃,因为除了“新年快乐!”之外,它还尝试打印一个不存在的“Max”变量(可能是由于从旨在祝某人生日快乐的脚本中剪切和粘贴造成的?)。以下是在圣诞节访问脚本时您将在 error_log 文件中找到的内容

    Traceback (innermost last):
      File "/cgi-bin/buggy.py", line 59, in ?
        Main()
      File "/cgi-bin/buggy.py", line 53, in Main
        Christmas()
    NameError: Christmas
    

脚本似乎正常执行(尤其是在元旦,因为所有应该打印的内容实际上都已打印)这一事实可能是一个陷阱。脚本实际上已经崩溃了!

当然,在此脚本中,崩溃不是什么大问题。但在 Intranet 应用程序中,它可能会非常有害。例如,想象一下一个脚本,它显示一条消息说它已更新您的库存数据库,但实际上在给出消息后立即崩溃。用户认为一切进展顺利,但数据尚未更新。

让我们回到列表 4。我们已经看到生成的 xbm 不好;但可能还有其他问题。如果发生以下情况会怎样

  • 脚本被调用时带有

    <img
    src="https:///cgi-bin/count.py?\
    https:///index.html">
    

    而不是

    <img
    src="https:///cgi-bin/count.py?
    _url=_https:///index.html">?
    
  • 数据库文件 counters.gdbm 不存在?

  • 访问计数超过 9999?

我建议您尝试这些,并尝试您自己的解决方案。对于列表中的最后一种情况——访问计数超过 9999——有几种解决方案;我建议如果 inc_counter() 函数中递增的值的长度超过 DIGITS,则修改 DIGITS 值。如果您的 Web 浏览器不显示任何内容,您将如何查看生成的文件?也许您可以添加以下代码,将对 CGImain() 的调用替换为 TSTmain(),然后从命令行运行脚本

def TSTmain() :
    #######
 url = "https:///test.html"
 counter = get_put_counter( url )
 print_header()
 print_digits_values( counter )
 print_footer()
表单处理

列表 6 显示了我们将在本文的剩余部分讨论的表单的 HTML 源代码。它允许用户输入一些值以对数据库执行查询。表单的 action 参数应根据您的需要进行调整。对于实际应用程序,您应该将 localhost 替换为您主机的完全限定名称。脚本的名称也应进行调整以调用正确的内容。请注意,HTML 代码定义了一个 隐藏 字段 (TableName)。

让我们从一个只回显用户输入的值的脚本开始(参见 列表 7)。您将看到,即使您将表单留空,也会显示两个参数。第一个是 (TableName),我们表单中的一个隐藏参数,第二个是 Submit 按钮的值(它也是一个字段)。请注意

  • 我们的脚本导入的 CGI 模块用于解析 HTML 表单发送的输入。它适用于 GETPOST 方法。

  • cgi.SvFormContentDict() 构建一个字典,其中包含

        { field name ; field value }

与用户编码的数据相对应的对。

  • cgi.escape() 用于将特殊字符转换为其 HTML 转义序列(例如,< 变为 <)。

数据库查询

现在我们将查看更多可以在 Intranet 应用程序中使用的“真实生活”脚本。

我们将使用 PostGres95。必须正确安装和配置它。我不会在这里解释该过程,因为它需要大量的额外文本。但应该提到两件事

  1. 在您的系统上运行 CGI 脚本时使用的“用户”必须有权访问 PostGres95 以及正在查询的数据库。

  2. 以下脚本中使用的 connect() 函数可能需要进行调整才能在您的系统上工作。我的不需要任何参数,因为一切都使用我配置的默认设置工作。

有关更多信息,请参阅 PostGres95 手册。

PyGres95 模块提供与 LIBPQ API 相同的接口,LIBPQ API 也在 PostGres95 手册中进行了描述。您应该知道有一个 connect() 函数用于连接到数据库,以及一个 query() 函数,该函数接收 SQL 字符串作为参数。

列表 8 显示了一个脚本,该脚本将处理客户数据库的查询,该数据库的结构类似于查询表单中可能存在的字段。该脚本将连接到数据库,构建 SQL 命令,查询数据库,最后,在为每个请求动态构建的表中显示结果。当然,这里的 SQL 语句非常简单,但可以编写脚本来执行任何操作。

此脚本不是很实用。我们必须为我们要使用的每个表编写特定的代码。列表 9 的脚本实现了从 HTML 表单对任何单个 PostGres95 表/视图的通用查询。这意味着它将适用于您需要表子集的任何查询。它可以用于客户(如我们的示例)、供应商或文章。与前一个脚本的主要区别在于 build_query() 函数

该脚本现在实现以下行为:对数字字段进行的查询将需要完全匹配,而对文本字段进行的查询将被视为以通配符结尾。这意味着数字字段被视为 ID,并且不可能,例如,使用它来搜索价值在 500 美元到 1,000 美元之间的文章。但它可以用于在个人数据库中搜索所有以“Van”开头的名称。

限制:为了确定字段的类型,如果字段名称以“num”结尾,我们将认为它是数字字段。这是因为发送到 CGI 脚本的所有数据都被视为文本。当然,您可以解析该值以查看它是否为数字。但这并不总是一个好的选择。如果您想搜索所有以“800”开头的电话号码,我们的脚本如果认为它是数字字段,将查找完全匹配,并且将找不到任何内容。当然,您也可以完全重写 build_query() 函数以满足您的需求。

脚本需要知道它应该在哪个表上执行查询。这就是为什么我们的表单包含一个名为 TableName 的不可见字段。它必须设置为所需表的名称。

表单字段名称必须与表字段名称相同,因为脚本使用它们来执行查询。但是,当然,用户输入表单上显示的标签可以是任何内容。

最后,脚本包含多行可以注释或取消注释以在结果页面中启用或禁用一些调试字符串(例如,作为查询字符串)。

下一步去哪里?

Python 有几个强大的功能,本文中没有讨论。Python 支持异常处理,就像在 C++ 或 Modula-3 中一样。这对于捕获 CGI 脚本中的错误非常有用。甚至可以编写一个脚本,其中包含一个函数,以便在检测到意外错误时通过电子邮件向其作者发送错误报告。当然,您也可以编写自己的类。

对于 CGI 脚本,尽管我们在示例脚本中没有使用它们,但仍有一些其他功能可用。在 Python 主页上,您会找到将 Python 解释器嵌入到 Apache 中的代码。Apache 本身带有与 PostGres95 交互的可选模块。但 PostGres95 不是唯一可用的数据库——其中,还有一个用于 Oracle 的模块。

现在,如果您想尝试 Python,首先要做的是阅读《Python 教程》(请参阅“文档和可用性”),然后打印一份《Python 库参考手册》的副本。然后,您应该尝试实现简单的目标——例如删除所有超过一天的 ~*.tmp 文件。

在 Web 上查找 Python
  1. https://pythonlang.cn/

  2. http://www.ora.com/

  3. https://pythonlang.cn/python/arwbook.html

  4. news:comp.lang.python

  5. http://www.ki.net/postgres95/

  6. http://zen.via.ecp.fr/via_dvpt/products/pygres.html

Michel Vanakan 是一位 32 岁的软件工程师和兼职网络管理员。他的兴趣包括幻想和科幻书籍和游戏、荒野漫步和轻型飞机飞行。可以通过 Michel.Vanaken@ping.be 联系他。

加载 Disqus 评论