Одна из няшних самых горячих тем сегодня - это "веб-приложения." В отличие от традиционного "исполнимого" программного обеспечения, которое выполняется в определенном месте на настольном компьютере, веб приложение выполняется на централизованном сервере и предоставляет его возможности через Интернет, обычно через протокол HTTP и обычный веб-браузер. Веб приложения все более и более популярны, потому что они могут быть легоко доступны -- просто введите URL в браузер -- и приложение доступно одновременно целому ряду пользователей. Некоторые веб приложения обеспечивают электронную коммерцию (eBay), некоторые служат для развлечений (как например Yahoo! Games), и другие, как например Salesforce.com, управляют информацией предприятия.
В то время как Java, Perl, и PHP часто восхваляются как идеальные языки программирования для разработки веб приложений, возможности Python в этой области такие же. Действительно, Python отлично для создания динамического содержимого веб страниц.
Самый простой путь создания веб приложение на Python - использовать Common Gateway Interface (CGI). CGI - это протокол, который описывает, как соединить клиентов с веб приложениями.
Обычно, когда вы запрашиваете статическую страницу у веб-сервера, он находит файл, что вы запросили и посылает файл обратно в ответ. Например, запрос на http://www.example.com/contact.html возвращает страницу HTML contact.html. Однако, если запрос ссылается на сценарий CGI, то вместо возвращения сценария (его кода как содержимого), сценарий запускается и результат работы сценария присылается в ответ. Сценарии CGI генерируют содержимое динамически в ответ на запрос (и его параметры, как вы вскоре увидите).
Однажды поняв, как работает CGI, создание динамического содержимого становится простым использованием команды print. И в противоположность его репутации, CGI не обязательно медленен. Даже если интерпретатор Python запускается для каждого вызова сценария, рекомендуется попробовать CGI перед выбором более сложной веб структуры приложения.
Углубимся в программирование CGI на Python. Первая из двух частей объясняет основы CGI, описывает, как посылаются формы HTML и объясняет, как обработать введенные в форму данные.
Весь код в этой статье должен работать в Python 2.2 и более поздних версиях
Заголовки и концы строк
Половина работы при написания веб приложения - это возвращение правильных заголовков в ответ на запрос. Пересылка действительных заголовков важна не только для информации, которую получает клиент -- если ваша программа не выдает действительных заголовков, веб-сервер предполагает, что ваш сценарий выполнился неуспешно и показывает устрашащую Ошибку 500... Внутренняя Ошибка Сервера.
Есть уйма различных заголовков, которые вы можете послать. Как минимум вы должны послать заголовок (фактически, во многих ситуациях это, возможно, является единственным заголовкмв, который вам нужно послать) Content-Type и вы должны завершить ваш список заголовков пустой линией.
Все заголовки выглядят как "тип_заголовка: имя_заголовока-значение\r\n". Однако, большинство клиентов и серверов позволяют лишь \n, то есть то, что вы получите в качестве нормального завершения строки на системах типа UNIX.
Hello World
Составим обязательную для новичков программу "Hello, World" как CGI скрипт:
#!/usr/bin/python import sys try: import cgitb cgitb.enable() except ImportError: sys.stderr = sys.stdout def cgiprint(inline=''): sys.stdout.write(inline) sys.stdout.write('rn') sys.stdout.flush() contentheader = 'Content-Type: text/html' thepage = '''< html>< head> < title>%s< /title> < /head>< body> %s < /body>< /html> ''' h1 = '< h1>%s< /h1>' if __name__ == '__main__': cgiprint(contentheader) # заголовок содержимого cgiprint() # завершить заголовки пустой строкой title = 'Hello World' headline = h1 % 'Hello World' print thepage % (title, headline)
Давайте пройдемся по коду.
Если вы запускаете сценарий CGI на системе Linux или Unix, вы должны включать обязательную линию (#!/usr/bin/python) "shebang" в строке 1, чтобы сказать сценарию, где найти Python.
Следующая часть сценария - это try/except блок, который пытается импортировать модуль cgitb. Обычно ошибки в программе на Python посылаются в sys.stderr. Однако, когда запускается CGI скрипт, sys.stderr транслирует в журнал регистрации ошибок сервера. Но постоянный поиск ошибок в журнале регистрации ошибок - это неудобно при отлаживании. Вместо этого cgitb выводит сообщения об ошибках, в том числе полезную информацию подобно значениям переменных, в браузер. (Этот модуль был только введен в Python 2.2.) Если импорт не удается, stderr присоединяется к stdout, который делает подобную, но не такую эффективную работу. (Не используйте модуль cgitb в производственных приложениях. Показываемая им информация включает детали о вашей системе, которая, возможно, полезна для возможного взломщика.)
Потом, cgiprint() выпускает две строки заголовков и должным образом завершает заголовки правильными окончаниями строк. (cgiprint() должна использоваться только для строк заголовков.) cgiprint() посылает заголовок Content-Type. Поскольку сценарий возвращает веб-страницу (которая является формой текста) тип/подтип заголовка - это text/html. Посылается только один заголовок, затем заголовки завершаются пустой строкой.
cgiprint() сбрасывает буфер выходного потока, используя sys.stdout.flush(). Большинство серверов буферизует выдачу сценариев, пока сценарий не завершен. Для сценариев, которые долго выполняются, буферизация выхода, возможно, расстроит вашего пользователя. Вы можете или регулярно сбрасывать ваш буфер, или запускать Python в небуферизованном режиме. Вариант командной строки: -u, который вы можете вставить как #!/usr/bin/python -u.
В конце концов, сценарий посылает маленькую страницу HTML, которая должна выглядеть знакомо, если вы использовали HTML до этого момента.
Пользовательский интерфейс и формы HTML
При написании CGI скриптов, ваш интерфейс пользователя - это веб-браузер. Объединеняя Javascript, Динамический HTML (DHTML) и HTML формы, вы можете создавать богатые возможностями веб приложения.
Основными элементами HTML, используемыми для общения с CGI скриптами, являются формы и элементы формы для пользовательского ввода, в том числе текстовые поля, радиокнопки, переключатели, меню и так далее.
Пример формы
Типичная, простая форма HTML, возможно, кодировалась бы подобно этому:
< form action="/cgi-bin/formprocessor.py" method="get"> What is Your Name : < input name="param1" type="text" value="Joe Bloggs" />< br /> < input name="param2" type="radio" value="this" checked="checked" /> Select This< br /> < input name="param2" type="radio" value="that" />or That< br /> < input name="param3" type="checkbox" checked="checked" /> Check This< br /> < input name="param4" type="checkbox" checked="checked" /> and This Too ?< br /> < input name="hiddenparam" type="hidden" value="some_value" /> < input type="reset" /> < input type="submit" /> < /form>
Когда пользователь нажимает кнопку Submit, данные из полей формы включается в запрос HTTP. Внутри тега form есть два параметра, которые определяют то, как происходит включение. Параметр action - это URI вашего сценария CGI, то есть куда направляется запрос. Параметр method конкретизирует, как значения кодируются в запросе. Два возможных метода - GET и POST.
Более простой - GET. При использовании GET значения формы кодируются, чтобы быть "URL безопасными" и добавляются в конец URL как список параметров. С POST кодируемые значения посылаются в теле запроса после того, как заголовки посланы.
GET проще, однако длина URL ограничена. Таким образом, использование GET навязывает максимальный ограничение для формы, которая может быть послана. (Около 1,000 символов - это ограничение многих серверов.) Если вы используете форму, чтобы получить длинный текстовый ввод из вашей формы, используйте POST. POST более подходит для запросов, где посылается больше данных.
Одно преимущество GET - то, что вы можете закодировать параметры для скрипта в нормальную ссылку HTML. Это означает, что параметры могут быть отправлены вашей программе без нажатия кнопки пользователем. Кодируемый набор значений выглядит примерно так:
param1=value1¶m2=value+2¶m3=value%263(Http GET запрос добавляет эту строку к URL.) Так что, целый URL, возможно, стал бы чем-нибудь вроде http://www.someserver.com/cgi-bin/test.py?param1=value1¶m2=value+2¶m3=value%26.
? отделяет URI вашего сценария от кодируемых параметров. Символы & отделяют параметры друг от друга. + представляет пространство (которое не нужно посылать как часть URI, конечно), и %26 - это кодируемое значение, которое представляет &. & не нужно посылать как часть значения, иначе CGI думал бы, что был послан новый параметр.
Если вы кодируете ваши собственные значения в URL, используйте функцию urllib.encode() от модуля urllib подобно этому:
value_dict = { 'param_1' : 'value1', 'param_2' : 'value2' }
encoded_params = urllib.encode(value_dict)
full_link = script_url + '?' + encoded_params
Получение данных из форм
Формы HTML инкапсулированы в запросы так, что хорошо отображаются в тип данных "словарь" Python. Каждый элемент формы имеет имя и соответственное значение.
Например, если элемент - это радиокнопка, то посланное значение - это значение выбранной кнопки. Например, в предыдущей форме радиокнопка имеет имя param2 и ее значение есть или this, или that. Для переключателя, скажем param3 или param4, посланное значение есть off или on.
Теперь, когда вы знаете основы того, как формы кодируются и отправляются к CGI скриптам, настало время представить вам модуль cgi. Модуль cgi - это ваш механизм для получения данных из формы. Он делает процесс кодирования очень легким.
Чтение данных формы слегка осложняется двумя фактами. Для начала, имена входных элементов формы могут повторяться, так что значения могут быть списками. (Представьте себе форму, которая позволяет вам выбрать все правильные ответы.) Во-вторых, по умолчанию входной элемент, который не имеет никакого значения -- как например текстовое поле, которое не заполнено -- будет скорее отсутствовать, чем быть просто пустым.
Метод FieldStorage() модуля cgi возвращает объект, который представляет данные формы. Он - почти словарь. Вместо того, чтобы повторять страницу руководства об использовании модуля cgi, давайте помотрим на пару функций общего назначения, которые принимают объект, созданный FieldStorage() и возвращают словари.
Функции
def getform(theform, valuelist, notpresent='', nolist=False): """ This function, given a CGI form as a FieldStorage instance, extracts the data from it, based on valuelist passed in. Any non-present values are set to '' - although this can be changed. (e.g. to return None so you can test for missing keywords - where '' is a valid answer but to have the field missing isn't.) It also takes a keyword argument 'nolist'. If this is True list values only return their first value. """ data = {} for field in valuelist: if not theform.has_key(field): # if the field is not present (or was empty) data[field] = notpresent else: # the field is present if type(theform[field]) != type([]): # is it a list or a single item data[field] = theform[field].value else: if not nolist: # do we want a list ? data[field] = theform.getlist(field) else: data[field] = theform.getfirst(field) # just fetch the first item return data def getall(theform, nolist=False): """ Passed a form (cgi.FieldStorage instance) return *all* the values. This doesn't take into account multipart form data (file uploads). It also takes a keyword argument 'nolist'. If this is True list values only return their first value. """ data = {} for field in theform.keys(): # we can't just iterate over it, but must use the keys() method if type(theform[field]) == type([]): if not nolist: data[field] = theform.getlist(field) else: data[field] = theform.getfirst(field) else: data[field] = theform[field].value return data def isblank(indict): """ Passed an indict of values it checks if any of the values are set. Returns True if the indict is empty, else returns False. I use it on the a form processed with getform to tell if my CGI has been activated without any form values. """ for key in indict.keys(): if indict[key]: return False return True
Почти для всех CGI скриптов, которые получают входные данные от формы, вы будете знать, какие параметры ожидать. (В конце концов, вероятно вы сами и написали форму.) Если вы передаете функции getform() экзампляр FieldStorage() и список всех параметров, которые вы ожидаете получить, она возвращает словарь значений. Любые отсутствующие параметры имеют значение по умолчанию '', если только вы не изменяли ключевое слово notpresent. Если вы хотите убедиться, что вы не получаете никаких списковых значений, установите ключевое слово nolist. Если переменная формы была списком, nolist возвращает только первое значение в списке.
Или, если вы хотите получить все значения, посланные формой, используйте функцию getall(). Она также принимает ключевое слово nolist как необязательный аргумент.
isblank() - это специальная функция: она проводит быструю проверку, чтобы определить все ли значения содержатся в словаре, возвращенном getall(), или getform() пуст. Если это так, CGI скрипт был вызван без параметров. В таком случае обычно генерируется страница приветствия и форма. Если словарь не пуст (isblank() возвращает False), форма содержит данные для обработки.
Использование getform()
Давайте обработаем данные из примера формы, описанной выше. Этой части программы нужны предыдущие функции и первые несколько строк программы Hello World.
import cgi
mainpage = '''<html><head><title>Receiving a Form</title></head><body>%s</body></html>''' error = ''' <h1>Error</h1> <h2>No Form Submission Was Received</h2> ''' result = ''' <h1>Receiving a Form Submission</h1> We received the following parameters from the form : <ul> <li>Your name is "%s".</li> <li>You selected "%s".</li> <li>"this" is "%s". </li> <li>"this too" is "%s". </li> <li> A hidden parameter was sent "%s".</li> </ul> ''' possible_parameters = ['param1', 'param2', 'param3', 'param4', 'hidden_param'] if __name__ == '__main__': cgiprint(contentheader) # content header cgiprint() # finish headers with blank line theform = cgi.FieldStorage() formdict = getform(theform, possible_parameters) if isblank(formdict): body = error else: name = formdict['param1'] radio = formdict['param2'] # should be 'this' or 'that' check1 = formdict['param3'] # 'on' or 'off' check2 = formdict['param4'] hidden = formdict['hidden_param'] body = result % (name, radio, check1, check2, hidden) print mainpage % body
Давайте обсудим код. Есть три главных отрезка html: mainpage - это рамка страницы, которой только нужно тело для вставки внутрь нее. error показывается, если сценарий выл вызван без параметров. Однако, если сценарий вызван путем отправки формы, то параметры извлекаются и помещаются в result.
Сценарий печатает обязательные заголовки, а затем создает экземпляр FieldStorage, чтобы представить данные формы. Затем theform передается функции getform() наряду со списком ожидаемых параметров.
Если форма не была послана, то все значения в словаре, возвращенном getform() пусты (то есть ''). В данном случае isblank() возвращает True и body установлена, чтобы быть сообщением об ошибке.
Если форма была послана, то isblank() возваращает False и значения из словаря извлекаются и вставляются в result. Переменная name содержит имя введенное в текстовое поле. Значение радиокнопки (в radio) или this, или that, в зависимости от того, какае значение было выбрано. check1 и check2 установлены в off или on в зависимости от того, были ли выбраны соответствующие переключатели. Скрытый параметр возвращается всегда.
Наконец страница напечатана, показывая или ошибку, или результаты. Легко или нет? Использование скрытых значений открывает возможность создания уникальных значений и кодирования их в форму. Это могло бы связать запросы вместе, так что вы можете динамически скроить содержимое для каждого пользователя, так как они проходят через ваше приложение (но это другая история).
Использование getall()
Если бы приложение было больше, возможно с несколькими формами, вы могли бы не знать заранее какие параметры будут введены. В таком случае вы можете использовать getall() вместо getform(). Тогда вы можете проверить существование определенных параметров и выполнить различные действия в зависимости от того, какая форма была отправлена:
formdict = getall(theform) if formdict.has_key('rating'): process_feedback(formdict) # пользователь вводит обратную связь elif formdict.has_key('email'): subscribe(formdict) # пользователь подписывается на рассылку else: optionlist() # показать форму со всеми вариантами
Используя getall(), фактически вы можете переделать наш последний сценарий во что-нибудь немного более общее и полезное:
import cgi
mainpage = '''<html><head><title>Receiving a Form</title></head><body>%s</body></html>''' result = ''' <h1>Receiving a Form Submission</h1> We received the following parameters from the form : <ul>%s</ul> ''' li = " <li>%s = %s</li> " if __name__ == '__main__': cgiprint(contentheader) # заголовок содержимого cgiprint() # завершить заголовки пустой линией theform = cgi.FieldStorage() formdict = getall(theform) params = [] for entry in formdict: params.append(li % (entry, str(formdict[entry]))) print mainpage % (result % ''.join(params))
Этот код получает все введенные параметры используя getall(). Затем он вставляет их в страницу как неопределенный список. Если вы посылаете этому сценарию форму, он показывает вам все полученные параметры на странице, где каждая строка выглядит как "параметр = значение". Поскольку строка кода, которая производит это, использует str() функция для каждого значения, она может справиться со списками.
Список значений
Различные параметры формы могут иметь одинаковые имена. В данном случае, значение, возвращенное в FieldStorage - это список. Вы могли бы использовать это для сбора информации от вашего пользователя. Например, список тем для информационных бюллетеней, которых вы, возможно, рассылаете.
<form action="/cgi-bin/formprocessor.py" method="get"> What is Your Name : <input name="name" type="text" value="Joe Bloggs" /> Email Address : <input name="email" type="text" /> <input name="interests" type="checkbox" value="computers" />Computers <input name="interests" type="checkbox" value="sewing" />Sewing <input name="interests" type="checkbox" value="ballet" />Ballet <input name="interests" type="checkbox" value="scuba" />Scuba Diving <input name="interests" type="checkbox" value="cars" />Cars <input type="reset" /> <input type="submit" /> </form>
Когда эта форма отсылается, она будет содержать именя пользователя, его адрес электронной почты и список всех интересов, которые он отметил. Код получения значений из экземпляра FieldStorage:
import cgi
theform = cgi.FieldStorage()
interests = theform['interests'].value
Трудность в том, что если пользователь отмечает только один интерес, тогда интересы - это единое значение вместо списка, который мы ожидаем. Альтернатива - использовать высокоуровневые методы, доступные в FieldStorage.
Метод Getlist() всегда возвращает список, даже если было введено только одно значение. Если выбор вообще не был сделан, он возвращает пустой список.
import cgi
theform = cgi.FieldStorage()
interests = theform.getlist('interests')
Было бы очень легко приспособить функции getform() и getall() к вашим специфическим потребностям при работе со значениями, которые будут списками.
Экспериментируйте
Вам не нужен выделенный сервер, чтобы отлаживать CGI скрипты. Вы можете кодировать и отлаживать веб приложения на вашей настольной машине, и это является хорошей новостью для тех, кто все еще платит за поминутный доступ в Интернета. Использую локальный веб-сервер на вашей собственной машине, вы можете выполнять цикл "кодировать, тестировать, рвать волосы, отлаживать" дома. Попробуйте Xitami_. Это - быстрый и легкий веб-сервер, особенно для платформы Windows.
Нужно проявить внимание при настройке CGI на сервере. Это не трудно, но есть несколько шагов, которые должны быть сделаны.
Если сценарий перемещают на другой сервер, вам вероятно придется поместить его на сервер с помощью FTP. Ваш FTP-клиент должен быть настроен так, чтобы пересылать сценарии Python как текст. Скопируйте в нужный каталог, установите корректные права доступа. Проверьте путь к интрепретатору.
Веб-страница с примерами CGI http://www.voidspace.org.uk/python/cgi.shtml. Скрипты доступны для проверки или загрузке. Они включают генератор анаграмм и различные более маленькие тестовые сценарии. Есть также полный набор инструметов для создания аутентификации пользователей и управления с помощью CGI logintools.
Приичины ошибки 500
Отладка CGI скриптов может быть тяжелым процессом. По умолчанию какая-нибудь проблема с вашим сценарием CGI приводит к анонимной ошибке 500. Фактические детали ошибки вписаны в лог файл веб-сервера, который может быть полезен, если вы имеете доступ к этому файлу.
Однако, более чем половина 500 ошибок может быть легко устранена путем проверки следующих общих источников ошибок. Вы будете удивлены как часто вы будуете допускать некоторые из них.
Был ли ваш сценарий помещен на сервер в 'текстовом' режиме? (Настроен ли ваш FTP-клиентдля пересылки файлов .py в текстовом режиме?)
Установили ли вы скрипту корректные права доступа 755? (выполнение для всех)?
Установили ли вы правильный путь к интерпретатору в первой строке?
Напечатали ли вы корресктные заголовки, в том числе заключительную пустую строку?
Кроме того, некоторые серверы требуют, чтобы скрипт находился в каталоге cgi-bin (или его подкаталоге), и некоторые даже требуют, чтобы расширение файла было .cgi вместо .py.
Заключение
Мы обсудили все основы CGI. Информация здесь достаточна для начала работы, или хотя бы как минимум для поиска нужных материалов для самообучения.
Однако осталось еще много тем: кодировки символов, использование шаблонов для вывода подобного кода HTML, получение информации о HTTP-запросе пользователя и т.д.
Постоянные ссылки
При копировании ссылка на TeaM RSN обязательна!
Оставить комментарий
Вы должны войти, чтобы оставить комментарий.