cgimodel:使用 Python 轻松进行 CGI 编程

作者:Chenna Ramu

通用网关接口 (CGI) 是一种让来自世界各地的其他人执行驻留在您计算机上的程序的方法。CGI 是动态的,因为它实时运行。您可以使用 HTML(超文本标记语言)来装饰 CGI 输出。大多数时候,CGI 用作现有应用程序的前端。CGI 可以很简单也可以很复杂,具体取决于您的项目的复杂性。大多数 CGI 开发人员都知道调试 CGI 程序带来的挫败感。

我们介绍一种非常简单且稳健的 Python CGI 编程方法。调试您的 CGI 很简单,因为您可以在命令行上进行调试,并且将现有应用程序集成到 CGI 中只需一步。

为了我们的工作,我们选择了 Python,一种具有清晰语法的面向对象脚本语言。它非常易于使用,广泛可用且是免费软件。

我们的目标受众是经验丰富的 CGI 程序员和新手 CGI 程序员。我们将交替使用“函数”和“方法”这两个词。请注意,CGI 可以用任何计算机语言编写。

GET 和 POST 方法

有两种调用 CGI 程序的方法:通过包含所有数据的 URL,或通过提交 HTML 表单。

HTTP 中定义的两种将数据发送到 CGI 的方法是 GETPOST。当方法为 GET 时,CGI 程序从 QUERY_STRING 环境变量获取输入。当方法为 POST 时,CGI 程序从标准输入 (STDIN) 获取输入。在这两种情况下,都必须解析输入以获得输入参数名称、值对。

CGI 可能很复杂,也可能不复杂,但是当您有一个具有许多功能的大型应用程序时,您可能会在测试、调试等方面遇到问题。这对于所有软件项目都是如此。调试对于 CGI 来说变得很麻烦。例如,当方法为 GET 时,您必须设置环境变量 QUERY_STRINGREQUEST_METHOD。当方法为 POST 时,您必须设置 REQUEST_METHODCONTENT_LENGTH(字节数)才能从标准输入 (STDIN) 读取。此外,当您的程序崩溃时,您的浏览器看不到它——您不知道发生了什么。在这种情况下,您收到的唯一消息是 Web 服务器发出的错误报告。

您可以根据您的需要使用这些方法(GET/POST)中的任何一种。如果您要向 CGI 发送更多数据,请使用 POST 方法。当您要发送到 CGI 的数据较少时,请使用 GET 将所有数据放入 URL 中。例如,在一行中,键入

<A HREF="/cgi/cgimodel.py?fun=DisplayFile&fileName=
cgimodel.pycgimodel">cgimodel</A>

使用 HTML 表单(对于 POST 方法),同样是

<FORM METHOD="post" ACTION="/cgi-bin/cgimodel.py">
<INPUT TYPE=hidden name=fun value=DisplayFile>
<INPUT TYPE=hidden name=fileName value=cgimodel.py>
<INPUT TYPE=SUBMIT VALUE="cgimodel">
</FORM>
我们都知道调试 CGI 程序的困难,并且采用了不同的风格。我们的目的是构建不像传统方式那样工作,而是像其他在命令行上工作的程序一样工作的 CGI。这意味着您可以像在命令行上测试任何其他程序一样测试您的 CGI。当它在命令行上工作时,保证在 Web 上表现出相同的行为。
cgimodel 模块

让我们看看如何使用 cgimodel 让生活更轻松,它可以让您以优雅的方式集成现有应用程序,而无需太多麻烦。它基本上由两个模块组成:cgimodel.py(见列表 1)和 cgidisp.py(见列表 2)。

列表 1. cgimodel.py

列表 2. cgidisp.py

cgimodel.py 是 Python CGI 模块的包装器。它还封装了从命令行读取的功能,因此从 HTML 表单、URL 或命令行调用没有真正的区别。

