7月第3周博客,
最近在做一个项目,里面用到了网站的开发,而CGI作为网页开发的基础,要有有一个深入的理解。读了一些好的例子,特别拿出来与大家分享。不过技术的学习也确实是一个循序渐进的过程,很多事情急不得,要慢慢来,大家一起努力。
下面贴出网页交互的全部代码,最后再给大家足部的见解:
#!/usr/bin/env python
from cgi import FieldStorage
from os import environ
from StringIO import StringIO
from urllib import quote, unquote
class AdvCGI(object):
header = 'Content-Type: text/html\n\n'
url = '/cgi-bin/advcgi.py'
formhtml = '''
Advanced CGI Demo
Advanced CGI Demo Form
'''
langSet = ('Python', 'Ruby', 'Java', 'C++', 'PHP', 'C', 'JavaScript')
langItem = ' %s\n'
def getCPPCookies(self): # reads cookies from client
if 'HTTP_COOKIE' in environ:
cookies = [x.strip() for x in environ['HTTP_COOKIE'].split(';')]
for eachCookie in cookies:
if len(eachCookie) > 6 and eachCookie[:3] == 'CPP':
tag = eachCookie[3:7]
try:
self.cookies[tag] = eval(unquote(eachCookie[8:]))
except (NameError, SyntaxError):
self.cookies[tag] = unquote(eachCookie[8:])
if 'info' not in self.cookies:
self.cookies['info'] = ''
if 'user' not in self.cookies:
self.cookies['user'] = ''
else:
self.cookies['info'] = self.cookies['user'] = ''
if self.cookies['info'] != '':
self.who, langStr, self.fn = self.cookies['info'].split(':')
self.langs = langStr.split(',')
else:
self.who = self.fn = ''
self.langs = ['Python']
def getUserCookie(self):
# see if user cookie set up yet
if not ('user' in self.cookies and self.cookies['user']):
cookStatus = '(cookie has not been set yet)'
userCook = ''
else:
userCook = cookStatus = self.cookies['user']
return userCook
def showForm(self):
self.getCPPCookies()
# put together language checkboxes
langStr = []
for eachLang in AdvCGI.langSet:
langStr.append(AdvCGI.langItem % (eachLang,
' CHECKED' if eachLang in self.langs else '',
eachLang))
userCook = self.getUserCookie()
print '%s%s' % (AdvCGI.header, AdvCGI.formhtml % (
AdvCGI.url, cookStatus, userCook, self.who,
''.join(langStr), self.fn))
errhtml = '''
Advanced CGI Demo
ERROR
%s
'''
def showError(self):
print '%s%s' % (AdvCGI.header, AdvCGI.errhtml % (self.error))
reshtml = '''
Advanced CGI Demo
Your Uploaded Data
Your cookie value is: %s
Your name is: %s
You can program in the following languages:
%s
Your uploaded file...
Name: %s
Contents:
%s
Click here to return to form.
'''
def setCPPCookies(self):
for eachCookie in self.cookies:
print 'Set-Cookie: CPP%s=%s; path=/' % (
eachCookie, quote(self.cookies[eachCookie]))
def doResults(self):
MAXBYTES = 4096
langList = ''.join(
'%s
' % eachLang for eachLang in self.langs)
filedata = self.fp.read(MAXBYTES)
if len(filedata) == MAXBYTES and f.read():
filedata = '%s%s' % (filedata,
'... (file truncated due to size)')
self.fp.close()
if filedata == '':
filedata = '(file not given or upload error)'
filename = self.fn
userCook = self.getUserCookie()
# set cookies
self.cookies['info'] = ':'.join(
(self.who, ','.join(self.langs), filename))
self.setCPPCookies()
print '%s%s' % (AdvCGI.header, AdvCGI.reshtml % (
cookStatus, self.who, langList,
filename, filedata, AdvCGI.url))
def go(self):
self.cookies = {}
self.error = ''
form = FieldStorage()
if not form.keys():
self.showForm()
return
if 'person' in form:
self.who = form['person'].value.strip().title()
if self.who == '':
self.error = 'Your name is required. (blank)'
else:
self.error = 'Your name is required. (missing)'
self.cookies['user'] = unquote(form['cookie'].value.strip()) if 'cookie' in form else ''
if 'lang' in form:
langData = form['lang']
if isinstance(langData, list):
self.langs = [eachLang.value for eachLang in langData]
else:
self.langs = [langData.value]
else:
self.error = 'At least one language required.'
if 'upfile' in form:
upfile = form['upfile']
self.fn = upfile.filename or ''
if upfile.file:
self.fp = upfile.file
else:
self.fp = StringIO('(no data)')
else:
self.fp = StringIO('(no file)')
self.fn = ''
if not self.error:
self.doResults()
else:
self.showError()
if __name__ == '__main__':
page = AdvCGI()
page.go()
这段代码根据实际测试过的,可用!下面对代码进行简单的分析:
1、在声明Ad vCGI类之后,创建了header和url变量,在显示不同的页面时,会用到这些变量。下面是HTML静态文本的表单。
class AdvCGI(object):
header = 'Content-Type: text/html\n\n'
url = '/cgi-bin/advcgi.py'
formhtml = '''<HTML><HEAD><TITLE>
Advanced CGI DemoTITLE>HEAD>
<BODY><H2>Advanced CGI Demo FormH2>
<FORM METHOD=post ACTION="%s" ENCTYPE="multipart/form-data">
<H3>My Cookie SettingH3>
<LI> <CODE><B>CPPuser = %sB>CODE>
<H3>Enter cookie value<BR>
<INPUT NAME=cookie value="%s"> (<I>optionalI>)H3>
<H3>Enter your name<BR>
<INPUT NAME=person VALUE="%s"> (<I>requiredI>)H3>
<H3>What languages can you program in?
(<I>at least one requiredI>)H3>
%s
<H3>Enter file to upload <SMALL>(max size 4K)SMALL>H3>
<INPUT TYPE=file NAME=upfile VALUE="%s" SIZE=45>
<P><INPUT TYPE=submit>
FORM>BODY>HTML>'''
2、这个例子用到了cookie。下面还有setCPPCookies()方法,应用程序会调用这个方法来发送cookie(从Web 服务器)到浏览器,并存储在浏览器中。getCPPCookies()所做的刚好相反。当浏览器对应用进行连续调用时,这个方法将相同的cookie 通过HTTP 头发送回服务器。在应用执行时,应用可以通过HTTP_COOKIE 环境变量
访问到这些值。
这个方法解析cookie,特别是寻找以CPP 开头的字符串。在这个例子中,只查找名值。如果这个cookie 丢失,就会给它指定一个空字符串。getCPPCookies()方法
只会被showForm()调用。为“CPPuser”和“CPPinfo”的cookie。键“user”和“info”在第38 行提取为标签。跳过了索引7 处的等号。在第39~42 行去除了索引8 处的值并进行计算,计算结果保存到Python对象中。异常处理程序查看cookie 负载,对于非法的Python 对象,仅仅是保存相应的字符串值。如果这个cookie 丢失,就会给它指定一个空字符串(第43~48 行)。getCPPCookies()方法只会被showForm()调用。
在这个简单的例子中自行解析cookie,但对于复杂的应用,一般使用Cookie 模块(在Python 3 中重命名为http.cookies)来完成这个任务。与之类似,如果在编写Web 客户端,需要管理浏览器存储的所有cookie(一个cookie jar),并与Web 服务器通信,可能会需要用到cookielib 模块(在Python 3 中重命名http.cookiejar)。
3、showForm()和doReuslts()都会调checkUserCookie()方法,用来检查是否设置了用户提供的cookie 值。表单和结果的HTML 模板都会显示这个值。
showForm()方法唯一的目的是将表单显示给用户。这个方法需要getCPPCookies()从之前的请求中(如果有)获取cookie,并适当地调整表单的格式。
4、这一块代码会创建结果页面。setCPPCookies()方法请求客户端为应用程序存储cookie,doResults()方法将所有数据放在一起发送回客户端。go()方法会调用doResults(),用于处理主要任务,以输出数据。在这个方法的第一部分(第109~119 行),用于处理用户数据,即选择的编程语言集(至少需要选择一个,详见go()方法)、上传的文件以及用户提供的cookie 值,后两者都是可选的。doResults()的最后一步(第128~135 行)将所有数据打包到单个“CPPinfo”cookie 中,为后面做准备,并根据数据渲染结果模板。
6、这段代码首先实例化一个AdvCGI 页面对象,接着调用其中的go()方法开始工作。go()方法用于读取所有将要到达的数据并确定显示哪个页面。如果没有给出名字或选定语言,则会显示错误页面。如果没有收到任何输入数据,将调用showForm()方法来输出表单;否则,将调用doResults()方法来显示结果页面。通过设置self.error 变量可以创建错误页面,这样做有两个目的。一是可以将错误原因设置在字符串里,二是可以作为一个标记表明有错误发生。如果该变量不为空,用户会被导向到错误页面。person 字段是一个键值对,处理方法(第145~150 行)和先前看到的一样。但在收集语言信息时(第153~160 行)需要一点技巧,原因是必须检查一个(Mini)FieldStorage 实例或一个包含该实例的列表。这里将使用熟悉的isinstance()内置函数来达到目的。最终,会获得单个或多个语言名的列表,具体依赖于用户的选择情况。
如果使用cookie 来保管数据,就可以避免使用任何类型的CGI 字段。在本章之前的示例中,将这些值作为CGI 变量传递。而现在只使用cookie。读者会注意到获取这些数据的代码没有调用CGI 处理,这意味着数据并非来自FieldStorage 对象。Web 客户端的每次请求都会发送相应的数据,其中的值从cookie 中获得(包括用户的选择结果和用来填充后续表单的已有信息)。
因为showResults()方法从用户那里取得了新的输入值,所以该方法负责设置cookie,例如,通过调用setCPPCookies()。而showForm()必须读出cookie 中的值才能用表单页显示用户的当前选项。这通过getCPPCookies()的调用来实现。
最后,来看看文件的上传处理(第162~171 行)。不论一个文件实际上是否已经上传,FieldStorage 都会从file 属性中获得一个文件句柄。在第171 行,如果没有指明文件名,就把它设置成空字符串。还有一个更好的做法,可以访问文件指针(即file 属性),并且可以每次只读一行或者其他更慢一些的处理方法。
在这个例子里,文件上传只是用户提交过程的一部分,所以只是简单地把文件指针传给doResults()函数,从文件中抽取数据。由于受到空间限制,doResults()将只显示文件的前 4KB内容(第112 行),完全没有必要显示一个4GB 的完整二进制文件。