该模块定义了许多由Python编写的CGI脚本使用的实用程序。
21.2.1。介绍
HTTP服务器调用CGI脚本,通常用于处理通过HTML <FORM>或<ISINDEX>元素提交的用户输入。
通常,CGI脚本存在于服务器的特殊cgi-bin目录中。 HTTP服务器在脚本的shell环境中放置有关请求的各种信息(例如客户端的主机名,请求的URL,查询字符串和许多其他好东西),执行脚本,并发送脚本的输出回到客户端。
脚本的输入也连接到客户端,有时表单数据以这种方式读取;在其他时候,表单数据通过URL的部分字符串传递。此模块旨在处理不同的情况,并为Python脚本提供更简单的界面。它还提供了许多帮助调试脚本的实用程序,最新增加的是对表单中文件上载的支持(如果您的浏览器支持它)。
CGI脚本的输出应由两部分组成,用空行分隔。第一部分包含许多标题,告诉客户端正在跟踪哪种数据。生成最小标题部分的Python代码如下所示:
print("Content-Type: text/html") # HTML is following print() # 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!")
21.2.2。使用cgi模块
首先编写import cgi。
编写新脚本时,请考虑添加以下行:
导入cgitb
cgitb.enable()
这将激活一个特殊的异常处理程序,如果发生任何错误,它将在Web浏览器中显示详细的报告。如果你不想向你的脚本用户展示程序的内容,你可以将报告保存到文件中,代码如下:
导入cgitb
cgitb.enable(display = 0,logdir =“/ path / to / logdir”)
在脚本开发期间使用此功能非常有用。 cgitb生成的报告提供的信息可以为您节省大量时间来查找错误。您可以随后在测试脚本后删除cgitb行,并确信它可以正常工作。
要获取提交的表单数据,请使用FieldStorage类。如果表单包含非ASCII字符,请使用encoding关键字参数设置为为文档定义的编码值。它通常包含在HTML文档的HEAD部分的META标记中,或者包含在Content-Type标题中。这将从标准输入或环境中读取表单内容(取决于根据CGI标准设置的各种环境变量的值)。由于它可能消耗标准输入,因此应仅实例化一次。
FieldStorage实例可以像Python字典一样编入索引。 它允许使用in运算符进行成员资格测试,并且还支持标准字典方法keys()和内置函数len()。 包含空字符串的表单字段将被忽略,并且不会出现在字典中; 要保留此类值,请在创建FieldStorage实例时为可选的keep_blank_values关键字参数提供true值。
例如,以下代码(假定已打印Content-Type标题和空行)检查字段名称和addr是否都设置为非空字符串:
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.") return print("<p>name:", form["name"].value) print("<p>addr:", form["addr"].value) ...further form processing here...
这里通过form [key]访问的字段本身就是FieldStorage(或MiniFieldStorage,取决于表单编码)的实例。 实例的value属性产生字段的字符串值。 getvalue()方法直接返回此字符串值; 它还接受一个可选的第二个参数作为默认值,如果请求的密钥不存在则返回。
如果提交的表单数据包含多个具有相同名称的字段,则由form [key]检索的对象不是FieldStorage或MiniFieldStorage实例,而是此类实例的列表。 同样,在这种情况下,form.getvalue(key)将返回一个字符串列表。 如果您期望这种可能性(当您的HTML表单包含多个具有相同名称的字段时),请使用getlist()方法,该方法始终返回值列表(这样您就不需要特殊情况下的单项案例)。 例如,此代码连接任意数量的用户名字段,以逗号分隔:
value = form.getlist("username") usernames = ",".join(value)
如果字段表示上载的文件,则通过value属性或getvalue()方法访问该值会将内存中的整个文件作为字节读取。 这可能不是你想要的。 您可以通过测试filename属性或file属性来测试上载的文件。 然后,您可以在文件属性自动关闭之前读取数据,作为FieldStorage实例的垃圾收集的一部分(read()和readline()方法将返回字节):
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类型)。在这种情况下,它可以像顶层表单对象一样递归迭代。
当表单以搊ld格式提交时(作为查询字符串或类型为application / x-www-form-urlencoded的单个数据部分),这些项实际上将是MiniFieldStorage类的实例。在这种情况下,list,file和filename属性始终为None。
通过POST提交的同时具有查询字符串的表单将包含FieldStorage和MiniFieldStorage项目。
在3.4版中更改:在创建FieldStorage实例的垃圾回收时,文件属性自动关闭。
在3.5版中更改:向FieldStorage类添加了对上下文管理协议的支持
21.2.3。 更高级别的界面
上一节介绍了如何使用FieldStorage类读取CGI表单数据。 本节描述了一个更高级别的接口,该接口已添加到此类中,以允许用户以更易读和更直观的方式执行此操作。 该接口不会使前面部分中描述的技术过时 - 例如,它们对于有效地处理文件上载仍然有用。
界面由两个简单的方法组成。 使用这些方法,您可以以通用方式处理表单数据,而无需担心是否只在一个名称下发布了一个或多个值。
在上一节中,您学习了在您希望用户在一个名称下发布多个值时随时编写以下代码:
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异常。
因此,读取表单数据值的适当方法是始终使用检查所获取的值是单个值还是值列表的代码。这很烦人,导致脚本可读性降低。
更方便的方法是使用此更高级别接口提供的方法getfirst()和getlist()。
FieldStorage.getfirst(name,default = None)
此方法始终只返回与表单字段名称关联的一个值。如果在此名称下发布了更多值,则该方法仅返回第一个值。请注意,收到这些值的顺序可能因浏览器而异,因此不应计算在内。 [1]如果不存在此类表单字段或值,则该方法返回由可选参数default指定的值。如果未指定,此参数默认为“无”。
FieldStorage.getlist(名称)
此方法始终返回与表单字段名称关联的值列表。如果name不存在此类表单字段或值,则该方法返回空列表。如果只存在一个这样的值,它返回一个由一个项组成的列表。
使用这些方法,您可以编写漂亮的紧凑代码:
import cgi form = cgi.FieldStorage() user = form.getfirst("user", "").upper() # This way it's safe. for item in form.getlist("item"): do_something(item)
21.2.4。功能
如果您想要更多控制,或者如果您想在其他情况下使用此模块中实现的某些算法,这些非常有用。
cgi.parse(fp = None,environ = os.environ,keep_blank_values = False,strict_parsing = False)
在环境中或从文件中解析查询(文件默认为sys.stdin)。 keep_blank_values和strict_parsing参数传递给urllib.parse.parse_qs()不变。
cgi.parse_qs(qs,keep_blank_values = False,strict_parsing = False)
此模块中不推荐使用此功能。请改用urllib.parse.parse_qs()。它仅在向后兼容时保留在此处。
cgi.parse_qsl(qs,keep_blank_values = False,strict_parsing = False)
此模块中不推荐使用此功能。请改用urllib.parse.parse_qsl()。它仅在向后兼容时保留在此处。
cgi.parse_multipart(fp,pdict)
解析multipart / form-data类型的输入(用于文件上传)。参数是输入文件的fp和包含Content-Type标头中其他参数的字典的pdict。
返回字典就像urllib.parse.parse_qs()键是字段名称,每个值都是该字段的值列表。这很容易使用,但如果您希望上传兆字节not不是很好 在这种情况下,使用FieldStorage类而不是更灵活。
请注意,这不会解析嵌套的多部分 使用FieldStorage。
cgi.parse_header(串)
将MIME标头(例如Content-Type)解析为主值和参数字典。
cgi.test()
强大的测试CGI脚本,可用作主程序。写入最小的HTTP标头并格式化以HTML格式提供给脚本的所有信息。
cgi.print_environ()
以HTML格式化shell环境。
cgi.print_form(形式)的
以HTML格式化表单。
cgi.print_directory()
以HTML格式化当前目录。
cgi.print_environ_usage()
在HTML中打印有用(由CGI使用)环境变量的列表。
cgi.escape(s,quote = False)
将字符串s中的字符'&','<'和'>'转换为HTML安全序列。 如果需要在HTML中显示可能包含此类字符的文本,请使用此选项。 如果可选标记引用为true,则引号字符(“)也会被翻译;这有助于包含在由双引号分隔的HTML属性值中,如<a href="...">中所示。请注意单引号 从未翻译过。
从版本3.2开始不推荐使用:此函数不安全,因为默认情况下quote为false,因此不推荐使用。 请改用html.escape()。
21.2.5。关心安全
有一个重要的规则:如果你调用一个外部程序(通过os.system()或os.popen()函数或其他具有类似功能的函数),请确保你不要将从客户端收到的任意字符串传递给贝壳。这是一个众所周知的安全漏洞,Web上任何地方的聪明黑客都可以利用一个容易上当的CGI脚本来调用任意shell命令。甚至部分URL或字段名称也不可信,因为请求不必来自您的表单!
为了安全起见,如果必须将从表单获取的字符串传递给shell命令,则应确保该字符串仅包含字母数字字符,短划线,下划线和句点。
21.2.6。在Unix系统上安装CGI脚本
阅读HTTP服务器的文档,并与本地系统管理员联系,找到应安装CGI脚本的目录;通常这是在服务器树中的目录cgi-bin中。
确保您的脚本可由搊thers 读取并执行; Unix文件模式应该是0o755八进制(使用chmod 0755文件名)。确保脚本的第一行包含#!从第1列开始,后跟Python解释器的路径名,例如:
!/usr/local/bin/python
确保Python解释器存在并且可由搊thers 执行。
确保脚本需要读取或写入的任何文件分别是可读或可写的搊thers 他们的模式应为0o644表示可读,0o666表示可写。这是因为出于安全原因,HTTP服务器以“用户”的形式执行您的脚本,没有任何特殊权限。它只能读取(写入,执行)每个人都可以读取(写入,执行)的文件。执行时的当前目录也不同(通常是服务器的cgi-bin目录),环境变量集也与登录时的不同。特别是,不要指望shell的搜索可执行文件的路径(PATH)或Python模块搜索路径(PYTHONPATH)设置为任何有趣的东西。
如果需要从不在Python的默认模块搜索路径上的目录中加载模块,则可以在导入其他模块之前更改脚本中的路径。例如:
import sys sys.path.insert(0, "/usr/home/joe/lib/python") sys.path.insert(0, "/usr/local/lib/python")
(这样,将首先搜索最后插入的目录!)
非Unix系统的说明会有所不同; 检查你的HTTP服务器的文档(它通常有一个关于CGI脚本的部分)。
21.2.7。测试您的CGI脚本
遗憾的是,当您从命令行尝试CGI脚本时,它通常不会运行,并且从服务器运行时,从命令行完美运行的脚本可能会神秘地失败。你应该仍然从命令行测试你的脚本有一个原因:如果它包含语法错误,Python解释器根本不会执行它,并且HTTP服务器很可能会向客户端发送一个神秘的错误。
假设您的脚本没有语法错误,但它不起作用,您别无选择,只能阅读下一节。
21.2.8。调试CGI脚本
首先,检查琐碎的安装错误 阅读上面有关安装CGI脚本的部分可以为您节省大量时间。如果您想知道是否已正确理解安装过程,请尝试将此模块文件(cgi.py)的副本安装为CGI脚本。当作为脚本调用时,该文件将以HTML格式转储其环境和表单内容。给它正确的模式等,并发送一个请求。如果它安装在标准的cgi-bin目录中,则应该可以通过在表单的浏览器中输入URL来向它发送请求:
http://yourhostname/cgi-bin/cgi.py?name=Joe+Blow&addr=At+Home
如果这给出404类型的错误,则服务器找不到脚本 可能需要将其安装在不同的目录中。 如果它给出了另一个错误,那么在尝试进一步操作之前,您应该修复一个安装问题。 如果你得到一个格式良好的环境和表单内容列表(在这个例子中,字段应该列为揳ddr ,值为 tHome 和“非ame ,值为揓oBlow ”),cgi.py脚本 已正确安装。 如果对自己的脚本执行相同的过程,则现在应该可以对其进行调试。
下一步可能是从脚本中调用cgi模块的test()函数:将其主代码替换为单个语句
cgi.test() 这应该产生与安装cgi.py文件本身相同的结果。
当普通的Python脚本引发一个未处理的异常时(无论出于何种原因:模块名称中的拼写错误,可以打开的文件等),Python解释器会打印出一个很好的回溯并退出。 虽然Python解释器仍然会在CGI脚本引发异常时执行此操作,但很可能回溯将最终出现在HTTP服务器的一个日志文件中,或者完全丢弃。
幸运的是,一旦您设法让脚本执行某些代码,您就可以使用cgitb模块轻松地将回溯发送到Web浏览器。 如果你还没有这样做,只需添加以下行:
import cgitb cgitb.enable()
到你的脚本的顶部。 然后尝试再次运行它; 当问题发生时,您应该看到一个详细的报告,可能会显示崩溃的原因。
如果您怀疑导入cgitb模块时可能存在问题,则可以使用更强大的方法(仅使用内置模块):
import sys sys.stderr = sys.stdout print("Content-Type: text/plain") print() ...your code here...
这依赖于Python解释器来打印回溯。 输出的内容类型设置为纯文本,禁用所有HTML处理。 如果您的脚本有效,则客户端将显示原始HTML。 如果它引发异常,很可能在打印前两行之后,将显示回溯。 由于没有进行HTML解释,因此回溯将是可读的。
21.2.9。常见问题和解决方案
大多数HTTP服务器缓冲CGI脚本的输出,直到脚本完成。这意味着在脚本运行时无法在客户端显示器上显示进度报告。
检查上面的安装说明。
检查HTTP服务器的日志文件。 (在单独的窗口中使用tail -f logfile可能很有用!)
通过执行python script.py之类的操作,首先检查脚本是否存在语法错误。
如果您的脚本没有任何语法错误,请尝试添加import cgitb; cgitb.enable()到脚本的顶部。
调用外部程序时,请确保找到它们。通常,这意味着使用绝对路径名 PATH通常不会在CGI脚本中设置为非常有用的值。
在读取或写入外部文件时,请确保它们可以由运行CGI脚本的用户标识读取或写入:这通常是运行Web服务器的用户标识,或者是Web服务器的一些明确指定的用户标识。 suexec功能。
不要试图给CGI脚本一个set-uid模式。这对大多数系统都不起作用,也是一种安全责任。
网友评论