cgi– 通用网关接口支持

源代码: Lib / cgi.py


通用网关接口(CGI)脚本的支持模块.

模块定义了许多由Python编写的CGI脚本使用的实用程序.

介绍

HTTP服务器调用CGI脚本,通常用于处理通过HTML <FORM>要么 <ISINDEX>元件。

大多数情况下,CGI脚本存在于服务器的特殊情况中cgi-bin目录.HTTP服务器在脚本的shell环境中放置有关请求的各种信息(例如客户端的主机名,请求的URL,查询字符串和许多其他项目),执行脚本,并将脚本的输出发送回客户端.

脚本的输入也连接到客户端,有时表单数据以这种方式读取;在其他时候,表单数据通过URL的“查询字符串”部分传递。该模块旨在处理不同的情况,并为Python脚本提供更简单的界面。它还提供了许多有助于调试脚本的实用程序,最新增加的是支持从表单上传文件(如果您的浏览器支持它).

CGI脚本的输出应包含两个部分,分开用空白线。第一部分包含许多标题,告诉客户端数据是什么样的。用于生成最小标题部分的Python代码如下所示:

print("Content-Type: text/html")    # HTML is followingprint()                             # blank line, end of headers

第二部分通常是HTML,它允许客户端软件显示带有标题,内嵌图像等的格式化文本。这是Python代码,用于编写一个简单的部分HTML:

print("<TITLE>CGI script output</TITLE>")print("<H1>This is my first CGI script</H1>")print("Hello, world!")

使用cgi模块

首先写import cgi.

编写新脚本时,请考虑添加以下行:

import cgitbcgitb.enable()

这会激活一个特殊的异常处理程序,如果发生任何错误,它将在Web浏览器中显示详细的报告。如果您不想向您的脚本用户显示您的程序的内容,您可以将报告保存到filesinstead,使用以下代码:

import cgitbcgitb.enable(display=0, logdir="/path/to/logdir")

在脚本开发期间使用此功能非常有用。由cgitb生成的报告提供的信息可以为您节省大量时间来缓解错​​误。您可以随后在测试脚本时删除cgitb行,并确信它可以正常工作.

要获取提交的表单数据,请使用FieldStorage类。如果form包含非ASCII字符,请使用encoding关键字参数设置为为文档定义的编码的值。它通常包含在HTML文档的HEAD部分的META标签中或Content-Type头)。这将从标准输入或环境中读取表单内容(取决于根据CGI标准设置的variousenvironment变量的值)。因为它可能会消耗标准输入,所以它应该只被实例化一次.

FieldStorage实例可以像Python字典一样索引。它允许使用in运算符进行成员资格测试,并且还支持标准字典方法keys()和内置函数len()。包含空字符串的表单字段将被忽略,并且不会出现在字典中;保留这些值,在创建keep_blank_valuesinstance.FieldStorage时为可选的

关键字参数提供真值。例如,下面的代码(假设Content-Type标题和空行已经打印)检查字段nameaddr都设置为非emptystring:

form = cgi.FieldStorage()if "name" not in form or "addr" not in form:    print("<H1>Error</H1>")    print("Please fill in the name and addr fields.")    returnprint("<p>name:", form["name"].value)print("<p>addr:", form["addr"].value)...further form processing here...

这里的字段,访问通过form[key],它们本身是FieldStorage(或MiniFieldStorage,取决于formencoding)的实例。value实例的属性产生字段的字符串值。getvalue()方法直接返回此字符串值;它还接受一个可选的第二个参数作为默认值,如果请求的密钥不存在则返回.

如果提交的表单数据包含多个具有相同名称的字段,则由form[key]检索的对象是不是FieldStorageMiniFieldStorage实例,而是一个这样的实例列表。同样,在这种情况下,form.getvalue(key)会返回一个字符串列表。如果您发现这种可能性(当您的HTML表单包含多个具有相同名称的字段时),请使用getlist()方法,该方法始终返回值列表(这样您就不需要特殊情况下单个项目用例)。例如,此代码连接任意数量的用户名字段,用逗号分隔:

value = form.getlist("username")usernames = ",".join(value)