cgimodel.py 模块中的 CollectArgs 函数负责从 CGI 或命令行收集参数,包括名称、值参数。在 UNIX 命令行上,您可以像这样提供名称、值参数

-name1 value1 -name2 value2

或像这样

name1 value1 name2 value2
URL 和表单也是如此。

您无需修改 cgimodel.py 中的任何内容。您只需要使用它。cgimodel 的主要部分包含以下行

d = Dispatcher()
parDict = CollectArgs(parDict)
print mime_html
fun=parDict['fun']
if not fun:
   print "usage: cgimodel -fun functionName"
   d.ShowAvailableFunc()
   TraceIt(parDict)
else:
   try:
      d.dispatch(fun,parDict)
   except:
      TraceIt(parDict)

cgimodel.py 尝试调用您作为参数提供给参数 -fun 的函数。

当没有这样的函数可用时,它会告诉您可以调用的函数名称。如果程序中存在异常(由于语法错误等),则会追溯并报告异常。您可以使用此功能将异常通过电子邮件发送给自己,并使您的 CGI 程序更加稳定。

cgidisp 模块

另一个模块 cgidisp.py 是您必须修改或插入类 Dispatcher 的实例的模块,以便您的应用程序使用一个参数,即 parDict。例如,在类 Dispatcher 下,如果您定义一个类似如下的方法

def cmd_myHello(self,parDict):
print "<H1>Hello</H1>"

那么这个函数就可以立即供外部世界使用。您可以通过以下方式在命令行上调用它

cgimodel.py -fun myHello
使用 URL(GET 方法)
cgimodel.py?-fun=myHello
并使用 HTML 表单作为
<FORM METHOD="post" ACTION="/cgi-bin/cgimodel.py>
<INPUT TYPE=hidden name=fun value=myHello>
<INPUT TYPE=SUBMIT VALUE="Say Hello">
</FORM>
就这么简单!

Dispatcher 下的 dispatch 方法从 cgimodel.py 调用,带有一个参数。此参数是要执行的函数的名称。有趣的部分来了。在函数名称前加上“cmd_”字符串后,dispatch 方法检查是否可以使用 hassattr 找到这样的函数。Dispatcher 将命令映射到函数并执行它。这样,您无需使用查找表来跟踪可用函数。向新函数添加新命令的额外开销不存在;您只需编写函数并通过命令行调用它即可。该功能已经存在。这种模式在 Python 中是可能的,因为它是一种高度动态的语言。

请注意,在调用方法时,我们没有使用方法的前缀 cmd_。这将在稍后解释。

Dispatcher 类的主部分包含以下内容

class Dispatcher:
   def __init__(self):
      self.debug = None
   def dispatch(self, command,args=None):
      mname = 'cmd_' + command
      if hasattr(self, mname):
      method = getattr(self, mname)
         if not args:
            return method()
         else:
            return method(args)
      else:
         print "<PRE>" self.error(command)<\n>
         self.ShowAvailableFunc()
         print "</PRE>"
   def cmd_Hello(self,parDict):
      print " Hello World !"
   def cmd_ShowDict(self):
      print "<PRE></H1>Debug Info:</H1><HR>"
      for k,v in parDict.items():
            print "%-30s :  %s " %(k,v)
      print "</PRE>"
   def error(self,s):
      print " #<B>Error<B>: <BB>Function ( %s ) not available\n " %s
      return

您的所有参数都可以在 parDict 字典中找到,无论它们是从 URL、表单还是命令行输入的——没有区别。您可以通过这种方式检查它们是否存在

if parDict['param']:
   print " yes ", parDict['param']
else:
   print " No "
当没有参数时,即当您尝试访问未指定的参数时,将返回 None 对象。

Dispatcher 内的实例有两种类型:以“cmd_”字符串为前缀的实例有资格从外部调用;内部实例在外部不可见。例如,error 实例无法从 CGI 调用,但实例 cmd_Hellocmd_ShowDict 可以调用。制定此约定是为了区分用于内部(在类 Dispatcher 内部使用)和外部(由 cgimodel/cgidisp 使用)使用的实例。

