Наконец, файл commonhtml.py, представленный в примере 16.14, служит «центральным вокзалом» этого приложения — его программный код используется почти всеми остальными файлами системы. По большей части он самодокументирован, а все основные идеи, заложенные в нем, мы уже изучали ранее, когда знакомились со сценариями CGI, использующими его.
Однако я ничего не сказал о поддержке от лад ки в нем. Обратите внимание, что этот модуль связывает поток вывода sys.stderr с потоком sys. stdout, чтобы заставить текст сообщений Python об ошибках выводиться в броузере клиента (напомню, что информация о необработанных исключениях выводится в sys.stderr). Иногда это действует в PyMailCGI, но не всегда — текст ошибки появляется на веб-странице, только если функция page_header уже вывела преамбулу ответа. Если нужно увидеть все сообщения об ошибках, вызывайте page_header (или выводите строки Content-type: вручную) перед тем, как выполнять какую-либо обработку.
В этом модуле также определены функции, сбрасывающие в броузер массу информации окружения CGI (dumpstatepage) и служащие обертками для функций, выводящих сообщения о состоянии, чтобы их вывод не попадал в поток HTML (runsilent). Дополнительно версия 3.0 пытается также учитывать тот факт, что вызов встроенной функции print может терпеть неудачу в Python 3.1 при выводе некоторых типов текста Юникода (например, при выводе заголовков, содержащих национальные символы, не входящие в диапазон набора ASCII), принудительно устанавливая двоичный режим и отправляя байты в поток вывода (print). Я предоставляю читателю самому исследовать магию, оставшуюся в программном коде, представленном в примере 16.14. А мы пойдем дальше; нам предстоит читать, обращаться за справками и повторно использовать.
Пример 16.14. PP4E\Internet\Web\PyMailCgi\cgi-bin\commonhtml.py
#!/usr/bin/python
############################################################################ Генерирует стандартный заголовок страницы, список и нижний колонтитул; в этом файле скрыты детали, относящиеся к созданию разметки HTML; выводимый здесь текст поступает клиенту через сокеты, создавая части новой веб-страницы в веб-броузере; каждая строка выводится отдельным вызовом print, использует urllib для экранирования параметров в ссылках URL, создаваемых из словаря, а для вставки их в скрытые поля форм HTML — cgi.escape; некоторые инструменты отсюда могут использоваться вне pymailcgi; можно было бы возвращать генерируемую разметку HTML вместо вывода в поток, для включения в другие страницы; можно было бы выстроить структуру в виде единого сценария CGI, который получает и проверяет имя следующей операции в скрытом поле формы; предупреждение: система действует, но была написана в основном во время 2-часовой задержки в чикагском аэропорту O‘Hare: некоторые компоненты стоило бы улучшить и оптимизировать;
############################################################################
import cgi, urllib.parse, sys, os
# 3.0: в Python 3.1 наблюдаются проблемы при выводе некоторых
# декодированных строк str в поток вывода stdout
import builtins
bstdout = open(sys.stdout.fileno(), ‘wb’)
def print(*args, end=’\n’):
try:
builtins.print(*args, end=end)
sys.stdout.flush()
except:
for arg in args:
bstdout.write(str(arg).encode(‘utf-8’))
if end: bstdout.write(end.encode(‘utf-8’)) bstdout.flush()
sys.stderr = sys.stdout # выводить сообщения об ошибках в броузер
from externs import mailconfig # из пакета, находящегося на сервере from externs import mailtools # для анализа и декодирования заголовков
parser = mailtools.MailParser() # один парсер на процесс в этом модуле
# корневой каталог cgi-сценариев
#urlroot = ‘http://starship.python.net/~lutz/PyMailCgi/’
#urlroot = ‘http://localhost:8000/cgi-bin/’
urlroot = » # использовать минимальные, относительные пути
def pageheader(app=’PyMailCGI’, color=’#FFFFFF’, kind=’main’, info=»): print(‘Content-type: text/html\n’)
print(‘<html><head><title>%s: %s page (PP4E)</title></head>’
% (app, kind))
print(‘<body bgcolor="%s"><h1>%s %s</h1><hr>’ % (color, app, (info or kind)))
def pagefooter(root=’pymailcgi.html’):
print(‘</p><hr><a href="http://www.python.org">’)
print(‘<img src="../PythonPoweredSmall.gif" ‘)
print(‘align=left alt="[Python Logo]" border=0 hspace=15></a>’) print(‘<a href="../%s">Back to root page</a>’ % root) print(‘</body></html>’)
def formatlink(cgiurl, parmdict):
создает ссылку запроса "%url?key=val&key=val" из словаря;
экранирует str() всех ключей и значений,
подставляя %xx, замещает ‘ ‘ на +
обратите внимание, что адреса URL экранируются иначе, чем HTML (cgi.escape) parmtext = urllib.parse.urlencode(parmdict) # вызовет parse.quote_plus
return ‘%s?%s’ % (cgiurl, parmtext) # всю работу делает urllib
def pagelistsimple(linklist): # выводит простой нумерованный список print(‘<ol>’)
for (text, cgiurl, parmdict) in linklist:
link = formatlink(cgiurl, parmdict)
text = cgi.escape(text)
print(‘<li><a href="%s">\n %s</a>’ % (link, text))
print(‘</ol>’)
def pagelisttable(linklist): # выводит список в виде таблицы
print(‘<p><table border>’) # для верности выполняет экранирование for (text, cgiurl, parmdict) in linklist:
link = formatlink(cgiurl, parmdict)
text = cgi.escape(text)
print(‘<tr><th><a href="%s">View</a><td>\n %s’ % (link, text)) print(‘</table>’)
def listpage(linkslist, kind=’selection list’):
pageheader(kind=kind)
pagelisttable(linkslist) # [(‘text’, ‘cgiurl’, {‘parm’:’value’})] pagefooter()
def messagearea(headers, text, extra=»): # extra — для readonly addrhdrs = (‘From’, ‘To’, ‘Cc’, ‘Bcc’) # декодировать только имена print(‘<table border cellpadding=3>’)
for hdr in (‘From’, ‘To’, ‘Cc’, ‘Subject’):
rawhdr = headers.get(hdr, ‘?’)
if hdr not in addrhdrs:
dechdr = parser.decodeHeader(rawhdr) # 3.0: декодировать
# для отобр.
else: # закодированы при отправке
dechdr = parser.decodeAddrHeader(rawhdr) # только имена
# в адресах
val = cgi.escape(dechdr, quote=1)
print(‘<tr><th align=right>%s:’ % hdr)
print(‘ <td><input type=text ‘)
print(‘ name=%s value="%s" %s size=60>’ % (hdr, val, extra)) print(‘<tr><th align=right>Text:’)
print(‘<td><textarea name=text cols=80 rows=10 %s>’ % extra)
print(‘%s\n</textarea></table>’ % (cgi.escape(text) or ‘?’)) # если
# имеются </>s
def viewattachmentlinks(partnames):
создает гиперссылки для сохраняемых локально файлов частей/вложений, открытие файлов будет выполнять веб-броузер
предполагается наличие единственного пользователя, файлы сохраняются только для одного сообщения print(‘<hr><table border cellpadding=3><tr><th>Parts:’)
for filename in partnames:
basename = os.path.basename(filename)
filename = filename.replace(‘\\’, ‘/’) # грубый прием для Windows print(‘<td><a href=../%s>%s</a>’ % (filename, basename))
print(‘</table><hr>’)
def viewpage(msgnum, headers, text, form, parts=[]):
при выборе сообщения в списке и выполнении операции просмотра (вызывается щелчком на созданной ссылке) очень тонкое место: к этому моменту пароль был закодирован в ссылке, в формате URL, и затем декодировался при анализе входных данных; здесь он встроен в разметку HTML, поэтому мы применяем cgi.escape; обычно в скрытых полях появляются непечатные символы, но в IE и NS как-то работает:
в url: ?user=lutz&mnum=3&pswd=%8cg%c2P%1e%f0%5b%c5J%1c%f3&…
в html: <input type=hidden name=pswd value="…непечатные..">
можно бы пропустить поле HTML через urllib.parse.quote,
но это потребовало бы вызывать urllib.parse.unquote в следующем сценарии (что не мешает передавать данные в URL, а не форме); можно вернуться к цифровому формату строк из secret.py pageheader(kind=’View‘)
user, pswd, site = list(map(cgi.escape, getstandardpopfields(form))) print(‘<form method=post action="%sonViewPageAction.py">’ % urlroot) print(‘<input type=hidden name=mnum value="%s">’ % msgnum)
print(‘<input type=hidden name=user value="%s">’ % user) # из стран.|иг1 print(‘<input type=hidden name=site value="%s">’ % site) # для удаления print(‘<input type=hidden name=pswd value="%s">’ % pswd) # кодиров. пароль messagearea(headers, text, ‘readonly’)
if parts: viewattachmentlinks(parts)
# onViewPageAction.quotetext требует передачи даты в странице print(‘<input type=hidden name=Date value="%s">’
% headers.get(‘Date’,’?’))
print(‘<table><tr><th align=right>Action:’)
print(‘<td><select name=action>’)
print(‘ <option>Reply<option>Forward<option>Delete</select>’)
print(‘<input type=submit value="Next">’)
print(‘</table></form>’) # ‘сброс‘ здесь не требуется pagefooter()
def sendattachmentwidgets(maxattach=3):
print(‘<p><b>Attach:</b><br>’)
for i in range(1, maxattach+1):
print(‘<input size=80 type=file name=attach%d><br>’ % i) print(‘</p>’)
def editpage(kind, headers={}, text=»):
# вызывается при отправке, View+ebi6op+Reply, View+ebi6op+Fwd pageheader(kind=kind)
print(‘<p><form enctype="multipart/form-data" method=post’, end=’ ‘) print(‘action="%sonEditPageSend.py">’ % urlroot)
if mailconfig.mysignature:
text = ‘\n%s\n%s’ % (mailconfig.mysignature, text)
messagearea(headers, text)
sendattachmentwidgets()
print(‘<input type=submit value="Send">’)
print(‘<input type=reset value="Reset">’)
print(‘</form>’)
pagefooter()
def errorpage(message, stacktrace=True):
pageheader(kind=’Error’) # было sys.exc_type/exc_value
exc_type, exc_value, exc_tb = sys.exc_info()
print(‘<h2>Error Description</h2><p>’, message)
print(‘<h2>Python Exception</h2><p>’, cgi.escape(str(exc_type)))
print(‘<h2>Exception details</h2><p>’, cgi.escape(str(exc_value))) if stacktrace:
print(‘<h2>Exception traceback</h2><p><pre>’)
import traceback
traceback.print_tb(exc_tb, None, sys.stdout) print(‘</pre>’)
pagefooter()
def confirmationpage(kind):
pageheader(kind=’Confirmation’)
print(‘<h2>%s operation was successful</h2>’ % kind)
print(‘<p>Press the link below to return to the main page.</p>’) pagefooter()
def getfield(form, field, default=»):
# имитация метода get словаря
return (field in form and form[field].value) or default
def getstandardpopfields(form):
поля могут отсутствовать, быть пустыми или содержать значение, жестко определены в URL; по умолчанию используются настройки из mailconfig
return (getfield(form, ‘user’, mailconfig.popusername), getfield(form, ‘pswd’, ‘?’), getfield(form, ‘site’, mailconfig.popservername))
def getstandardsmtpfields(form):
return getfield(form, ‘site’, mailconfig.smtpservername)
def runsilent(func, args):
выполняет функцию, подавляя вывод в stdout
например: подавляет вывод из импортируемых инструментов, чтобы он не попал клиенту/броузеру class Silent:
def write(self, line): pass
save_stdout = sys.stdout
sys.stdout = Silent() # отправлять вывод в фиктивный объект,
try: # который имеет метод write
result = func(*args) # попытаться вернуть результат функции
finally: # и всегда восстанавливать stdout
sys.stdout = save_stdout
return result
def dumpstatepage(exhaustive=0)
для отладки: вызывается в начале сценария CGI
для создания новой страницы с информацией о состоянии CGI if exhaustive:
cgi.test() # вывести страницу с формой, окруж. и пр.
else:
pageheader(kind=’state dump’)
form = cgi.FieldStorage() # вывести только имена/значения
# полей формы
cgi.print_form(form)
pagefooter()
sys.exit()
def selftest(showastable=False): # создает фиктивную веб—страницу
links = [ # [(text, url, {parms})]
(‘text1’, urlroot + ‘page1.cgi’, {‘a’:1}),
(‘text2’, urlroot + ‘page1.cgi’, {‘a’:2, ‘b’:’3′}),
(‘text3’, urlroot + ‘page2.cgi’, {‘x’:’a b’, ‘y’:’a<b&c’, ‘z’:’?’}),
(‘te<>4’, urlroot + ‘page2.cgi’, {‘<x>’:», ‘y’:'<a>’, ‘z’:None})] pageheader(kind=’View’) if showastable:
pagelisttable(links)
else:
pagelistsimple(links)
pagefooter()
if __name__ == ‘__main__’: # когда запускается, а не импортируется
selftest(len(sys.argv) > 1) # разметка HTML выводится в stdout
Использованная литература:
Марк Лутц — Программирование на Python, 4-е издание, II том, 2011