执行之前的程序,然后在浏览器中打开 http://localhost:8051/?a=10&b=w&b=r 这样的url。
环境变量字典中保存了请求信息REQUEST_METHOD和QUERY_STRING。问号之后的内容即为此次请求字符串的值。可以写一个函数对它进行解析,或者直接使用CGI模块的parse_qs函数,它返回一个字典,其值为一个列表。
下面是一个简单的例子:
1 | #!/usr/bin/env python |
执行之前的程序,然后在浏览器中打开 http://localhost:8051/?a=10&b=w&b=r 这样的url。
环境变量字典中保存了请求信息REQUEST_METHOD和QUERY_STRING。问号之后的内容即为此次请求字符串的值。可以写一个函数对它进行解析,或者直接使用CGI模块的parse_qs函数,它返回一个字典,其值为一个列表。
下面是一个简单的例子:
1 | #!/usr/bin/env python |
将上一个例子的返回:
return [response_body]
改为:
return response_body
再次运行会发现速度变慢了。这是因此服务器对发送过来的字符串是按单个字节进行迭代的,所以最好对返回的字符串用一个可迭代对象包装一下。
如果返回的这个可迭代对象生成多个字符串,那么正文的长度即为这些字符串长度的总和。
接下来看一个例子:
1 | #! /usr/bin/env python |
环境变量字典包含了类似于CGI的变量,它是在每次请求时被服务器填充。
1 | #! /usr/bin/env python |
执行该脚本,然后在浏览器中打开http://localhost:8051/ 查看效果。
这个例子是将环境变量字典的值全部输出。
WSGI(Web Server Gateway Interface)并不是一个服务器,而是一个协议。最开始是用Python写的,现在很多语言都有了对应的实现。详细内容可以看这里: http://www.python.org/dev/peps/pep-3333/
WSGI应用程序接口是一个可调用的对象。它必须接收两个固定的参数:一个包含了类似CGI变量的字典;一个可调用的函数用于返回HTTP状态代码和数据头。
1 | # This is our application object. It could have any name, |
以上是一个应用程序的基本框架。由于没有服务器,因此这段代码目前还不能运行。
Session是位于服务器端的Cookie。它保存在服务器上的文件或者数据库中。每条session是由session id(SID)进行标识。
Cookie可以长久胡保存SID,直到Cookie过期。用这种方式更快更安全。但是得客户端的浏览器支持Cookie才行。
1 | #!/usr/bin/env python |
我们对服务器的时间进行哈希生成一个唯一的Session ID。
%s
SID = %s
这是将sid直接在url中传递。
%s
SID = %s
这是将sid放在表单中作为隐藏字段提交。
光有session id是不够的,还需要将内容保存到文件或者数据库中。这里可以使用shelve模块保存到文件。
session = shelve.open('/tmp/.session/sess_' + sid, writeback=True)
它打开文件并返回一个类似于字典的对象。
session['lastvisit'] = repr(time.time())
设置session的值。
lastvisit = session.get('lastvisit')
读取刚刚设置的值。
session.close()
最后操作完成之后要记得关闭文件。
接下来用一个例子展示下Cookie和Shelve共同使用。
#!/usr/bin/env python
import sha, time, Cookie, os, shelve
cookie = Cookie.SimpleCookie()
string_cookie = os.environ.get('HTTP_COOKIE')
if not string_cookie:
sid = sha.new(repr(time.time())).hexdigest()
cookie['sid'] = sid
message = 'New session'
else:
cookie.load(string_cookie)
sid = cookie['sid'].value
cookie['sid']['expires'] = 12 * 30 * 24 * 60 * 60
# The shelve module will persist the session data
# and expose it as a dictionary
session_dir = os.environ['DOCUMENT_ROOT'] + '/tmp/.session'
session = shelve.open(session_dir + '/sess_' + sid, writeback=True)
# Retrieve last visit time from the session
lastvisit = session.get('lastvisit')
if lastvisit:
message = 'Welcome back. Your last visit was at ' + \
time.asctime(time.gmtime(float(lastvisit)))
# Save the current time in the session
session['lastvisit'] = repr(time.time())
print """\
%s
Content-Type: text/html\n
<html><body>
<p>%s</p>
<p>SID = %s</p>
</body></html>
""" % (cookie, message, sid)
有两个与cookie相关的操作,设置cookie和读取cookie。
以下例子展示了cookie的设置。
1 | #!/usr/bin/env python |
这是在数据头中使用Set-Cookie进行的操作。
浏览器返回来的cookie存放于os.environ字典中,对应的字段名为’HTTP_COOKIE’。以下是一个例子:
1 | #!/usr/bin/env python |
使用SimpleCookie对象的load()方法对字符串进行解析。
可以使用subprocess.Popen或者os.popen4让cgi执行shell命令。
1 | #!/usr/bin/python |
注意:这只是一个例子,在生产环境中这么使用是非常的不安全。
可以使用Cookies和Session对用户进行认证以提高安全性。
cgi模块中有一个FieldStorage类可用于表单处理。
有一个HTML表单如下:
1 | <html><body> |
form1.py内容为:
#!/usr/bin/env python
import cgi
form = cgi.FieldStorage() # instantiate only once!
name = form.getfirst('name', 'empty')
# Avoid script injection escaping the user input
name = cgi.escape(name)
print """\
Content-Type: text/html\n
<html><body>
<p>The submitted name was "%s"</p>
</body></html>
""" % name
getfirst方法获取指定字段的第一个值,如果该字段不存在则为空。将表单的方法改为post它同样适用。
为了避免用户提交危险的内容,可以使用cgi.escape()方法对内容进行转换。
对于多个字段具有相同名字的可以使用getlist()方法,它返回一个列表包含了这些值。
<html><body>
<form method="post" action="/cgi-bin/form2.py">
Red<input type="checkbox" name="color" value="red">
Green<input type="checkbox" name="color" value="green">
<input type="submit" value="Submit">
</form>
</body></html>
form2.py内容如下:
#!/usr/bin/env python
import cgi
form = cgi.FieldStorage()
# getlist() returns a list containing the
# values of the fields with the given name
colors = form.getlist('color')
print "Content-Type: text/html\n"
print '<html><body>'
print 'The colors list:', colors
for color in colors:
print '<p>', cgi.escape(color), '</p>'
print '</body></html>'
<html><body>
<form enctype="multipart/form-data" action="/cgi-bin/form3.py" method="post">
<p>File: <input type="file" name="file"></p>
<p><input type="submit" value="Upload"></p>
</form>
</body></html>
getfirst()和getlist()都只能获取文件的内容。想获取文件名需要使用FieldStorage。
form3.py内容如下:
#!/usr/bin/env python
import cgi, os
import cgitb; cgitb.enable()
try: # Windows needs stdio set for binary mode.
import msvcrt
msvcrt.setmode (0, os.O_BINARY) # stdin = 0
msvcrt.setmode (1, os.O_BINARY) # stdout = 1
except ImportError:
pass
form = cgi.FieldStorage()
# A nested FieldStorage instance holds the file
fileitem = form['file']
# 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('files/' + 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,)
在处理大文件时如果内存不足,可以使用生成器将文件分成小片。
可将之前的脚本改写如下:
#!/usr/bin/env python
import cgi, os
import cgitb; cgitb.enable()
try: # Windows needs stdio set for binary mode.
import msvcrt
msvcrt.setmode (0, os.O_BINARY) # stdin = 0
msvcrt.setmode (1, os.O_BINARY) # stdout = 1
except ImportError:
pass
form = cgi.FieldStorage()
# Generator to buffer file chunks
def fbuffer(f, chunk_size=10000):
while True:
chunk = f.read(chunk_size)
if not chunk: break
yield chunk
# A nested FieldStorage instance holds the file
fileitem = form['file']
# 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)
f = open('files/' + fn, 'wb', 10000)
# Read the file in chunks
for chunk in fbuffer(fileitem.file):
f.write(chunk)
f.close()
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,)
有时数据头出错是很难定位的,除非有权限访问服务器日志。
好在Python有cgitb模块,可以将异常的堆栈信息放在正文中,作为HTML输出。
以下是一个简单的例子:
1 | #!/usr/bin/env python |
也可以使用handler()方法进行捕获异常处理。
1 | #!/usr/bin/env python |
还有一种更直接的方法,将数据头设为”text/plain”并把标准错误输出设置到标准输出。
1 | print "Content-Type: text/plain" |
注意:这些只是用于在开发阶段,在生产环境中要把它禁用。以免异常信息被攻击者利用。
CGI(Common Gateway Interface),通用网关接口的简称。它是客户端和服务器程序进行数据传输的一种标准。
一个CGI程序可以使用任何语言编写,通常它是放在Web服务器(如Apache)目录下的cgi-bin目录里。
接下来看一个简单的例子。
1 | #!/usr/bin/env python |
脚本程序的第一行指定了python解释器的路径。在你系统中它也可能为:
1 | #!/usr/bin/python |
1 | print "Content-Type: text/html" |
脚本必须输出一个HTTP的头,它由一条或者多条消息构成,然后再一个空行。空行是必需的,它意味着头的结束。
这里我们想要把输出作为HTML解释,因此指定Content-Type为 text/html。
这里也可以写成:
1 | print "Content-Type: text/html\n" |
保存以上脚本,并添加执行权限。然后在浏览器中访问执行该脚本,应该可以看到”Hello World”这几个字。