Шаг 6: добавляем веб-интерфейс

shag 6 dobavlyaem veb interfejs «Программирование на Python»: краткий очерк

Графические интерфейсы проще в использовании, чем командная строка, и зачастую это все, что нам требуется, чтобы упростить доступ к данным. Однако, обеспечивая доступ к нашей базе данных из Интернета, мы открываем ее для более широкого круга пользователей. Любой, кто обладает выходом в Интернет и имеет броузер, сможет получить доступ к данным, независимо от того, где он находится и какой операционной системой пользуется. Годится любое устройство, от рабочей станции до сотового телефона. Кроме того, при наличии веб-интерфейса требуется только веб-броузер — чтобы получить доступ к данным, не нужно устанавливать Python, за исключением установки на сервере. Традиционные веб-интерфейсы обычно уступают в удобстве и скорости графическим интерфейсам, однако их переносимость может иметь решающее значение.

Как будет показано далее в этой книге, существуют различные способы реализации интерактивных веб-страниц, обеспечивающих доступ к нашим данным. Для решения простых задач, как наша, CGI-сценариев, выполняющихся на стороне сервера, будет более чем достаточно. Поскольку это, пожалуй, самый простой подход и к тому же являющийся основой для более совершенных технологий, разработка CGI-сценариев хорошо подходит для изучения основ разработки веб-приложений.

Для создания более сложных приложений существует богатое многообразие инструментальных средств и фреймворков для Python — включая Django, TurboGears, Google App Engine, pylons, web2py, Zope, Plone, Twisted, CherryPy, Webware, mod_python, PSP и Quixote, — упрощающих решение типичных задач и предоставляющих инструменты, которые в противном случае может потребоваться реализовать самостоятельно. Новейшие технологии, такие как Flex, Silverlight и pyjamas (версия фреймворка Google Web Toolkit, перенесенная на язык Python, и компилятор с языка Python на язык JavaScript), предлагают дополнительные пути создания интерактивных и динамических пользовательских интерфейсов веб-страниц и открывают дверь к использованию Python в разработке полнофункциональных интернет-приложений (Rich Internet Applications, RIA).

Я еще вернусь к этим инструментам позднее, а пока не будем усложнять задачу и напишем CGI-сценарий.

Основы CGI

Писать CGI-сценарии на языке Python достаточно просто, если уже имеется опыт работы с формами HTML, адресами URL и имеется некоторое представление об особенностях модели клиент/сервер Интернета (все эти темы мы будем обсуждать далее в этой книге). Вы можете знать или не знать все подробности, но в основном модель взаимодействий вам должна быть знакома.

В двух словах: пользователь приходит на веб-сайт и получает форму HTML для заполнения в броузере. После отправки формы на сервере запускается сценарий, указанный либо в самой форме, либо в адресе URL сервера, который в ответ воспроизводит другую страницу HTML. В такой схеме взаимодействий данные обычно проходят через три программы: от броузера клиента они передаются веб-серверу, затем CGIсценарию и возвращаются обратно броузеру. Это естественная модель взаимодействия с базами данных, которой мы будем следовать, — пользователь будет отправлять серверу ключ в базе данных и в ответ будет получать соответствующую страницу с записью.

Далее в книге мы подробнее познакомимся с основами CGI, а пока, в качестве первого примера, создадим простой интерактивный сценарий, который будет запрашивать имя пользователя и возвращать его обратно веб-броузеру. Первая страница в этом примере — это просто форма ввода, реализованная в виде разметки HTML, как показано в примере 1.30. Этот файл HTML хранится на веб-сервере и передается вебброузеру, выполняющемуся на компьютере клиента.

Пример 1.30. PP4E\Preview\cgi101.html

<html>

<title>Interactive Page</title>

<body>

<form method=POST action=”cgi-bin/cgi101.py”>

<P><B>Enter your name:</B>

<P><input type=text name=user>

<P><input type=submit>

</form>

</body></html>

Обратите внимание, что в атрибуте action этой формы определяется сценарий, который будет обрабатывать ее. Эта страница будет возвращаться при обращении по ее адресу URL. После получения клиентом эта форма в окне броузера будет выглядеть, как показано на рис. 1.10 (в данном случае, в Internet Explorer).

Рис. 1.10. Форма ввода на странице cgi101.html

 

После отправки формы клиентом веб-сервер получит запрос (подробнее о веб-сервере чуть ниже) и запустит CGI-сценарий на языке Python, представленный в примере 1.31. Как и файл HTML, этот сценарий также находится на веб-сервере — он выполняется на стороне сервера, обрабатывает введенные данные и воспроизводит ответ, который отправляется броузеру на стороне клиента. Сценарий использует модуль cgi, чтобы извлечь данные из формы и вставить их в поток разметки HTML ответа с соответствующим экранированием. Модуль cgi обеспечивает интерфейс к полям ввода формы, отправленной броузером, напоминающий интерфейс словаря, и передачу разметки HTML, которую выводит сценарий, броузеру для отображения в виде следующей страницы. В мире CGI поток стандартного вывода соединен с клиентом посредством сокета.

Пример 1.31. PP4E\Preview\cgi-bin\cgi101.py

#!/usr/bin/python

import cgi

form = cgi.FieldStorage() # парсинг данных формы

print(‘Content-type: text/html\n’) # httpзаголовок плюс пустая строка

print(‘<title>Reply Page</title>’) # htmlразметка ответа if not ‘user’ in form:

print(‘<h1>Who are you?</h1>’)

else:

print(‘<h1>Hello <i>%s</i>!</h1>’ % cgi.escape(form[‘user’].value))

И если все пройдет как надо, мы получим в ответ страницу, изображенную на рис. 1.11, которая, по сути, просто выводит данные, введенные на странице с формой ввода. Страница на этом рисунке была воспроизведена разметкой HTML, которую вывел CGI-сценарий на стороне сервера. В данном случае имя пользователя сначала было передано со стороны клиента на сервер, а затем обратно, возможно преодолев по пути многие сети и километры. Безусловно, это очень небольшой веб-сайт, но одни и те же принципы действуют для любого сайта, выводит он просто введенные данные или является полноценным сайтом электронного бизнеса.