因此,为您要与 CGI 一起使用的实例添加“cmd_”前缀。例如,可以使用以下命令调用 cmd_TopPage

cgimodel.py -fun TopPage

在命令行上,以及

cgimodel.py?-fun=TopPage
将是相应的 URL。-fun 是强制性的。这样,您可以指示要调用的函数。显然,您可以拥有任意数量的函数,并且它们已准备好用于 CGI。这是较大型 CGI 项目的确切要求。

该模块免费附带了几个函数。函数 DisplayFile 在 Web 上显示彩色 Python 源代码。这依赖于模块 py2html.py,该模块随标准 Python 发行版提供。

cgimodel.py -fun DisplayFile -fileName cgimodel.py

URL 等效项

cgimodel.py?-fun=DisplayFile&fileName=cgimodel.py
请注意 name=value 和 & 分隔名称、值对——CGI 的传统规范方法。

方法 cmd_ShowDict 显示 parDict 字典中的所有字典项,并且对于检查您是否提供了正确的参数很有用。

向 CGI 添加现有模块

假设您已经有这个模块

#!/usr/bin/env python
#  testmethod.py
def  Method1(name1,name2,name3):
        print name1,name2,name3
if __name__ == '__main__':
     Method1('one','two','three')

编辑 cgidisp.py 模块,在类 Dispatcher 下插入以下方法

def cmd_TestMeth(self,parDict):
   import testmethod
   name1 = parDict['name1']
   name2 = parDict['name2']
   name3 = parDict['name3']
   testmethod.Method1(name1,name2,name3)
现在它已准备就绪!您可以通过在一行中键入以下内容在命令行上调用它
cgimodel.py -fun TestMeth -name1 one -name2 two -name3 three
或通过 URL(全部在一行中)
cgimodel.py?-fun=TestMeth&name1=one&name2=two&name3=three
或通过表单
<FORM METHOD="post" ACTION="/cgi-bin/cgimodel.py">
<INPUT TYPE=hidden name=fun value=TestMeth>
<INPUT TYPE=hidden name=name1 value=one>
<INPUT TYPE=hidden name=name2 value=two>
<INPUT TYPE=hidden name=name3 value=three>
<INPUT TYPE=SUBMIT VALUE="Run">
</FORM>
如果您可以将 HTML 文本与 CGI 模块分开,那就更好了,这样 CGI 看起来更简洁易读。您可以使用模板模块(请参阅资源)来做到这一点。模板模块使文本远离 CGI,并具有页面-段落结构。每个 CGI 调用都可以与一个页面关联,每个段落都可以用于设置 HTML 页面的视图。

cgimodel 可以托管任意数量的应用程序。编写 CGI 前端的冗余不再必要。由于单个 cgimodel 可以运行许多应用程序,因此可以完成特定于每个应用程序的日志记录信息,以便稍后进行分析,以提高服务器性能、每个应用程序的稳定性、更好的服务等。目前,这可以使用 Web 服务器生成的日志信息来完成。

使用 cgimodel.py、cgidisp.py 以及可能的 template.py 模块,您应该会发现编写和测试 CGI 程序更容易。

资源

cgimodel: CGI Programming Made Easy with Python
Chenna Ramu (ramu.chenna@embl-heidelberg.de) 拥有数学研究生学位。他目前在德国海德堡的欧洲分子生物学实验室工作,从事生物计算领域。兴趣包括关于 DNA/蛋白质序列的理论研究、数据库开发、解析、编译器、系统管理和 Web 技术。他最近接触了 Python(感谢 Gert Vriend),并发现它非常适合编程。

Christine Gemuend 拥有计算机科学学位。她对并行计算机和数据库系统感兴趣,并且正在信息学领域工作。

加载 Disqus 评论