Python CGI编程
通用网关接口(Common Gateway Interface,CGI)是一组定义了Web服务器和自定义脚本之间如何交换信息的标准。CGI规范目前由NCSA负责维护。
什么是CGI
- 通用网关接口(CGI)是一种用于外部网关程序与诸如HTTP服务器等信息服务器进行接口的标准。
-
当前版本为CGI/1.1,CGI/1.2正在进行中。
网络浏览
为了理解CGI的概念,让我们看一下当我们点击超链接浏览特定网页或URL时发生了什么。
- 您的浏览器与HTTP Web服务器联系,并要求URL,即文件名。
-
Web服务器解析URL并查找文件名。如果找到该文件,则将其发送回浏览器,否则将发送一个错误消息,指示您请求了一个错误的文件。
-
Web浏览器接收来自Web服务器的回应,并显示接收到的文件或错误消息。
但是,可以设置HTTP服务器,以便每当请求某个目录中的文件时,该文件将不会发送回来;相反,它将作为程序执行,并将该程序输出的内容发送回供浏览器显示。这个功能称为通用网关接口(CGI),程序称为CGI脚本。这些CGI程序可以是Python脚本、PERL脚本、Shell脚本、C或C++程序等。
CGI架构图
Web服务器支持和配置
在进行CGI编程之前,请确保您的Web服务器支持CGI并且已配置为处理CGI程序。所有要由HTTP服务器执行的CGI程序都保存在预配置的目录中。这个目录被称为CGI目录,按照惯例,它被命名为/var/www/cgi-bin。按照惯例,CGI文件的扩展名为 cgi ,但您也可以将文件保存为Python扩展名 .py 。
默认情况下,Linux服务器只配置为运行/var/www目录下cgi-bin目录中的脚本。如果您想指定任何其他目录来运行您的CGI脚本,请在httpd.conf文件中注释以下行:
<Directory "/var/www/cgi-bin">
AllowOverride None
Options ExecCGI
Order allow,deny
Allow from all
</Directory>
<Directory "/var/www/cgi-bin">
Options All
</Directory>
下面这行代码也需要添加到apache服务器中,以便将.py文件作为cgi脚本来处理。
AddHandler cgi-script .py
在这里,我们假设您的Web服务器已经正常运行,并且您能够运行任何其他的CGI程序,比如Perl或者Shell等。
第一个CGI程序
这里有一个简单的链接,链接到一个CGI脚本,命名为hello.py。该文件保存在/var/www/cgi-bin目录中,其内容如下。在运行您的CGI程序之前,请确保使用 chmod 755 hello.py UNIX命令更改文件的权限,使其可执行。
print ("Content-type:text/html\r\n\r\n")
print ('<html>')
print ('<head>')
print ('<title>Hello Word - First CGI Program</title>')
print ('</head>')
print ('<body>')
print ('<h2>Hello Word! This is my first CGI program</h2>')
print ('</body>')
print ('</html>')
注意 − 脚本的第一行必须是Python可执行文件的路径。它在Python程序中作为注释出现,但被称为shebang行。
在Linux中,它应该是#!/usr/bin/python3。
在Windows中,它应该是#!c:/python311/python.exd。
在浏览器中输入以下URL –
http://localhost/cgi-bin/hello.py
## Hello Word! This is my first CGI program
这个hello.py脚本是一个简单的Python脚本,它的输出写在STDOUT文件,即屏幕上。有一个重要的额外特性可用,即要打印的第一行 Content-type:text/html\r\n\r\n . 这一行将被发送回浏览器,并指定在浏览器屏幕上显示的内容类型。
到现在为止,您已经了解了CGI的基本概念,并且可以使用Python编写许多复杂的CGI程序。这个脚本还可以与任何其他外部系统交互,以交换信息,如关系数据库管理系统。
HTTP头
行 Content-type:text/html\r\n\r\n 是HTTP头的一部分,它被发送到浏览器以理解内容。所有的HTTP头都将在以下格式中:
HTTP Field Name: Field Content
For Example
Content-type: text/html\r\n\r\n
除了上述提到的重要HTTP头部信息,还有几个你在CGI编程中会经常使用的。
序号 | 头部与标签 |
---|---|
1 | Content-type: 定义返回文件的格式的MIME字符串。例如:Content-type:text/html |
2 | Expires:Date 信息变为无效的日期。浏览器根据此日期决定何时刷新页面。有效日期字符串格式为01 Jan 1998 12:00:00 GMT。 |
3 | Location:URL 返回的URL而不是请求的URL。您可以使用此字段将请求重定向到任意文件。 |
4 | Last-modified: Date 资源的最后修改日期。 |
5 | Content-length: N 返回数据的字节长度。浏览器使用此值报告文件的预计下载时间。 |
6 | Content-length: N 设置通过字符串传递的Cookie |
CGI 环境变量
所有CGI程序都可以访问以下环境变量。这些变量在编写任何CGI程序时起着重要作用。
Sr.No. | 变量名称及描述 |
---|---|
1 | CONTENT_TYPE 内容的数据类型。在客户端将附加内容发送到服务器时使用,例如文件上传。 |
2 | CONTENT_LENGTH 查询信息的长度。仅对POST请求可用。 |
3 | HTTP_COOKIE 以键值对的形式返回设置的Cookie。 |
4 | HTTP_USER_AGENT User-Agent请求头字段包含有关发起请求的用户代理的信息。这是Web浏览器的名称。 |
5 | PATH_INFO CGI脚本的路径。 |
6 | QUERY_STRING 使用GET方法请求时发送的URL编码信息。 |
7 | REMOTE_ADDR 发出请求的远程主机的IP地址。这在日志记录或身份验证中很有用。 |
8 | REMOTE_HOST 发出请求的主机的完全限定名称。如果此信息不可用,则可以使用REMOTE_ADDR获取IR地址。 |
9 | REQUEST_METHOD 用于发出请求的方法。最常见的方法是GET和POST。 |
10 | SCRIPT_FILENAME CGI脚本的完整路径。 |
11 | SCRIPT_NAME CGI脚本的名称。 |
12 | SERVER_NAME 服务器的主机名或IP地址 |
13 | SERVER_SOFTWARE 服务器正在运行的软件的名称和版本。 |
下面是一个小的CGI程序,用于列出所有的CGI变量。点击这个链接查看结果 获取环境变量
import os
print ("Content-type: text/html\r\n\r\n");
print ("<font size=+1>Environment</font><\br>");
for param in os.environ.keys():
print ("<b>%20s</b>: %s<\br>" % (param, os.environ[param]))
GET和POST方法
当你需要将一些信息从你的浏览器传递到Web服务器并最终传递给CGI程序时,你一定遇到过许多情况。浏览器经常使用两种方法将这些信息传递给Web服务器。这些方法是GET方法和POST方法。
使用GET方法传递信息
GET方法将编码后的用户信息附加到页面请求中。页面和编码的信息由问号字符分隔,如下所示:
http://www.test.com/cgi-bin/hello.py?key1=value1&key2=value2
- 默认方法是通过GET方法将信息从浏览器传递到Web服务器,它会生成一个长字符串出现在浏览器的位置框中。
-
如果要传递密码或其他敏感信息到服务器,请勿使用GET方法。
-
GET方法有大小限制:只能发送1024个字符的请求字符串。
-
GET方法使用QUERY_STRING标题发送信息,并且可以通过QUERY_STRING环境变量在CGI程序中访问。
您可以通过简单地将键值对与任何URL连接起来或者使用HTML<FORM>
标签来使用GET方法传递信息。
简单URL示例:GET方法
这是一个简单的URL示例,它使用GET方法将两个值传递给hello_get.py程序。
/cgi-bin/hello_get.py?first_name=Malhar&last_name=Lathkar
以下是用于处理Web浏览器输入的hello_get.py脚本。我们将使用cgi模块,它使得访问传递的信息非常简单。
# Import modules for CGI handling
import cgi, cgitb
# Create instance of FieldStorage
form = cgi.FieldStorage()
# Get data from fields
first_name = form.getvalue('first_name')
last_name = form.getvalue('last_name')
print ("Content-type:text/html")
print()
print ("<html>")
print ('<head>')
print ("<title>Hello - Second CGI Program</title>")
print ('</head>')
print ('<body>')
print ("<h2>Hello %s %s</h2>" % (first_name, last_name))
print ('</body>')
print ('</html>')
这将生成以下结果−
## Hello Malhar Lathkar
简单的表单示例:GET方法
这个例子使用HTML表单和提交按钮来传递两个值。我们使用同样的CGI脚本hello_get.py来处理这个输入。
<form action = "/cgi-bin/hello_get.py" method = "get">
First Name: <input type = "text" name = "first_name"> <br />
Last Name: <input type = "text" name = "last_name" />
<input type = "submit" value = "Submit" />
</form>
使用POST方法传递信息
传递信息到CGI程序的一种更可靠的方法是使用POST方法。它与GET方法以相同的方式打包信息,但不是将其作为URL中?后面的文本字符串发送,而是将其作为单独的消息发送。该消息以标准输入的形式进入CGI脚本。
下面是处理GET和POST方法的相同的hello_get.py脚本。
# Import modules for CGI handling
import cgi, cgitb
# Create instance of FieldStorage
form = cgi.FieldStorage()
# Get data from fields
first_name = form.getvalue('first_name')
last_name = form.getvalue('last_name')
print "Content-type:text/html\r\n\r\n"
print "<html>"
print "<head>"
print "<title>Hello - Second CGI Program</title>"
print "</head>"
print "<body>"
print "<h2>Hello %s %s</h2>" % (first_name, last_name)
print "</body>"
print "</html>"
让我们再次以上面的示例为例,使用HTML FORM和提交按钮传递两个值。我们使用相同的CGI脚本hello_get.py来处理这个输入。
<form action = "/cgi-bin/hello_get.py" method = "post">
First Name: <input type = "text" name = "first_name"><br />
Last Name: <input type = "text" name = "last_name" />
<input type = "submit" value = "Submit" />
</form>
将复选框数据传递给CGI程序
当需要选择多个选项时,使用复选框。
下面是一个带有两个复选框的示例HTML代码:
<form action = "/cgi-bin/checkbox.cgi" method = "POST" target = "_blank">
<input type = "checkbox" name = "maths" value = "on" /> Maths
<input type = "checkbox" name = "physics" value = "on" /> Physics
<input type = "submit" value = "Select Subject" />
</form>
下面是用于处理复选框按钮的checkbox.cgi脚本。
# Import modules for CGI handling
import cgi, cgitb
# Create instance of FieldStorage
form = cgi.FieldStorage()
# Get data from fields
if form.getvalue('maths'):
math_flag = "ON"
else:
math_flag = "OFF"
if form.getvalue('physics'):
physics_flag = "ON"
else:
physics_flag = "OFF"
print "Content-type:text/html\r\n\r\n"
print "<html>"
print "<head>"
print "<title>Checkbox - Third CGI Program</title>"
print "</head>"
print "<body>"
print "<h2> CheckBox Maths is : %s</h2>" % math_flag
print "<h2> CheckBox Physics is : %s</h2>" % physics_flag
print "</body>"
print "</html>"
将单选按钮数据传递给CGI程序
单选按钮用于只需选择一个选项的情况。
以下是一个带有两个单选按钮的表单的示例HTML代码-
<form action = "/cgi-bin/radiobutton.py" method = "post" target = "_blank">
<input type = "radio" name = "subject" value = "maths" /> Maths
<input type = "radio" name = "subject" value = "physics" /> Physics
<input type = "submit" value = "Select Subject" />
</form>
下面是radiobutton.py脚本来处理由web浏览器给出的单选按钮输入−
# Import modules for CGI handling
import cgi, cgitb
# Create instance of FieldStorage
form = cgi.FieldStorage()
# Get data from fields
if form.getvalue('subject'):
subject = form.getvalue('subject')
else:
subject = "Not set"
print "Content-type:text/html\r\n\r\n"
print "<html>"
print "<head>"
print "<title>Radio - Fourth CGI Program</title>"
print "</head>"
print "<body>"
print "<h2> Selected Subject is %s</h2>" % subject
print "</body>"
print "</html>"
将文本区数据传递给CGI程序
当需要传递多行文本给CGI程序时,使用TEXTAREA元素。
这是一个带有TEXTAREA框的表单的HTML代码示例-
<form action = "/cgi-bin/textarea.py" method = "post" target = "_blank">
<textarea name = "textcontent" cols = "40" rows = "4">
Type your text here...
</textarea>
<input type = "submit" value = "Submit" />
</form>
以下是textarea.cgi脚本用于处理Web浏览器输入−
# Import modules for CGI handling
import cgi, cgitb
# Create instance of FieldStorage
form = cgi.FieldStorage()
# Get data from fields
if form.getvalue('textcontent'):
text_content = form.getvalue('textcontent')
else:
text_content = "Not entered"
print "Content-type:text/html\r\n\r\n"
print "<html>"
print "<head>";
print "<title>Text Area - Fifth CGI Program</title>"
print "</head>"
print "<body>"
print "<h2> Entered Text Content is %s</h2>" % text_content
print "</body>"
将下拉列表框数据传递给CGI程序
当我们有多个选项可用,但只会选择一个或两个选项时,可以使用下拉列表框。
下面是一个带有一个下拉列表框的表单的示例HTML代码:
<form action = "/cgi-bin/dropdown.py" method = "post" target = "_blank">
<select name = "dropdown">
<option value = "Maths" selected>Maths</option>
<option value = "Physics">Physics</option>
</select>
<input type = "submit" value = "Submit"/>
</form>
下面的代码的结果是以下表格 – 数学 物理 下面是dropdown.py脚本来处理网页浏览器提供的输入。
# Import modules for CGI handling
import cgi, cgitb
# Create instance of FieldStorage
form = cgi.FieldStorage()
# Get data from fields
if form.getvalue('dropdown'):
subject = form.getvalue('dropdown')
else:
subject = "Not entered"
print "Content-type:text/html\r\n\r\n"
print "<html>"
print "<head>"
print "<title>Dropdown Box - Sixth CGI Program</title>"
print "</head>"
print "<body>"
print "<h2> Selected Subject is %s</h2>" % subject
print "</body>"
print "</html>"
在CGI中使用Cookies
HTTP协议是一个无状态协议。对于商业网站来说,需要在不同的页面之间保持会话信息。例如,一个用户注册在完成多个页面后结束。如何在所有网页之间保持用户的会话信息?
在许多情况下,使用cookies是记住和跟踪偏好、购买、佣金和其他提供更好的访客体验或站点统计所需的信息最有效的方法。
它是如何工作的
您的服务器会将一些数据以cookie的形式发送给访问者的浏览器。浏览器可能会接受cookie。如果接受了,它会以纯文本形式记录在访问者的硬盘上。现在,当访问者在您网站的另一个页面上到达时,可以检索到该cookie。一旦检索到,您的服务器就知道/记住了以前存储的内容。
cookies是由5个可变长度字段组成的纯文本数据记录:
- 过期时间(Expires) - cookie将会过期的日期。如果为空,则cookie将在访问者关闭浏览器时过期。
-
域名(Domain) - 您的站点的域名。
-
路径(Path) - 设置cookie的目录或网页的路径。如果您想从任何目录或页面检索cookie,则此字段可以为空。
-
安全(Secure) - 如果该字段包含单词”secure”,则只能使用安全服务器检索cookie。如果该字段为空,则没有此类限制。
-
名称=值(Name = Value) - cookie以键值对的形式设置和检索。
设置Cookies
向浏览器发送cookie非常简单。这些cookie在Content-type字段之前,随HTTP头一起发送。假设您想将UserID和Password设置为cookie。设置cookie的方式如下:
print "Set-Cookie:UserID = XYZ;\r\n"
print "Set-Cookie:Password = XYZ123;\r\n"
print "Set-Cookie:Expires = Tuesday, 31-Dec-2007 23:12:40 GMT;\r\n"
print "Set-Cookie:Domain = www.tutorialspoint.com;\r\n"
print "Set-Cookie:Path = /perl;\n"
print "Content-type:text/html\r\n\r\n"
...........Rest of the HTML Content....
从这个例子中,你必须已经了解到如何设置cookie。我们使用 Set-Cookie HTTP header来设置cookie。
可选设置cookie的属性如Expires、Domain和Path。值得注意的是,在发送魔术行 “Content-type:text/html\r\n\r\n 之前设置cookie。
检索Cookie
检索所有设置的cookie非常容易。cookie存储在CGI环境变量HTTP_COOKIE中,形式如下:
key1 = value1;key2 = value2;key3 = value3....
以下是一个检索cookie的示例。
# Import modules for CGI handling
from os import environ
import cgi, cgitb
if environ.has_key('HTTP_COOKIE'):
for cookie in map(strip, split(environ['HTTP_COOKIE'], ';')):
(key, value ) = split(cookie, '=');
if key == "UserID":
user_id = value
if key == "Password":
password = value
print "User ID = %s" % user_id
print "Password = %s" % password
通过以上脚本设置的cookie,将产生以下结果 –
User ID = XYZ
Password = XYZ123
文件上传示例
要上传文件,HTML表单必须将enctype属性设置为 multipart/form-data 。带有文件类型的input标签会创建一个“浏览”按钮。
<html>
<body>
<form enctype = "multipart/form-data" action = "save_file.py" method = "post">
<p>File: <input type = "file" name = "filename" /></p>
<p><input type = "submit" value = "Upload" /></p>
</form>
</body>
</html>
上述示例已被故意禁用,以防止人们将文件上传到我们的服务器上,但您可以尝试将上述代码用于您的服务器。
这是处理文件上传的脚本 save_file.py -
import cgi, os
import cgitb; cgitb.enable()
form = cgi.FieldStorage()
# Get filename here.
fileitem = form['filename']
# Test if the file was uploaded
if fileitem.filename:
# strip leading path from file name to avoid
# directory traversal attacks
fn = os.path.basename(fileitem.filename)
open('/tmp/' + fn, 'wb').write(fileitem.file.read())
message = 'The file "' + fn + '" was uploaded successfully'
else:
message = 'No file was uploaded'
print """\
Content-Type: text/html\n
<html>
<body>
<p>%s</p>
</body>
</html>
""" % (message,)
如果您在Unix / Linux上运行上述脚本,则需要注意将文件分隔符替换为以下内容,否则在Windows机器上,上述的open()语句应该能正常工作。
fn = os.path.basename(fileitem.filename.replace("\\", "/" ))
如何弹出“文件下载”对话框
有时,您希望提供一个选项,用户可以通过单击链接来弹出一个“文件下载”对话框,而不是显示实际内容。这非常容易实现,可以通过HTTP头部来实现。这个HTTP头部与前一部分提到的头部不同。
例如,如果您希望从给定的链接下载一个名为 FileName 的文件,则其语法如下:
# HTTP Header
print " **Content-Type:** application/octet-stream; name = \"FileName\"\r\n";
print " **Content-Disposition:** attachment; filename = \"FileName\"\r\n\n";
# Actual File Content will go here.
fo = open("foo.txt", "rb")
str = fo.read();
print str
# Close opend file
fo.close()