高級CGI
mulitipart 表單提交和文件上傳
. CGI 特別指出只允許兩種表單編碼:“application/x-www-form-urlencoded”和“multipart/form-dat”。且前者是默認的,因此前者不需要特別指出,但是後者需要明確給出編碼:
<FORM enctype="multipart/form-data" ...>
. 表單提交的時候可以使用任意編碼,但是在上傳文件的時候只能使用multipart編碼,通過使用輸入類型爲文件的方式來完成:
<INPUT type=file name=...>
. 這個指令顯示一個空的文本框,同時旁邊有個按鈕,可以通過該按鈕瀏覽文件目錄結構
多值字段
. 常見的處理多值字段的情況是有一系列的複選框,在提交表單的時候,數據以鍵值對的方式傳遞給服務器,當提交的不止一個複選框的時候,就會出現多個值對應一個鍵。在這種情況下,cgi模塊會建立一個包含這類實例的列表,可以遍歷該列表獲得所有值。
cookie
. 可以將其看做web站點服務器要求保存在客戶端上的二進制數據。
從一個頁面跳轉到另一個頁面的方式可以是通過GET請求中的鍵值對來實現;也可以通過使用隱藏的表單字段,就像是一個標識符,找得到這個標識符就顯示這個頁面。這些變量和值由服務器託管,因爲這些信息必須嵌入到新生成的頁面中並返回給客戶端。
另一個可以保持多個頁面連續瀏覽的方式是在客戶端保存這些數據,這就是引入cookie的原因。服務器向客戶端發送一個請求來保存 cookie,而不必用在返回的 Web 頁面中嵌入數據的方法來保持數據。cookie 連接到最初服務器的主域上(這樣一個服務器就不能設置或者覆蓋其他 Web 站點中的 cookie),並且有一定的存儲期限(因此瀏覽器不會堆滿 cookie)。
cookie 和文件上傳
. 接下來的例子展示了cookie和文件上傳,當第一次登錄網頁,沒有緩存,表單中的輸入框都是空的,生成過結果頁面後,腳本設置了cookie,緩存在瀏覽器中,當再次回到表單頁面的時候,會自動填入相應的值:
import cgi
from urllib.parse import quote,unquote
import os
import io
class AdvCGI(object):
header = 'Content-Type: text/html\n\n'
url = "/cgi-bin/advcgi.py"
formhtml = '''<html><head><tltle>
Advanced CGI demon</title></head>
<body><h2>Advanced CGI Demo Form:</h2>
<form method = post action = "%s" enctype = "multipart/form-data">
<h3>my cookie setting</h3>
<li><code><b>CPPuser = %s</b></code>
<h3>enter cookie value:<br>
<input name = cookie value = %s> (<i>optional</i>)</h3>
<h3>enter your name:<br>
<input name = person value = %s> (<i>required</i>)</h3>
<h3>what languages can you program in? (<i>at least one required</i>)</h3>
%s
<h3>enter file to upload <small>(max size 4K)</samll></h3>
<input type = file name = upfile value = "%s" size = 45>
<p><input type = submit>
</form></body></html>
'''
langset = ('Python','C','C++','Ruby','Java','PHP')
langitem = '<input type = checkbox name = lang value = "%s" %s>%s\n'
# 從客戶端(瀏覽器)讀取cookie,這個方法只會被showform方法調用
def getcookie(self):
if 'HTTP_COOKIE' in os.environ:
#使用HTTP_COOKIE這個環境變量訪問cookie
cookies = [x.strip() for x in os.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:
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 showForm(self):
self.getcookie()
#放置複選框
langStr = []
for eachlang in AdvCGI.langset:
langStr.append(AdvCGI.langitem % (eachlang,'CHECKED' if eachlang in self.langs else '',eachlang))
if not ('user' in self.cookies and self.cookies['user']):
cookStatus = '<i>(cookie has not been set yet)</i>'
userCook = ''
else:
userCook = cookStatus = self.cookies['user']
print('%s%s' % (AdvCGI.header,AdvCGI.formhtml % (AdvCGI.url,cookStatus,
userCook,self.who,
''.join(langStr),self.fn)))
#用於錯誤反饋
errhtml = '''html><head><tltle>
Advanced CGI demon</title></head>
<body><h3>Error</h3>
<b>%s</b><p>
<form><input type = button value = Back
ONCLICK = "window.history.back()"></form>
</body></html>
'''
def showErr(self):
print(AdvCGI.header + AdvCGI.errhtml % self.err)
#用於生成結果信息頁
reshtml = '''<html><head><tltle>
Advanced CGI demon</title></head>
<h3>your cookies value is: <b>%s</b></h3>
<h3>your name is: <b>%s</b></h3>
<h3>your can program in the fallowing languages:</h3>
<ul>%s</ul>
<h3>your uoloaded file...<br>
name:<i>%s</i><br>
contents:</h3>
<pre>%s</pre>
Click <a href = "%s"><b>here</b></a>to return to form
</body></html>
'''
#這裏設置了cookies,還加上了CPP前綴,和get那裏是對應的
def setcookie(self):
for eachcookie in self.cookies.keys():
print('Set-Cookie: CPP%s=%s;path=/' % \
(eachcookie,quote(self.cookies[eachcookie])))
def doResult(self):
MAXBYTES = 4096
langlist = ''.join('<li>%s<br>' % eachlang for eachlang in self.langs)
filedata = self.fp.read(MAXBYTES)
if len(filedata) == MAXBYTES and f.read():
filedata = '%s%s' % (filedata,'... <b><i>(file truncated due to size)</i></b>')
self.fp.close()
if filedata == '':
filedata = '<b><i>(file not given or upload error)</i></b>'
filename = self.fn
if not ('user' in self.cookies and self.cookies['user']):
cookStatus = '<i>(cookie has not been set yet)</i>'
userCook = ''
else:
userCook = cookStatus = self.cookies['user']
self.cookies['info'] = ':'.join((self.who,','.join(self.langs),filename))
self.setcookie()
print('%s%s' % (AdvCGI.header,AdvCGI.reshtml % (cookStatus,self.who,langlist,
filename,filedata,AdvCGI.url)))
#決定跳轉到哪一個頁面
def go(self):
self.cookies = {}
self.err = ''
form = cgi.FieldStorage()
if not form.keys():
self.showForm()
return
if 'person' in form:
self.who = form['person'].value.strip().title()
if self.who == '':
self.err = 'your name is required.(blank)'
else:
self.err = 'your name is required.(miss)'
self.cookies['user'] = unquote(form['cookie'].value.strip()) if 'cookie' in form else ''
if 'lang' in form:
langData = form['lang']
#isinstance() 函數來判斷一個對象是否是一個已知的類型
if isinstance(langData,list):
self.langs = [eachLang.value for eachLang in langData]
else:
self.langs = [langData.value]
else:
self.err = 'at least one language required'
if 'upfile' in form:
upfile = form['upfile']
#or 表示從左到右掃描,返回第一個爲真的表達式值,無真值則返回最後一個表達式值。
self.fn = upfile.filename or ''
if upfile.file:
self.fp = upfile.file
else:
self.fp = io.StringIO('(no data)')
else:
self.fp = io.StringIO('(no file)')
self.fn = ''
if not self.err:
self.doResult()
else:
self.showErr()
if __name__ == '__main__':
page = AdvCGI()
page.go()