如果字段表示上传的文件,则通过value属性或getvalue()method以字节为单位读取内存中的整个文件。这可能不是你想要的。您可以通过测试filename属性或file属性来测试上传的文件。然后,您可以在file属性中读取数据,然后将其作为FieldStorage实例的垃圾收集的一部分自动关闭(read()readline()方法willreturn bytes):

fileitem = form["userfile"]if fileitem.file:    # It"s an uploaded file; count lines    linecount = 0    while True:        line = fileitem.file.readline()        if not line: break        linecount = linecount + 1

FieldStorage对象也支持在with语句中使用,它会在完成时自动关闭它们.

如果在获取内容时遇到错误上传文件(例如,当用户通过单击“返回”或“取消”按钮中断表单提交时)该字段的对象的done属性将设置为值-1.

文件上传草稿标准有可能从一个字段上传多个文件(使用递归的multipart/*编码)。当发生这种情况时,该项目将是一个类似字典的FieldStorage项目。这可以确定通过测试它type属性,应该是multipart/form-data(或者可能是另一种匹配multipart/*的MIME类型)。在这种情况下,它可以递归迭代,就像顶层表单对象一样.

表单以“旧”格式提交时(作为查询字符串或类型为application/x-www-form-urlencoded的singledata部分),这些项目将成为类MiniFieldStorage的实例。在这种情况下,list, filefilename属性总是None.

通过POST提交的表单也有一个查询字符串将同时包含FieldStorageMiniFieldStorage items.

更改版本3.4: file属性在创建FieldStorage instance.

的垃圾收集时自动关闭。版本3.5:添加了对FieldStorage的上下文管理协议的支持类。

更高级别的界面

上一节介绍了如何使用FieldStorage类。本节描述了一个更高级别的接口,该接口已添加到此类中,以便以更易读和更直观的方式执行此操作。该接口不会使前面部分中描述的技术过时 – 它们仍然有效地处理文件上传,例如.

接口包含两个简单的方法。使用这些方法,您可以通过一般方式处理表单数据,而无需担心是否只有一个或多个值在一个名称下发布.

在上一节中,您学会了在用户期望的任何时候编写以下代码在一个名称下发布多个值:

item = form.getvalue("item")if isinstance(item, list):    # The user is requesting more than one item.else:    # The user is requesting only one item.

这种情况很常见,例如当一个表单包含一组具有相同名称的多重检查框时:

<input type="checkbox" name="item" value="1" /><input type="checkbox" name="item" value="2" />

但是,在大多数情况下,只有一个表单使用表单中的特定名称进行控制,然后您只需要一个与此名称关联的值。所以你写了一个包含例如这段代码的脚本:

user = form.getvalue("user").upper()

代码的问题在于,您永远不应期望客户端会为您的脚本提供有效的输入。例如,如果一个好奇的用户将另一个user=foo对附加到查询字符串,那么脚本就会崩溃,因为在这种情况下getvalue("user")方法调用会返回一个字符串而不是字符串。在列表上调用upper()方法是无效的(因为列表没有这个名称的方法)并导致AttributeError异常

所以,适当的方式读取表单数据值总是使用code来检查获得的值是单个值还是值列表。这很麻烦,导致脚本可读性降低.

更方便的方法是使用这个更高级别界面提供的方法getfirst()getlist() .

FieldStorage.getfirstname, default=None

此方法始终只返回与表单字段name。该方法仅返回第一个值,以防在此名称下发布更多值。请注意,接收值的顺序可能因浏览器而异,因此不应计算在内。[1]如果不存在这样的字段或值,则该方法返回由参数default。此参数默认为None如果没有说明的话

FieldStorage.getlistname

此方法始终返回与表单字段name。如果name不存在这样的表单字段或值,则该方法返回一个空列表。如果只存在一个这样的值,它返回一个由一个项组成的列表.

使用这些方法你可以编写漂亮的紧凑代码:

import cgiform = cgi.FieldStorage()user = form.getfirst("user", "").upper()    # This way it"s safe.for item in form.getlist("item"):    do_something(item)

函数

如果你想要更多的控制,或者你想在其他情况下使用这个模块中实现的一些算法,这些非常有用.

cgi.parse (fp=None, environ=os.environ, keep_blank_values=False, strict_parsing=False )

在环境中或从文件中解析查询(文件默认为sys.stdin)。keep_blank_valuesstrict_parsing参数被通过urllib.parse.parse_qs()不变.

cgi.parse_qs (qs, keep_blank_values=False, strict_parsing=False)

模块中不推荐使用此功能。使用 urllib.parse.parse_qs()代替。这里只保留向后兼容性.

cgi.parse_qslqs, keep_blank_values=False, strict_parsing=False

模块中不推荐使用此功能。使用 urllib.parse.parse_qsl()代替。它只保留在这里以便向后兼容.

cgi.parse_multipart(fp, pdict, encoding=”utf-8″, errors=”replace”)

输入类型multipart/form-data的输入(用于文件上传)。参数是fp输入文件,pdict对于包含Content-Type标题中的其他参数的字典和encoding,请求编码

就像urllib.parse.parse_qs():keys是字段名称,每个值都是该字段的值列表。对于非文件字段,值是字符串列表.

这很容易使用,但是如果你想要兆字节上载则不太好 – 在这种情况下,使用FieldStorage类而不是更灵活.

更改版本3.7:添加了encodingerrors参数。对于非文件字段,value现在是一个字符串列表,而不是bytes.

cgi.parse_header(string)

Parse MIME header(例如Content-Type)成主要值和参数字典.

cgi.test ( )

强大的测试CGI脚本,可用作主程序。写入最小的HTTP标头并格式化以HTML格式提供给脚本的所有信息.

cgi.print_environ ()

格式化HTML中的shell环境.

cgi.print_formform

格式化HTML格式的表格

cgi.print_directory)

格式化HTML中的当前目录

cgi.print_environ_usage

在HTML中打印一个有用的(由CGI使用)环境变量列表.

cgi.escape (s, quote=False

将字符"&", "<"">"中的字符s转换为HTML-safesequences。如果需要在HTML中显示可能包含此类字符的文本,请使用此选项。如果可选标志quote是的,引号字符(")也被翻译;这有助于包含在由双引号分隔的HTMLattribute值中,如<a href="...">。请注意,单引号从未被翻译过.

自版本3.2以来已删除:此函数不安全,因为默认情况下quote为false,因此不推荐使用。使用html.escape()代替

关于安全性

有一条重要规则:如果你调用外部程序(通过os.system()os.popen() functions。或其他具有相似功能的人),请确保不要将从客户端收到的任意字符串传递给shell。这是一个众所周知的安全漏洞,Web上任何地方的聪明人都可以利用一个容易上当的CGI脚本来调用任意shell命令。甚至部分URL或字段名称都不能被信任,因为请求不必来自您的表单

为了安全起见,如果必须将从表单获取的字符串传递给shellcommand,你应该确保字符串只包含字母数字字符,短划线,下划线和句点.

Unix系统上安装你的CGI脚本

阅读HTTP服务器的文档,并与当地的系统管理员联系,找到应安装CGI脚本的目录;通常位于服务器树的cgi-bin目录中.

确保你的脚本是“他人”可读和可执行的;Unix文件模式应该是0o755八进制(使用chmod 0755 filename)。确保脚本的第一行包含#!从第1列开始,后跟Python解释器的路径名,例如:

#!/usr/local/bin/python

确保Python解释器存在且可由“其他”执行。

确保脚本需要读取或写入的任何文件分别由“其他人”读取或写入 – 他们的模式应该是0o644 forreadable和0o666为了可写。这是因为出于安全原因,HTTP服务器以“nobody”用户身份执行您的脚本,没有任何特殊的特权。它只能读取(写入,执行)每个人都可以读取(写入,执行)的文件。执行时的当前目录也不同(通常是服务器的cgi-bin目录),环境变量集也与登录时的内容不同。特别是,不要计算shell的可执行文件的搜索路径(PATH)或Python模块搜索路径(PYTHONPATH)设置为有趣的任何东西

如果你需要从不在Python的defaultmodule搜索路径上的目录加载模块,您可以在导入其他模块之前更改脚本中的路径。例如:

import syssys.path.insert(0, "/usr/home/joe/lib/python")sys.path.insert(0, "/usr/local/lib/python")

(这样,将首先搜索最后插入的目录!)

Unix系统的说明会有所不同;检查你的HTTP服务器文档(它通常有一个关于CGI脚本的部分).

测试你的CGI脚本

遗憾的是,当您从命令行尝试CGI脚本时,它通常不会运行,并且从服务器运行时,从命令行完美运行的脚本可能会失败。你应该从命令行测试你的脚本有一个原因:如果它包含语法错误,那么Python解释器根本不会执行它,而HTTP服务器很可能会向客户端发出一个神秘的错误.

假设您的脚本没有语法错误,但它不起作用,您可以使用nochoice但是要阅读下一部分.

调试CGI脚本

首先,检查一些简单的安装错误 -阅读上面关于安装CGI脚本的部分可以为您节省大量时间。如果您不确定是否已正确理解安装过程,请尝试安装此模块文件的副本(cgi.py)作为CGI脚本。当作为脚本调用时,该文件将以HTML格式转储其环境和表单的内容。给它正确的模式等,并发送一个请求。如果它安装在标准的cgi-bin目录中,则应该可以通过在浏览器中输入以下格式来输入请求:

http://yourhostname/cgi-bin/cgi.py?name=Joe+Blow&addr=At+Home

如果这给出了404类型的错误,服务器找不到脚本 – 也许你需要将它安装在不同的目录中。如果它给出了另一个错误,那么在尝试进一步操作之前,您应该修复一个安装问题。如果你得到一个格式良好的环境和formcontent列表(在这个例子中,字段应该列为“addr”,值为“AtHome”和“name”,值为“Joe Blow”),cgi.py脚本已正确安装。如果你对你自己的脚本采用相同的程序,你现在应该能够调试它.

下一步可能是调用cgi模块test()来自你的脚本的函数:用单个语句

cgi.test()

替换它的主代码这应该产生与安装cgi.py文件本身相同的结果.

当一个普通的Python脚本引发一个未处理的异常(对于任何原因:模块名称中的拼写错误,无法打开的文件等),thePython解释器打印出一个很好的回溯并退出。虽然当你的CGI脚本引发异常时,Python解释器仍会执行此操作,但最常见的是回溯将最终出现在HTTP服务器的一个日志文件中,或者完全被搁置.

幸运的是,一旦你设法得到你的脚本执行some代码,您可以使用cgitb模块轻松地将回溯发送到Web浏览器。如果您还没有这样做,只需添加以下行:

import cgitbcgitb.enable()

to你的脚本的顶部。然后尝试再次运行它;当一个问题发生时,你应该看到一个详细的报告,可能会显示出崩溃的原因.

如果您怀疑导入cgitb模块时可能存在问题,可以使用更强大的方法(仅使用内置模块):

import syssys.stderr = sys.stdoutprint("Content-Type: text/plain")print()...your code here...

这依赖于Python解释器打印回溯。输出的内容类型设置为纯文本,禁用所有HTML处理。如果您的脚本有效,原始HTML将由您的客户显示。如果它引发了一个异常,很可能在打印完前两行后,将显示一个追溯。因为没有HTML解释,跟踪将是可读的

常见的问题和解决方案

  • 大多数HTTP服务器缓冲CGI脚本的输出,直到脚本完成。这意味着在脚本运行时无法在客户端显示器上显示进度报告.
  • 检查上面的安装说明.
  • 检查HTTP服务器的日志文件。(tail -f logfile在另一个窗口中可能有用!)
  • 首先检查脚本是否存在语法错误,通过执行类似python script.py.
  • 的操作如果脚本没有任何语法错误,请尝试将import cgitb;cgitb.enable()添加到脚本的顶部.
  • 调用外部程序时,请确保找到它们。通常,这意味着使用绝对路径名称 – PATH通常不会在CGI脚本中设置为非常有用的值.
  • 在读取或写入外部文件时,请确保它们可以由用户标识读取或写入您的CGI脚本将在其下运行:这通常是运行Web服务器的用户标识,或者是某个Web服务器的显式特定数据suexec功能
  • 不要试图给出一个CGI脚本是一个set-uid模式。这对大多数系统都不起作用,也是一种安全责任.

Footnotes

[1] 请注意,HTML规范的某些最新版本确实说明了应该提供字段值的顺序,但是知道是否从符合标准的浏览器,甚至从浏览器接收到请求,这是错误的和错误的.

评论被关闭。