Рис. 1.11. Страница ответа, воспроизведенная сценарием cgi101.py в ответ на получение формы ввода

 

Если у вас возникли проблемы с организацией этих взаимодействий в Unix-подобной системе, попробуйте изменить путь к интерпретатору Python в строке #! , находящейся в начале сценария, и дать файлу сценария право на выполнение командой chmod, хотя во многом это зависит от вашего веб-сервера (подробнее о сервере мы поговорим чуть ниже).

Обратите также внимание, что CGI-сценарий в примере 1.31 выводит не полную разметку HTML: здесь отсутствуют теги <html> и <body>, которые можно увидеть в примере 1.30. Строго говоря, эти теги следовало бы вывести, но веб-броузеры спокойно воспринимают их отсутствие, да и цель этой книги состоит вовсе не в том, чтобы обучить вас формальному языку разметки HTML, — более подробную информацию об HTML ищите в других источниках.

Графические и веб-интерфейсы

Прежде чем двинуться дальше, имеет смысл потратить минуту, чтобы сравнить этот простой пример CGI-сценария с простым графическим интерфейсом, реализованным в примере 1.28 и изображенным на рис. 1.6. В данном случае сценарии выполняются на стороне сервера и генерируют разметку HTML, которая отображается веб-броузером. В реализации графического интерфейса мы вызываем функции, конструирующие интерфейс на экране и реагирующие на события, которые выполняются в рамках единого процесса и на одном и том же компьютере. Графический интерфейс состоит из нескольких программных уровней, но целиком реализован в единственной программе. Реализация на основе CGI, напротив, имеет распределенную архитектуру — сервер, броузер и, возможно, сам CGI-сценарий выполняются как отдельные программы, обычно взаимодействующие друг с другом по сети.

Учитывая эти различия, автономная модель реализации графического интерфейса выглядит более простой и непосредственной: в ней отсутствует промежуточный сервер; чтобы получить ответ, не требуется вызывать новую программу; не требуется генерировать разметку HTML и в нашем распоряжении имеется вся мощь инструмента создания графического интерфейса. С другой стороны, веб-интерфейс может отображаться в любом броузере, на любом компьютере и для его работы необходимо установить Python только на сервере.

Еще больше ситуацию запутывает то обстоятельство, что графический интерфейс также может использовать сетевые инструменты из стандартной библиотеки Python для получения и отображения данных, хранящихся на удаленном сервере (то есть так же, как это делают броузеры). Некоторые новейшие фреймворки, такие как Flex, Silverlight и pyjamas, предоставляют инструменты реализации более полнофункциональных пользовательских интерфейсов в веб-страницах (полнофункциональных интернет-приложений, упоминавшихся выше), хотя и ценой сложности программного кода и большего количества программных уровней. Далее в книге мы еще вернемся к обсуждению различий между графическим интерфейсом и CGI, потому что на сегодняшний день это и есть основной выбор. А теперь рассмотрим несколько практических проблем, связанных с работой механизма CGI, прежде чем применить его к нашей базе данных.

Запуск веб-сервера

Для запуска CGI-сценариев нам потребуется веб-сервер, который будет обслуживать наши страницы HTML и запускать сценарии на языке Python по запросам. Сервер является необходимым промежуточным звеном между броузером и CGI-сценарием. Если у вас нет учетной записи на компьютере, где уже установлен такой веб-сервер, вам придется запустить собственный веб-сервер. Мы могли бы настроить полноценный веб-сервер промышленного уровня, такой как свободно распространяемый веб-сервер Apache (в котором, кстати, можно настроить поддержку Python с помощью расширения mod_python). Однако для данной главы я написал на языке Python собственный простой веб-сервер, программный код которого приводится в примере 1.32.

Мы еще вернемся к инструментам, использованным в этом примере, далее в книге. Тем не менее замечу, что в стандартной библиотеке Python уже имеется реализация некоторых типов сетевых серверов, благодаря чему мы можем реализовать CGI-совместимый и переносимый веб-сервер, написав всего 8 строк программного кода (точнее, 16, если учесть комментарии и пустые строки).

Далее в этой книге мы увидим, насколько просто создать собственный сетевой сервер, используя низкоуровневые функции для работы с сокетами в Python, однако в стандартной библиотеке уже имеются реализации многих наиболее распространенных типов серверов. Модуль socketserver, например, поддерживает многопоточные и ветвящиеся версии серверов TCP и UDP. Еще большее количество реализаций можно найти в сторонних системах, таких как Twisted. Модули из стандартной библиотеки, использованные в примере 1.32, предоставляют все, что необходимо для обслуживания нашего веб-содержимого.

Пример 1.32. PP4E\Preview\webserver.py

Реализация веб-сервера на языке Python, способная запускать серверные CGI-сценарии на языке Python; обслуживает файлы и сценарии в текущем рабочем каталоге; сценарии на языке Python должны находиться в каталоге webdir\cgibin или webdir\htbin;

import os, sys

from http.server import HTTPServer, CGIHTTPRequestHandler

webdir = ‘.’ # место, где находятся файлы html и подкаталог cgibin

port = 80 # по умолчанию http://localhost/, иначе используйте

# http://localhost:xxxx/

os.chdir(webdir) # перейти в корневой каталог HTML

srvraddr = (“”, port) # имя хоста и номер порта

srvrobj = HTTPServer(srvraddr, CGIHTTPRequestHandler)

srvrobj.serve_forever() # запустить как бесконечный фоновый процесс

Классы, используемые сценарием, предполагают, что обслуживаемые файлы HTML находятся в текущем рабочем каталоге, а запускаемые CGI-сценарии находятся в подкаталоге cgibin или htbin. Как следует из имени файла в примере 1.31, для сценариев мы будем использовать подкаталог cgibin. Некоторые веб-серверы определяют CGI-сценарии по расширению в именах файлов, однако мы будем считать CGIсценариями все файлы, находящиеся в определенном каталоге.

Чтобы запустить веб-сервер, достаточно запустить этот сценарий (из командной строки, щелчком на ярлыке или иным способом). Он будет выполняться бесконечно, ожидая запросы от броузеров и других клиентов. Сервер ожидает запросы, направляемые на компьютер, где он выполняется, прослушивая стандартный порт HTTP с номером 80. Чтобы использовать этот сценарий для обслуживания других веб-сайтов, необходимо либо запустить его из другого каталога, содержащего файлы HTML и подкаталог cgibin со сценариями CGI, либо изменить значение переменной webdir, записав в нее имя корневого каталога сайта (сценарий автоматически перейдет в этот каталог и будет обслуживать файлы, находящиеся в нем).

Но где в киберпространстве фактически выполняется сценарий сервера? Если посмотреть внимательнее, на рисунках в предыдущем разделе можно заметить, что в адресной строке броузера (в верхней части окна, сразу после последовательности символов http://) всегда используется имя сервера localhost. Чтобы не усложнять, я запустил веб-сервер на том же компьютере, где запускается веб-броузер, а это означает, что сервер будет иметь имя «localhost» (и соответствующий IP-адрес «127.0.0.1»). То есть клиент и сервер — это один и тот же компьютер: клиент (вебброузер) и сервер (веб-сервер) — это просто разные процессы, одновременно выполняющиеся на одном и том же компьютере.

Хотя этот веб-сервер не может использоваться в промышленных целях, тем не менее он отлично подходит для тестирования CGI-сценариев — вы можете разрабатывать их на том же самом компьютере без необходимости перемещать программный код на удаленный сервер после каждого изменения. Просто запустите этот сценарий из каталога, где находятся файлы HTML и подкаталог cgibin с CGI-сценариями, и затем вводите в броузере адрес http://loc.alh.ost/, чтобы получить доступ к своим HTML-страницам и сценариям. Ниже приводится вывод, полученный от сценария веб-сервера в окне консоли в ОС Windows, который был запущен на том же компьютере, что и веб-броузер, из каталога, где находятся файлы HTML:

\PP4E\Preview> python webserver.py

mark-VAIO — — [28/Jan/2010 18:34:01] “GET /cgi101.html HTTP/1.1” 200 —

mark-VAIO — — [28/Jan/2010 18:34:12] “POST /cgi-bin/cgi101.py HTTP/1.1” 200 — mark-VAIO — — [28/Jan/2010 18:34:12] command: C:\Python31\python.exe -u C:\ Users\mark\Stuff\Books\4E\PP4E\dev\Examples\PP4E\Preview\cgi-bin\cgi101.py “” mark-VAIO — — [28/Jan/2010 18:34:13] CGI script exited OK

mark-VAIO — — [28/Jan/2010 18:35:25] “GET /cgi-bin/cgi101.py?user=Sue+Smith HTTP/1.1” 200 —

mark-VAIO — — [28/Jan/2010 18:35:25] command: C:\Python31\python.exe -u C:\ Users\mark\Stuff\Books\4E\PP4E\dev\Examples\PP4E\Preview\cgi-bin\cgi101.py mark-VAIO — — [28/Jan/2010 18:35:26] CGI script exited OK

Здесь следует сделать одно замечание: на некоторых платформах, чтобы запустить сервер, прослушивающий порт по умолчанию с номером 80, вам могут потребоваться привилегии администратора, поэтому узнайте, как в вашей системе запустить такой сервер, или попробуйте использовать порт с другим номером. Чтобы задействовать порт с другим номером, измените значение переменной port в сценарии и указывайте его явно в адресной строке броузера (например, http://localhost:8888/). Подробнее об этом соглашении будет рассказываться далее в книге.

Чтобы запустить этот сервер на удаленном компьютере, выгрузите файлы HTML и подкаталог с CGI-сценариями на удаленный компьютер, запустите там этот сценарий, а в адресной строке броузера вместо имени «localhost» используйте доменное имя или IP-адрес удаленного компьютера (например, http://www.myserver.com/). При использовании удаленного сервера все взаимодействия будут протекать, как показано здесь, но при этом запросы и ответы будут передаваться не между приложениями на одном и том же компьютере, а автоматически будут направляться через сетевые соединения.

Чтобы покопаться в реализации серверных классов нашего веб-сервера, обращайтесь к файлам с исходными текстами в стандартной библиотеке Python (C:\Python31\Lib для версии Python 3.1). Одно из основных преимуществ открытых систем, таких как Python, состоит в том, что мы всегда можем заглянуть «под капот». В главе 15 мы расширим пример 1.32 возможностью указывать имя каталога и номер порта из командной строки.

Использование строки запроса и модуля urllib

В простом CGI-сценарии, представленном выше, мы запускали сценарий на языке Python, заполняя и отправляя форму, содержащую имя сценария. На практике серверные CGI-сценарии могут вызываться разными способами — либо за счет отправки формы, как было показано выше, либо за счет отправки серверу строки URL (интернет-адреса), содержащей входные данные в конце. Такую строку URL можно отправить не только с помощью броузера, то есть минуя традиционный путь, лежащий через форму ввода.

Например, на рис. 1.12 изображена страница, сгенерированная в ответ на ввод адреса URL в адресной строке броузера (символ + здесь означает пробел):

http://localhost/cgibin/cgi101.py?user=Sue+Smith

Рис. 1.12. Ответ сценария cgi101.py на запрос GET с параметрами

 

Входные данные здесь, известные как параметры запроса, находятся в конце строки URL, после символа ?. Они не были введены в поля формы. Строку URL с дополнительными входными данными иногда называют GET-запросом. Наша оригинальная форма отправляет запрос методом POST, в котором входные данные отправляются отдельно. К счастью, в CGI-сценариях на языке Python не требуется различать эти два вида запросов — парсер входных данных в модуле cgi автоматически обрабатывает все различия между методами отправки данных.

Вполне возможно и часто даже полезно иметь возможность отправлять входные данные в строке URL в виде параметров запроса вообще без помощи веб-броузера. Пакет urllib из стандартной библиотеки Python, например, позволяет читать ответ, сгенерированный сервером для любого допустимого адреса URL. Фактически он позволяет посещать вебстраницы или вызывать CGI-сценарии из другого сценария — ваш программный код на языке Python будет играть роль веб-клиента. Ниже демонстрируется пример использования пакета в интерактивной оболочке:

>>> from urllib.request import urlopen

>>> conn = urlopen(‘http://localhost/cgi-bin/cgi101.py?user=Sue+Smith’)

>>> reply = conn.read()

>>> reply

b’<title>Reply Page</title>\n<h1>Hello <i>Sue Smith</i>!</h1>\n’

>>> urlopen(‘http://localhost/cgi-bin/cgi101.py’).read()

b’<title>Reply Page</title>\n<h1>Who are you?</h1>\n’

>>> urlopen(‘http://localhost/cgi-bin/cgi101.py?user=Bob’).read()

b’<title>Reply Page</title>\n<h1>Hello <i>Bob</i>!</h1>\n’

Пакет urllib реализует интерфейс получения ответов от сервера для заданной строки URL, напоминающий интерфейс файлов. Обратите внимание, что ответ, который мы получаем от сервера, представляет собой простую разметку HTML (обычно отображается броузером). Мы можем обрабатывать этот текст с помощью любых инструментов обработки текста, входящих в состав Python, включая:

     Строковые методы поиска и разбиения

     Модуль re, позволяющий выполнять сопоставление с регулярными выражениями

     Развитую поддержку парсинга разметки HTML и XML в стандартной библиотеке, включая модуль html.parser, а также SAX-, DOMи ElementTree-подобные инструменты парсинга разметки XML.

В сочетании с такими инструментами пакет urllib естественным образом подходит для применения в интерактивном тестировании вебсайтов, в собственных клиентских графических интерфейсах, в программах, извлекающих содержимое веб-страниц, и в автоматизированных регрессионных тестах для тестирования удаленных CGI-сценариев.

Форматирование текста ответа

Еще одно, последнее примечание: так как для взаимодействия с клиентами CGI-сценарии используют текст, они должны форматировать его, следуя определенному набору правил. Например, обратите внимание, что в примере 1.31 между заголовком ответа и разметкой HTML присутствует пустая строка в виде явного символа перевода строки (\n), в дополнение к символу перевода строки, который автоматически выводится функцией print, — это обязательный разделитель.

Кроме того, обратите внимание, что текст, добавляемый в разметку HTML ответа, передается через вызов функции cgi.escape (она же html. escape в Python 3.2 — смотрите примечание в разделе «Инструменты экранирования HTML и URL в языке Python», в главе 15), на тот случай, если он содержит символы, имеющие специальное значение в HTML. Например, на рис. 1.13 изображена страница ответа, полученная в результате ввода имени пользователя Bob </i> Smith, — последовательность символов </i> в середине преобразуется этой функцией в последовательность &lt;/i&gt; , благодаря чему исключается влияние этой последовательности на фактическую разметку HTML (воспользуйтесь возможностью просмотра исходного кода страницы, имеющейся в броузерах, чтобы убедиться в этом). Без вызова этой функции остаток имени был бы выведен обычным, некурсивным шрифтом.

Рис. 1.13. Экранирование символов HTML

 

Экранирование текста, как в данном примере, требуется не всегда, но его следует применять, когда содержимое текста заранее не известно, — сценарии, генерирующие разметку HTML, должны следовать правилам ее оформления. Как мы увидим далее в этой книге, похожая функция urllib.parse.quote применяет правила экранирования к тексту в строке с адресом URL. Кроме того, мы увидим, что крупные фреймворки часто решают задачи форматирования текста автоматически.

Веб-интерфейс к хранилищу с данными

Теперь для создания нашего приложения баз данных на основе технологии CGI, представленной в предыдущем разделе, нам потребуется реализовать более крупную форму ввода и отображения данных. На рис. 1.14 изображена форма, которую мы реализуем для доступа к нашей базе данных.

Реализация веб-сайта

Чтобы обеспечить возможность взаимодействий, создадим разметку HTML начальной формы ввода, а также CGI-сценарий на языке Python, который будет отображать полученные результаты и обрабатывать запросы на изменение данных в хранилище. В примере 1.33 приводится разметка HTML формы ввода, которая создает страницу, изображенную на рис. 1.14.

Рис. 1.14. Форма ввода peoplecgi.html

 

Пример 1.33. PP4E\Preview\peoplecgi.html

<html>

<title>People Input Form</title>

<body>

<form method=POST action=”cgi-bin/peoplecgi.py”>

<table>

<   tr><th>Key <td><input type=text name=key>

<tr><th>Name<td><input type=text name=name>

<   tr><th>Age <td><input type=text name=age>

<   tr><th>Job <td><input type=text name=job>

<   tr><th>Pay <td><input type=text name=pay>

</table>

<p>

<input type=submit value=”Fetch”, name=action>

<input type=submit value=”Update”, name=action>

</form>

</body></html>

Обработкой формы и других запросов будет заниматься CGI-сценарий на языке Python, представленный в примере 1.34, который будет извлекать и изменять записи в нашем хранилище. Обратно он будет возвращать страницу, похожую на ту, что воспроизводит разметка в примере 1.33, но с полями формы, заполненными значениями атрибутов объекта, извлеченного из хранилища.

Как и в реализации графического интерфейса, для вывода результатов и ввода изменений будет использоваться одна и та же веб-страница. В отличие от графического интерфейса, этот сценарий будет запускаться заново в ответ на каждое действие пользователя и каждый раз снова будет открывать базу данных (атрибут action формы содержит ссылку на сценарий для следующего запроса). Модель CGI не предоставляет возможности сохранения информации о состоянии между запросами, поэтому каждый раз мы вынуждены выполнять все действия с самого начала.

Пример 1.34. PP4E\Preview\cgi-bin\peoplecgi.py

Реализует веб-интерфейс для просмотра и изменения экземпляров классов в хранилище; хранилище находится на сервере (или на том же компьютере, если используется имя localhost)

import cgi, shelve, sys, os # cgi.test() выведет поля ввода

shelvename = ‘class-shelve’ # файлы хранилища находятся

#   в текущем каталоге

fieldnames = (‘name’, ‘age’, ‘job’, ‘pay’)

form = cgi.FieldStorage() # парсинг данных формы

print(‘Content-type: text/html’) # заголовок + пустая строка для ответа sys.path.insert(0, os.getcwd()) # благодаря этому модуль pickle

#   и сам сценарий будут способны

#   импортировать модуль person

#   главный шаблон разметки html replyhtml = “””

<html>

<title>People Input Form</title>

<body>

<form method=POST action=”peoplecgi.py”>

<table>

<tr><th>key<td><input type=text name=key value=”%(key)s”> $ROWS$

</table>

<p>

<input type=submit value=”Fetch”, name=action>

<input type=submit value=”Update”, name=action>

</form>

</body></html>

#   вставить разметку html с данными в позицию $ROWS$

rowhtml = ‘<tr><th>%s<td><input type=text name=%s value=”%%(%s)s”>\n’ rowshtml = ‘’

for fieldname in fieldnames:

rowshtml += (rowhtml % ((fieldname,) * 3))

replyhtml = replyhtml.replace(‘$ROWS$’, rowshtml)

def htmlize(adict):

new = adict.copy() # значения могут содержать &, >

for field in fieldnames: # и другие специальные символы,

value = new[field] # отображаемые особым образом;

new[field] = cgi.escape(repr(value)) # их необходимо экранировать

return new

def fetchRecord(db, form):

try:

key = form[‘key’].value

record = db[key]

fields = record.__dict__ # для заполнения строки ответа

fields[‘key’] = key # использовать словарь атрибутов

except:

fields = dict.fromkeys(fieldnames, ‘?’) fields[‘key’] = ‘Missing or invalid key!’ return fields

def updateRecord(db, form):

if not ‘key’ in form:

fields = dict.fromkeys(fieldnames, ‘?’) fields[‘key’] = ‘Missing key input!’

else:

key = form[‘key’].value

if key in db:

record = db[key] # изменить существующую запись

else:

from person import Person # создать/сохранить новую

record = Person(name=’?’, age=’?’) # eval: строки должны быть # заключены в кавычки

for field in fieldnames:

setattr(record, field, eval(form[field].value))

db[key] = record

fields = record.__dict__ fields[‘key’] = key

return fields

db = shelve.open(shelvename)

action = form[‘action’].value if ‘action’ in form else None

if action == ‘Fetch’:

fields = fetchRecord(db, form)

elif action == ‘Update’:

fields = updateRecord(db, form)

else:

fields = dict.fromkeys(fieldnames, ‘?’) # недопустимое значение

fields[‘key’] = ‘Missing or invalid action!’ # кнопки отправки формы

db.close()

подпись: # заполнить форму ответа
# из словаря
print(replyhtml % htmlize(fields))

Сценарий получился таким большим, потому что в его задачу входит обработка ввода пользователя, выполнение операций с базой данных и генерирование разметки HTML для ответа. Тем не менее действует он достаточно прямолинейно и по своему поведению напоминает реализацию графического интерфейса из предыдущего раздела.

Каталоги, форматирование строк и безопасность

Прежде чем двинуться дальше, необходимо сделать несколько важных замечаний. Прежде всего, сценарий веб-сервера, представленный в примере 1.32, должен быть запущен до того, как вы начнете экспериментировать, — он будет перехватывать запросы от броузера и передавать их нашему CGI-сценарию.

Обратите также внимание, что при запуске CGI-сценарий добавляет путь к текущему рабочему каталогу (os.getcwd) в путь поиска модулей sys.path. Не изменяя переменную окружения PYTHONPATH, этот прием позволит модулю pickle и самому сценарию импортировать модуль person, находящийся в том же каталоге, что и сценарий. Из-за нового способа запуска CGI-сценариев, реализованного в Python 3, текущий рабочий каталог не добавляется в список sys.path автоматически, хотя при этом файлы хранилища, находящиеся там, будут обнаруживаться и открываться корректно. Эта особенность в поведении может отличаться, в зависимости от выбранного веб-сервера.

Еще один интересный прием в CGI-сценарии — использование словаря атрибутов записи (__dict__) как источника значений в операции экранирования полей внутри выражения форматирования строки, преобразующего строку шаблона HTML в ответ, в последней строке сценария. Напомню, что выражение вида %(key)code заменит ключ key значением этого ключа в словаре:

>>> D = {‘say’: 5, get’: ‘shrubbery’}

>>> D[‘say’]

5

>>> S = ‘%(say)s => %(get)s’ % D

>>> S

‘5 => shrubbery

Благодаря использованию словаря атрибутов мы можем ссылаться на атрибуты по их именам в форме строк. Фактически часть шаблона ответа генерируется программным кодом. Если его структура кажется вам непонятной, просто вставьте инструкции вывода replyhtml и вызова sys. exit и запустите сценарий из командной строки. Ниже показано, как выглядит разметка HTML таблицы в середине сгенерированного ответа (немного отформатированная здесь для удобочитаемости):

<table>

<tr><th>key<td><input type=text name=key value=”%(key)s”>

<tr><th>name<td><input type=text name=name value=”%(name)s”>

<tr><th>age<td><input type=text name=age value=”%(age)s”>

<tr><th>job<td><input type=text name=job value=”%(job)s”>

<tr><th>pay<td><input type=text name=pay value=”%(pay)s”>

</table>

Далее этот текст заполняется значениями ключей из словаря атрибутов записи инструкцией форматирования строки в конце сценария. Эта инструкция выполняется после того, как словарь будет обработан вспомогательной функцией, преобразующей значения в текст с помощью функции repr и экранирующей текст вызовом функции cgi.escape, в соответствии с требованиями языка разметки HTML (опять же, последний шаг не всегда является обязательным, но он никогда не будет лишним).

Эти строки ответа в формате HTML можно было бы жестко определить в программном коде, но генерирование их из кортежа с именами полей обеспечивает более универсальное решение — в будущем мы сможем добавлять новые поля без необходимости изменять шаблон HTML. Инструменты обработки строк в языке Python позволяют это.

Справедливости ради следует заметить, что более новый метод str.format позволяет добиться того же эффекта, что и традиционный оператор % форматирования, используемый в сценарии, и дает возможность использовать синтаксис ссылок на атрибуты объектов, который выглядит более явным по сравнению с приемом использования ключей словаря __dict__:

>  >> D = {‘say’: 5, ‘get’: ‘shrubbery’}

>  >> ‘%(say)s => %(get)s% D # выражение: ссылка на ключ

‘5 => shrubbery’

>  >> ‘{say} => {get}’.format(**D) # метод: ссылка на ключ

‘5 => shrubbery’

>  >> from person import Person

>  >> bob = Person(‘Bob’, 35)

>  >> ‘%(name)s, %(age)s’ % bob.__dict__ # выражение: ключи __dict__ ‘Bob, 35’

>  >> ‘{0.name} => {0.age}’.format(bob) # метод: синтаксис атрибутов ‘Bob => 35’

Однако из-за того, что нам сначала необходимо экранировать атрибуты, мы не можем использовать синтаксис атрибутов в вызове метода форматирования. Фактически для выбора нам доступен лишь синтаксис ссылок на ключи, представленный выше. (К моменту написания этих строк еще не было очевидно, какой из двух способов форматирования займет доминирующее положение, поэтому мы позволим себе использовать в книге оба способа — даже если какой-то из этих способов заменит другой, вы все равно останетесь в выигрыше.)

В интересах безопасности необходимо также напомнить, что прием использования функции eval для преобразования входных данных в объекты языка Python является достаточно мощным, но далеко не безопасным. Эта функция с радостью выполнит любой программный код на языке Python, который в свою очередь сможет выполнить любые системные операции, разрешение на которые будет иметь процесс сценария. Если проблема безопасности имеет для вас значение, то вам придется обеспечить выполнение сценария в ограниченном окружении или использовать более специализированные механизмы преобразования, такие как функции int и float. Вообще говоря, проблема безопасности занимает важное место в мире веб-приложений, где строки запросов могут поступать из самых разных источников. Однако, поскольку все мы здесь считаемся друзьями, мы проигнорируем возможную угрозу.

Пользование веб-сайтом

Несмотря на сложности, связанные с серверами, каталогами и строками, пользоваться веб-интерфейсом ничуть не сложнее, чем графическим интерфейсом. Вдобавок веб-интерфейс имеет дополнительное преимущество — им можно пользоваться в любой операционной системе, где имеется броузер и подключение к Интернету. Чтобы извлечь запись из хранилища, заполните поле Key (Ключ) и щелкните на кнопке Fetch (Извлечь) — сценарий заполнит страницу данными, полученными из атрибутов соответствующего экземпляра класса, извлеченного из хранилища, как показано на рис. 1.15, где была извлечена запись с ключом bob.

На рис. 1.15 показано, что получится, когда ключ передается с помощью формы. Как уже отмечалось выше, CGI-сценарий можно также

Рис. 1.15. Страница ответа peoplecgi.py

вызвать, передав входные данные в виде строки запроса, поместив ее в конец адреса URL. На рис. 1.16 показана страница, полученная в ответ на попытку обратиться по следующему адресу URL:

http://localhost/cgibin/peoplecgi.py?action=Fetch&key=sue

Рис. 1.16. Ответ сценария peoplecgi.py на запрос с параметрами

 

Как вы уже знаете, такую строку URL можно отправить с помощью броузера или сценария, использующего такие инструменты, как пакет urllib. И снова, замените «localhost» на доменное имя своего сервера, если вы запускаете сценарий на удаленном компьютере.

Чтобы изменить запись, извлеките ее по ключу, введите новые значения в поля ввода и щелкните на кнопке Update (Изменить) — сценарий извлечет значения из полей ввода и запишет их в соответствующие атрибуты экземпляра класса в хранилище. На рис. 1.17 показана страница ответа, полученная после изменения записи с ключом sue.

Наконец, операция добавления новой записи выполняется точно так же, как и в графическом интерфейсе: укажите новые значения ключа и полей, щелкните на кнопке Update (Изменить) — CGI-сценарий создаст новый экземпляр класса, запишет в его атрибуты значения соответствующих полей ввода и сохранит его в хранилище с новым ключом. В действительности здесь под покровом веб-страницы выполняются операции с объектом класса, но нам не приходится иметь дело с логикой его создания. На рис. 1.18 изображена запись, добавленная в базу данных таким способом.

В принципе мы точно так же можем изменять и добавлять записи, отправляя соответствующие строки URL — из броузера или из сценария — например:

http://localhost/cgibin/

peoplecgi.py?action=Update&key=sue&pay=50000&name=Sue+Smith& …и далее…

Рис. 1.17. Ответ peoplecgi.py на операцию изменения записи

 

 

Рис. 1.18. Ответ peoplecgi.py после добавления новой записи

 

Однако вводить такую длинную строку URL без использования автоматизированных инструментов существенно сложнее, чем заполнять поля формы. Ниже приводится часть страницы ответа, сгенерированной в ответ на создание записи с ключом «guido» и изображенной на рис. 1.18 (воспользуйтесь возможностью просмотра исходного кода страницы, имеющейся в броузерах, чтобы убедиться в этом). Обратите внимание, что символы < и > были преобразованы функцией cgi.escape в экранированные последовательности HTML, перед тем как они были вставлены в ответ:

<tr><th>key<td><input type=text name=key value=”guido”>

<tr><th>name<td><input type=text name=name value=”’GvR’”>

<tr><th>age<td><input type=text name=age value=”None”>

<tr><th>job<td><input type=text name=job value=”’BDFL’”>

<tr><th>pay<td><input type=text name=pay value=”’&lt;shrubbery&gt;’”>

Как обычно, для тестирования нашего CGI-сценария можно использовать пакет urllib из стандартной библиотеки — возвращаемый результат представляет собой простую разметку HTML, которую можно проанализировать с помощью других инструментов, имеющихся в стандартной библиотеке, и использовать в качестве основы для системы регрессионного тестирования серверного сценария, выполняющейся на любой машине, подключенной к Интернету. Мы могли бы даже реализовать анализ ответа сервера, полученного таким способом, и отображать данные в графическом интерфейсе, реализованном с помощью библиотеки tkinter, — графические интерфейсы и веб-страницы не являются взаимоисключающими технологиями. В последнем примере получения данных в интерактивном сеансе демонстрируется фрагмент страницы HTML с сообщением об ошибке, которая была сгенерирована в ответ на отсутствующее или недопустимое входное значение, с разрывами строк, добавленными для удобочитаемости:

>>> from urllib.request import urlopen

>>> url = http://localhost/cgi-bin/peoplecgi.py?action=Fetch&key=sue’

>>> urlopen(url).read()

b’<html>\n<title>People Input Form</title>\n<body>\n

<form method=POST action=”peoplecgi.py”>\n <table>\n <tr><th>key<td><input type=text name=key value=”sue”>\n <tr><th>name<td><input type=text name=name value=”\’Sue Smith\’”>\n <tr><t остальной текст удален

>>> urlopen(‘http://localhost/cgi-bin/peoplecgi.py’).read() b’<html>\n<title>People Input Form</title>\n<body>\n

<form method=POST action=”peoplecgi.py”>\n <table>\n

<tr><th>key<td><input type=text name=key value=”Missing or invalid action!”>\n

<tr><th>name<td><input type=text name=name value=”\’?\’”>\n

<tr><th>age<td><input type=text name=age value=”\’?\’”>\n<tr> остальной текст удален

Фактически, если CGI-сценарий выполняется на локальном компьютере «localhost», для просмотра одного и того же хранилища вы сможете использовать и графический интерфейс из предыдущего раздела, и вебинтерфейс из этого раздела — это всего лишь альтернативные интерфейсы доступа к одним и тем же хранимым объектам Python. Для сравнения на рис. 1.19 показано, как выглядит запись в графическом интерфейсе, которую мы видели на рис. 1.18, — это тот же самый объект, но на этот раз мы получили ее, не обращаясь к промежуточному серверу, запускающему другие сценарии или генерирующему разметку HTML.

Как и прежде, мы всегда можем проверить результаты нашей деятельности на сервере, используя интерактивную оболочку или другие сценарии. Мы можем просматривать содержимое базы данных с помощью веб-броузеров или графического интерфейса, но в любом случае это всего лишь объекты Python в файле хранилища:

>>> import shelve

>>> db = shelve.open(‘class-shelve’)

>>> db[‘sue’].name

‘Sue Smith’

>>> db[‘guido’].job

‘BDFL’

>>> list(db[‘guido’].name)

[‘G’, ‘v’, ‘R’]

>>> list(db.keys())

[‘sue’, ‘bill’, ‘nobody’, ‘tomtom’, ‘tom’, ‘bob’, ‘peg’, ‘guido’]

Fetch Update Quit

Рис. 1.19. Тот же самый объект, отображаемый в графическом интерфейсе

 

Ниже приводятся результаты запуска первоначального сценария из примера 1.19, извлекающего информацию из базы данных, который мы написали до того, как перешли к реализации графического и вебинтерфейса, в языке Python существует масса способов просмотра данных:

\PP4E\Preview> dump_db_classes.py sue =>

Sue Smith 60000 bill => bill 9999 nobody => John Doh None tomtom =>

Tom Tom 40000 tom =>

Tom Doe 90000 bob =>

Bob Smith 30000 peg => 1 4

guido =>

GvR <shrubbery>

Smith

Doe

Дальнейшие направления усовершенствования

Естественно, что в этот пример можно было бы внести множество улучшений:

     Разметка HTML для начальной формы ввода, представленная в примере 1.33, несколько избыточна для сценария в примере 1.34, и ее можно было бы генерировать с помощью другого сценария, используемого как источник совместно используемой информации.

     Фактически мы вообще можем отказаться от встраивания разметки HTML в наш сценарий, если воспользуемся одним из инструментов- генераторов HTML, с которыми мы познакомимся далее в книге, таким как HTMLgen (система создания разметки HTML из дерева объектов документа) и PSP (Python Server Pages — серверные страницы Python, серверная система шаблонов HTML для Python, напоминающая PHP и ASP).

     Чтобы упростить обслуживание, можно было бы также вынести разметку HTML для CGI-сценария в отдельный файл, чтобы отделить представление от логики (с разными файлами могли бы работать разные специалисты).

     Кроме того, если веб-сайтом могут пользоваться сразу несколько человек, мы могли бы добавить возможность блокировки файла хранилища или перейти на использование базы данных, такой как ZODB или MySQL, чтобы обеспечить возможность параллельных изменений. ZODB и другие полноценные системы управления базами данных позволяют также использовать возможность отмены транзакций в случае ошибок. Реализовать простейшую блокировку файла можно с помощью функции os.open и ее флагов.

     Механизмы ORM (object relational mappers — объектно-реляционного отображения) для Python, такие как SQLObject и SQLAlchemy, упоминавшиеся выше, также способны обеспечить поддержку одновременной работы нескольких пользователей с реляционной базой данных, сохраняя в ней представление данных в виде наших классов Python.

     Наконец, если размер нашего сайта станет больше, чем несколько интерактивных страниц, мы могли бы перейти от CGI-сценариев к более развитым веб-фреймворкам, таким как упоминавшиеся в начале этого раздела — Django, TurboGears, pyjamas и другие. На случай, если потребуется сохранять информацию между обращениями к страницам, можно было бы использовать такие инструменты, как cookies, скрытые поля ввода, сеансы, поддерживаемые модулем mod_python и FastCGI.

    Если потребуется хранить на сайте информационное наполнение, производимое его пользователями, мы могли бы перейти на использование Plone. Это популярная и открытая система управления содержимым, написанная на языке Python, использующая сервер приложений Zope, реализующая модель документооборота и делегирующая управление содержимым сайта его авторам.

    А если на повестке дня встанет поддержка беспроводных или распределенных интерфейсов, мы могли бы перенести нашу систему на сотовые телефоны, используя один из трансляторов с языка Python, доступных, например, для платформы Nokia и Google Android, или на платформу распределенных вычислений, такую как Google App Engine. Язык Python с успехом проникает в области, куда ведет развитие технологий.

Но, тем не менее, и графический, и веб-интерфейс, созданные нами, вполне справляются со своей работой.

Конец демонстрационного примера

На этом мы заканчиваем знакомство с вводным демонстрационным примером использования языка Python. Мы исследовали способы представления данных, ООП, механизмы сохранения объектов, инструменты создания графических интерфейсов и основы разработки веб-сайтов. Ни одну из этих тем мы не рассматривали достаточно глубоко. Тем не менее хотелось бы надеяться, что эта глава пробудила в вас любопытство к программированию приложений на языке Python.

В оставшейся части книги мы углубимся в изучение этих и других инструментов и тем прикладного программирования, чтобы помочь вам включить язык Python в работу в ваших собственных программах. В следующей главе мы начнем наше путешествие с изучения инструментов системного программирования и администрирования, имеющихся в распоряжении программистов на языке Python.

Скрытые «сюрпризы» в Python

На настоящий момент, когда я пишу эти строки в 2010 году, я занимаюсь языком Python уже почти 18 лет, и я видел, как он вырос из никому не известного языка в инструмент, который в том или ином виде используется практически каждой организацией, занимающейся разработкой, и входит в четверку или пятерку наиболее используемых языков программирования в мире. Это были лучшие годы.

Оглядываясь назад, могу сказать, что если в языке Python что-то и осталось действительно неизменным, так это его врожденная способность заставлять разработчиков акцентировать внимание на качестве программного кода. И это практически неизбежно. Язык, который требует, чтобы разработчик форматировал программный код для большей удобочитаемости, не может не заставить людей поднимать вопросы о выборе наиболее удачных приемов разработки программного обеспечения.

Пожалуй, ничто так не подчеркивает эту сторону жизни языка Python, как модуль this из стандартной библиотеки — своего рода сюрприз, или «пасхальное яйцо» в Python, созданный одним из основных разработчиков Python — Тимом Петерсом (Tim Peters), который хранит в себе список основных принципов, на которых основывается язык. Чтобы увидеть их, запустите интерактивный сеанс интерпретатора Python и импортируйте модуль (естественно, он доступен на всех платформах):

>>> import this

The Zen of Python, by Tim Peters

Beautiful is better than ugly.

Explicit is better than implicit.

Simple is better than complex.

Complex is better than complicated.

Flat is better than nested.

Sparse is better than dense.

Readability counts.

Special cases aren’t special enough to break the rules.

Although practicality beats purity.

Errors should never pass silently.

Unless explicitly silenced.

In the face of ambiguity, refuse the temptation to guess.

There should be oneand preferably only one obvious way to do it.

Although that way may not be obvious at first unless you’re Dutch. Now is better than never.

Although never is often better than *right* now.

If the implementation is hard to explain, it’s a bad idea.

If the implementation is easy to explain, it may be a good idea. Namespaces are one honking great idea let’s do more of those! >>> (Перевод:

Дзен языка Python, составлен Тимом Петерсом

Красивое лучше, чем уродливое.

Явное лучше, чем неявное.

Простое лучше, чем сложное.

Сложное лучше, чем запутанное.

Плоское лучше, чем вложенное.

Разреженное лучше, чем плотное.

Удобочитаемость имеет значение.

Особые случаи не настолько особые, чтобы нарушать правила.

При этом практичность важнее безупречности.

Ошибки никогда не должны замалчиваться. Если не замалчиваются явно.

Встретив двусмысленность, отбрось искушение угадать.

Должен существовать один и, желательно, только один очевидный способ сделать что-то.

Хотя он поначалу может быть и не очевиден, если вы не голландец. Сейчас лучше, чем никогда.

Хотя никогда зачастую лучше, чем *прямо сейчас*.

Если реализацию сложно объяснить — идея плоха.

Если реализацию легко объяснить — идея, возможно, хороша. Пространства имен — отличная штука! Будем делать их побольше! )[II]

Особого упоминания заслуживает правило «Явное лучше, чем неявное», которое в мире Python известно, как аббревиатура «EIBTI» («Explicit is better than implicit») — одна из основных идей языка Python, и одно из самых сильных отличий от других языков. Любой, кто проработал на этой ниве более, чем несколько лет, сможет засвидетельствовать, что волшебство и инженерное искусство есть вещи несовместимые. Конечно, сам язык Python не всегда неукоснительно следовал всем этим правилам, но старался придерживаться их как можно ближе. И если Python заставляет людей задумываться о таких вещах, то это уже победа. Кстати, название языка отлично смотрится на футболке


Использованная литература:
Марк Лутц — Программирование на Python, 4-е издание, I том, 2011
Оцените статью
Секреты программирования
Добавить комментарий