LXF83:Python
(→Учимся посылать) |
WikiSysop (обсуждение | вклад) м (Правки RicroAcdom (обсуждение) откачены к версии AmbientLight) |
||
(не показаны 20 промежуточных версий 6 участников) | |||
Строка 1: | Строка 1: | ||
+ | {{Цикл/Python}} | ||
+ | |||
== Работа с базами данных и web-программирование == | == Работа с базами данных и web-программирование == | ||
− | '' ''' | + | '' '''Часть 3''' Что может быть мощнее связки «база данных + интернет»? А если к этому добавить еще и Python... Чтобы почувствовать все это на практике, погрузимся сегодня в пучины SQL-запросов и HTTP-ответов вместе с '''Сергеем Супруновым'''.'' |
Мы уже видели, что Python прекрасно подходит для работы с текстом. А что такое интернет-страницы, которые миллионы серверов Apache ежедневно миллиардами отдают на растерзание нашим браузерам? По сути, тот же текст, только немножко «гипер»... А значит, если нам нужно будет формировать html-страницу динамически, то Python прекрасно с этим справится. И никаких препятствий для разработки на нем CGI-сценариев не существует – web-серверу, по большому счету, безразлично, как именно выполняется скрипт и на каком языке он разработан: лишь бы он умел читать данные из потока ввода и переменных окружения да отдавать текст в стандартный выходной поток. | Мы уже видели, что Python прекрасно подходит для работы с текстом. А что такое интернет-страницы, которые миллионы серверов Apache ежедневно миллиардами отдают на растерзание нашим браузерам? По сути, тот же текст, только немножко «гипер»... А значит, если нам нужно будет формировать html-страницу динамически, то Python прекрасно с этим справится. И никаких препятствий для разработки на нем CGI-сценариев не существует – web-серверу, по большому счету, безразлично, как именно выполняется скрипт и на каком языке он разработан: лишь бы он умел читать данные из потока ввода и переменных окружения да отдавать текст в стандартный выходной поток. | ||
Строка 18: | Строка 20: | ||
=== Учимся посылать === | === Учимся посылать === | ||
Начнем с формирования HTTP-ответа. Чтобы браузер клиента мог его правильно обработать, он должен состоять из заголовка и тела, разделенных пустой строкой. В заголовке передается необходимая служебная информация, например, тип содержимого, его кодировка, указание браузеру запросить другой ресурс (так называемое перенаправление), и т.д. Простейший cgi-сценарий на языке Python может выглядеть так: | Начнем с формирования HTTP-ответа. Чтобы браузер клиента мог его правильно обработать, он должен состоять из заголовка и тела, разделенных пустой строкой. В заголовке передается необходимая служебная информация, например, тип содержимого, его кодировка, указание браузеру запросить другой ресурс (так называемое перенаправление), и т.д. Простейший cgi-сценарий на языке Python может выглядеть так: | ||
− | < | + | |
+ | <source lang="python"> | ||
#!/usr/bin/Python | #!/usr/bin/Python | ||
# -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||
− | print | + | print 'Content-Type: text/html\n' |
− | print | + | print '<H3>Если вы это видите, значит все работает</H3>' |
− | </ | + | </source> |
+ | |||
Первым оператором print мы формируем минимально необходимый заголовок – браузер клиента обязательно должен знать, каков тип пересылаемых ему данных (в нашем случае это простой текст, соответствующий формату HTML). Не забывайте о дополнительном переводе строки \n, необходимом для отделения заголовка от тела ответа. Ну и далее вы можете передавать любой HTML-код. | Первым оператором print мы формируем минимально необходимый заголовок – браузер клиента обязательно должен знать, каков тип пересылаемых ему данных (в нашем случае это простой текст, соответствующий формату HTML). Не забывайте о дополнительном переводе строки \n, необходимом для отделения заголовка от тела ответа. Ну и далее вы можете передавать любой HTML-код. | ||
Строка 29: | Строка 33: | ||
=== Здесь играть, здесь не играть... === | === Здесь играть, здесь не играть... === | ||
− | Однако какой смысл поручать формирование статических, по сути, | + | Однако какой смысл поручать формирование статических, по сути, страниц cgi-сценарию, если сам HTTP-сервер справится с этим намного лучше? В общем-то никакого. Разве что для общего развития... А вот в чем CGI по-настоящему силен, так это в формировании динамических страниц, содержимое которых зависит от информации, переданной пользователем. |
− | страниц cgi-сценарию, если сам HTTP-сервер справится с этим намного | + | |
− | лучше? В общем-то никакого. Разве что для общего развития... А вот | + | |
− | в чем CGI по-настоящему силен, так это в формировании | + | |
− | + | ||
− | пользователем | + | |
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | Если клиент использует метод GET, то данные поступят в сценарий | + | Протокол HTTP предусматривает несколько способов передачи информации от клиента на сервер, называемых методами. Наиболее популярные из них – GET, POST, PUT и HEAD. |
− | через переменную среды QUERY_STRING. Метод, которым данные | + | |
− | переданы (нужно же как-то разобраться, где их искать) можно всегда | + | Метод GET позволяет вставлять информацию в URL, то есть в строку адреса запрашиваемого ресурса. Когда «Яндекс» вернет вам список искомых страниц, посмотрите на адресную строку в браузере – вот так данные и передаются методом GET. Кстати, обратите внимание на то, как все это кодируется, особенно если вы искали какое-то русское слово. |
− | получить из REQUEST_METHOD. | + | |
− | Есть еще один особый случай. Если данные передаются методом | + | Если на сервер требуется передать больший объем информации, или ее желательно скрыть от любопытных глаз, используется другой метод – POST. В данном случае в заголовке передается лишь размер пользовательских данных, а сами данные пересылаются в теле запроса. |
− | GET, но с использованием «индексного» формата, который | + | |
− | + | Метод PUT предназначается для размещения ресурсов на сервере и по соображениям безопасности практически не используется. Ну и, наконец, метод HEAD очень похож на GET, за тем исключением, что сервер в ответ на такой запрос возвращает не весь ресурс, а лишь информацию о нем, такую как дата последнего изменения, помещаемую в заголовке. Обычно используется прокси-серверами для определения «свежести» имеющихся у них данных – стоит ли запрашивать ресурс повторно или можно вернуть клиенту то, что есть в кэше. | |
− | виде «переменная=значение&переменная=значение&...», а как | + | |
− | «значение+значение+...». И cgi-сценарию они будут переданы, | + | Определенная сложность для разработчика cgi-сценария заключается в том, что данные, отправленные различными методами, передаются в сценарий по-разному. Так, информация, поступившая с помощью POST, подается на стандартный вход сценария и может быть считана оттуда, например, с помощью sys.stdin.read(size) или даже функцией raw_input() (хотя во втором случае сложнее контролировать объем принимаемых данных). Количество байт, которые требуется считать, можно получить из переменной окружения CONTENT_LENGTH (например, так: size = os.environ['CONTENT_LENGTH']). |
− | + | ||
− | сценарий вызывался такой командой: | + | Если клиент использует метод GET, то данные поступят в сценарий через переменную среды QUERY_STRING. Метод, которым данные переданы (нужно же как-то разобраться, где их искать) можно всегда получить из REQUEST_METHOD. |
− | script.cgi arg1 arg2 arg3 | + | |
− | То есть, на этот раз пользовательские данные можно будет получить | + | Есть еще один особый случай. Если данные передаются методом GET, но с использованием «индексного» формата, который формируется тегом <ISINDEX>, то в этом случае они кодируются не в виде «переменная=значение&переменная=значение&...», а как «значение+значение+...». И cgi-сценарию они будут переданы, помимо QUERY_STRING, через аргументы командной строки, как если бы сценарий вызывался такой командой: |
− | как sys.argv[1] и т.д. | + | |
− | Как видите, огромное число вариантов, предусмотренных CGI- | + | script.cgi arg1 arg2 arg3 |
− | интерфейсом, которые все должны быть учтены при разработке | + | |
− | + | То есть, на этот раз пользовательские данные можно будет получить как sys.argv[1] и т.д. | |
− | которые и во сне потихоньку набивают по подушке какой-то код. А | + | |
− | если еще вспомнить, что данные передаются в закодированном виде | + | Как видите, огромное число вариантов, предусмотренных CGI-интерфейсом, которые все должны быть учтены при разработке сценария, может вызвать нервный тик даже у опытных программистов, которые и во сне потихоньку набивают по подушке какой-то код. А если еще вспомнить, что данные передаются в закодированном виде (это англичанам хорошо – взял значение переменной и работай, а нам-то с вами это значение вернется в виде %EC%E4%E0), да еще и о проверке этих данных нужно позаботиться, чтобы какой-нибудь начинающий хакер не попытался заставить наш сервер работать по-своему... Нет, обо всем этом лучше и не вспоминать. Благо у нас есть модуль cgi, в котором все это уже сделано! |
− | (это англичанам хорошо – взял значение переменной и работай, а | + | |
− | нам-то с вами это значение вернется в виде %EC%E4%E0), да еще | + | Но о нем – чуть позже. Сначала пару слов нужно сказать о HTML-формах. |
− | и о проверке этих данных нужно позаботиться, чтобы какой-нибудь | + | |
− | начинающий хакер не попытался заставить наш сервер работать по- | + | |
− | своему... Нет, обо всем этом лучше и не вспоминать. Благо у нас есть | + | |
− | модуль cgi, в котором все это уже сделано! | + | |
− | Но о нем – чуть позже. Сначала пару слов нужно сказать о | + | |
− | HTML-формах. | + | |
=== Формируем формы === | === Формируем формы === | ||
− | Чтобы вам было проще понять рассматриваемый сегодня пример, | + | Чтобы вам было проще понять рассматриваемый сегодня пример, коротко скажу про то, как же клиент выполняет передачу данных нашему cgi-сценарию. Конечно, продвинутые пользователи могут набрать GET-запрос вручную в адресной строке браузера. Хотя что мелочиться – ведь можно же сформировать и POST-запрос, подключившись телнетом на 80-й порт! Впрочем, обычные пользователи предпочитают более понятные и «осязаемые» способы, например, формы. |
− | + | ||
− | cgi-сценарию. Конечно, продвинутые пользователи могут набрать GET- | + | Как они выглядят, думаю, каждый знает. Создаются они с помощью тега <FORM>, внутри которого добавляются такие элементы, как <INPUT> (поле ввода) или <TEXTAREA> (многострочный редактор). Этим элементам, если их данные должны быть переданы на сервер, присваиваются имена с помощью атрибута name. Начальное значение задается параметром value и в дальнейшем для «редактируемых» полей может быть изменено пользователем. Когда пользователь нажимает кнопку «Отправить» (надпись на ней, в принципе, можно изменить), то браузер объединяет все данные полей в пары name=value, разделяя их символом &. Затем полученная таким образом строка передается на сервер методом, указанным в атрибуте method тега <FORM>. Путь к сценарию, который будет заниматься ее обработкой, задается атрибутом action этого же тега. Если action не задан, то данные передаются файлу, сформировавшему текущую страничку. |
− | запрос вручную в адресной строке браузера. Хотя что мелочиться – ведь | + | |
− | можно же сформировать и POST-запрос, подключившись телнетом на | + | Если что-то не совсем понятно, обратитесь к коду разрабатываемой гостевой книги, который приведен ниже. |
− | 80-й порт! Впрочем, обычные пользователи предпочитают более | + | |
− | + | ||
− | Как они выглядят, думаю, каждый знает. Создаются они с | + | |
− | + | ||
− | <INPUT> (поле ввода) или <TEXTAREA> (многострочный редактор). | + | |
− | Этим элементам, если их данные должны быть переданы на сервер, | + | |
− | присваиваются имена с помощью атрибута name. Начальное | + | |
− | + | ||
− | полей может быть изменено пользователем. Когда пользователь | + | |
− | + | ||
− | + | ||
− | разделяя их символом &. Затем полученная таким образом строка | + | |
− | передается на сервер методом, указанным в атрибуте method тега | + | |
− | <FORM>. Путь к сценарию, который будет заниматься ее обработкой, | + | |
− | задается атрибутом action этого же тега. Если action не задан, то | + | |
− | + | ||
− | Если что-то не совсем понятно, обратитесь к коду разрабатываемой | + | |
− | гостевой книги, который приведен ниже. | + | |
=== Наш спаситель – модуль cgi === | === Наш спаситель – модуль cgi === | ||
− | Возвращаемся к обработке всего этого добра, которое сотни | + | Возвращаемся к обработке всего этого добра, которое сотни пользователей уже готовы обрушить на наш бедный сценарий. Мы решили воспользоваться стандартными средствами Python, и здесь все действительно очень просто – импортируйте модуль cgi и, создав объект класса FieldStorage, вы получите через него доступ ко всем данным, переданным пользователем, независимо от используемого метода: |
− | + | ||
− | воспользоваться стандартными средствами Python, и здесь все | + | <source lang="python"> |
− | + | ||
− | класса FieldStorage, вы получите через него доступ ко всем данным, | + | |
− | переданным пользователем, независимо от используемого метода: | + | |
import cgi | import cgi | ||
data = cgi.FieldStorage() | data = cgi.FieldStorage() | ||
for entry in data.keys(): | for entry in data.keys(): | ||
− | print | + | print 'Переменная %s имеет значение %s' % (entry, data[entry].value) |
− | Если вам нужно получить значение определенного поля, это | + | </source> |
− | + | ||
− | field = data[ | + | Если вам нужно получить значение определенного поля, это делается так: |
− | Помимо пользовательских данных, объект класса FieldStorage | + | |
− | содержит информацию и о полях заголовка (в нашем примере их можно | + | <source lang="python"> |
− | получить из словаря data.headers). MIME-тип данных (передаваемый | + | field = data['field'].value |
− | полем заголовка Content-Type) можно получить из атрибута data.type. | + | </source> |
− | Через этот же объект может быть выполнена и загрузка файла. | + | |
− | С помощью методов keys() и has_key() можно выполнять | + | Помимо пользовательских данных, объект класса FieldStorage содержит информацию и о полях заголовка (в нашем примере их можно получить из словаря data.headers). MIME-тип данных (передаваемый полем заголовка Content-Type) можно получить из атрибута data.type. Через этот же объект может быть выполнена и загрузка файла. |
− | + | ||
− | + | С помощью методов keys() и has_key() можно выполнять обработку полученных данных в цикле и проверять наличие той или иной переменной. Кстати говоря, проверять наличие переменной во входных данных, прежде чем приступать к их обработке, нужно непременно – ведь запрос формируется клиентом, а кто знает, что у него на уме? | |
− | + | ||
− | запрос формируется клиентом, а кто знает, что у него на уме? | + | |
=== Базируем данные === | === Базируем данные === | ||
− | Итак, получать данные от клиента мы научились. Отправлять тоже | + | Итак, получать данные от клиента мы научились. Отправлять тоже умеем. Осталось придумать, как эти данные лучше всего хранить. Конечно, для несложной гостевой книги с небольшой нагрузкой вполне хватило бы и текстовых файлов. Правда, там есть свои сложности – если сразу пять человек захотят высказать свое мнение о вашей крутейшей домашней страничке, то сценарию придется каким-то образом регулировать доступ к файлу-хранилищу (как минимум, обрабатывать ситуацию, если файл уже открыт на запись другим экземпляром сценария). Но зачем нам все эти головные боли? Если мы так ловко отвертелись от необходимости вручную разбирать HTTP-запросы, то неужели не найдем что-то подходящее на этот раз? |
− | + | ||
− | для несложной гостевой книги с небольшой нагрузкой вполне хватило | + | Конечно, найдем! И это «что-то» называется системой управления базами данных (в просторечье – СУБД). Теперь наше дело – отправить запрос и получить ответ. Все остальное – уже не наша забота. |
− | бы и текстовых файлов. Правда, там есть свои сложности – если сразу | + | |
− | пять человек захотят высказать свое мнение о вашей крутейшей | + | Для этого примера я выбрал в качестве «ответственного» за хранение данных сервер баз данных PostgreSQL. Поскольку мы пишем ну очень простую гостевую книгу, то и структура базы будет у нас элементарной – одна таблица с тремя полями: время публикации сообщения, имя автора и, собственно, само сообщение: |
− | + | ||
− | доступ к файлу-хранилищу (как минимум, обрабатывать ситуацию, если | + | admin@toshiba:~$ psql |
− | файл уже открыт на запись другим экземпляром сценария). Но зачем | + | Welcome to psql 8.1.4, the PostgreSQL interactive terminal. |
− | нам все эти головные боли? Если мы так ловко отвертелись от | + | guestbook=# create user "www-data" nocreatedb nocreateuser; |
− | + | CREATE ROLE | |
− | подходящее на этот раз? | + | admin=# create database guestbook with owner "www-data"; |
− | Конечно, найдем! И это «что-то» называется системой управления | + | CREATE DATABASE |
− | базами данных (в просторечье – СУБД). Теперь наше дело – отправить | + | admin=# \connect guestbook |
− | запрос и получить ответ. Все остальное – уже не наша забота. | + | Вы подсоединились к базе данных "guestbook". |
− | Для этого примера я выбрал в качестве «ответственного» за | + | guestbook=# create table guestbook ( |
− | + | guestbook(# datum timestamp, author varchar, message varchar); | |
− | очень простую гостевую книгу, то и структура базы будет у нас | + | CREATE TABLE |
− | + | guestbook=# alter table guestbook owner to "www-data"; | |
− | имя автора и, собственно, само сообщение: | + | ALTER TABLE |
− | admin@toshiba:~$ psql | + | guestbook=# \q |
− | Welcome to psql 8.1.4, the PostgreSQL interactive terminal. | + | admin@toshiba:~$ |
− | guestbook=# create user | + | |
− | CREATE ROLE | + | Пожалуй, единственное, что здесь нужно пояснить, это почему базе и таблице мы назначили владельцем пользователя www-data. Просто к ним будет обращаться cgi-сценарий, работающий с правами HTTP-сервера Apache, который, в свою очередь, исполняется от имени данного пользователя [в вашем дистрибутиве он может назваться по-другому, – прим. ред.]. А PostgreSQL по умолчанию требует, чтобы имя пользователя в БД совпадало с его системным именем. Мне это кажется достаточно удобным, хотя вы, конечно, можете поступить по-своему. |
− | admin=# create database guestbook with owner | + | |
− | CREATE DATABASE | + | |
− | admin=# \connect guestbook | + | |
− | Вы подсоединились к базе данных | + | |
− | guestbook=# create table guestbook ( | + | |
− | guestbook(# datum timestamp, author varchar, message varchar); | + | |
− | CREATE TABLE | + | |
− | guestbook=# alter table guestbook owner to | + | |
− | ALTER TABLE | + | |
− | guestbook=# \q | + | |
− | admin@toshiba:~$ | + | |
− | Пожалуй, единственное, что здесь нужно пояснить, это почему | + | |
− | базе и таблице мы назначили владельцем пользователя www-data. | + | |
− | Просто к ним будет обращаться cgi-сценарий, работающий с правами | + | |
− | HTTP-сервера Apache, который, в свою очередь, исполняется от | + | |
− | + | ||
− | по-другому, – прим. ред.]. А PostgreSQL по умолчанию требует, | + | |
− | + | ||
− | это кажется достаточно удобным, хотя вы, конечно, можете поступить | + | |
− | по-своему. | + | |
=== DB API на страже унификации === | === DB API на страже унификации === | ||
− | Осталось разобраться, как же Python взаимодействует с базами | + | Осталось разобраться, как же Python взаимодействует с базами данных. Для этого Python предоставляет DB API – специальный интерфейс, унифицирующий набор методов, которые будут одинаково работать независимо от того, с какой СУБД мы взаимодействуем. Для работы с PostgreSQL нам понадобится модуль PyPgSQL (в стандартной поставке его может не оказаться, но ваш менеджер пакетов наверняка будет в курсе, как его установить; кстати, это не единственный модуль – у вас, возможно, будет PyGreSQL, который работает ничуть ни хуже и с теми же самыми методами). |
− | + | ||
− | унифицирующий набор методов, которые будут одинаково работать | + | DB API определяет стандартные методы работы с базами данных, так что, какой бы модуль вы ни загрузили и с какой бы СУБД ни работали (будь то MySQL, PostgreSQL, SQLite или что-то еще), меняться будет только имя модуля. Главное, чтобы используемый модуль соответствовал DB API. Рассмотрим коротко основные методы: |
− | независимо от того, с какой СУБД мы взаимодействуем. Для работы с | + | |
− | PostgreSQL нам понадобится модуль PyPgSQL (в стандартной поставке | + | <source lang="python"> |
− | его может не оказаться, но ваш менеджер пакетов наверняка будет в | + | conn = connect(dsn='localhost', user='admin', password='superparol', database='mydb') |
− | курсе, как его установить; кстати, это не единственный модуль – у вас, | + | </source> |
− | возможно, будет PyGreSQL, который работает ничуть ни хуже и с теми | + | |
− | же самыми методами). | + | Так осуществляется подключение к базе. В зависимости от ситуации, вам может потребоваться указать только нужные параметры (например, имя хоста 'localhost' подразумевается по умолчанию). |
− | DB API определяет стандартные методы работы с базами данных, | + | |
− | так что, какой бы модуль вы ни загрузили и с какой бы СУБД ни | + | <source lang="python"> |
− | + | ||
− | только имя модуля. Главное, чтобы используемый модуль | + | |
− | + | ||
− | conn = connect(dsn= | + | |
− | database= | + | |
− | Так осуществляется подключение к базе. В зависимости от ситуации, | + | |
− | вам может потребоваться указать только нужные параметры (например, | + | |
− | имя хоста | + | |
cur = conn.cursor() | cur = conn.cursor() | ||
− | Курсоры поддерживаются далеко не всеми СУБД, но для общности | + | </source> |
− | в DB API они введены и, в случае необходимости, должны | + | |
− | + | Курсоры поддерживаются далеко не всеми СУБД, но для общности в DB API они введены и, в случае необходимости, должны эмулироваться модулями сопряжения искусственно. Так что не забывайте отправлять все ваши запросы через курсор. | |
− | все ваши запросы через курсор. | + | |
− | cur.execute( | + | <source lang="python"> |
− | Так выполняется SQL-запрос. Если в строке запроса используются | + | cur.execute('''SELECT * FROM mytable''') |
− | знакоместа %s, то вторым параметром передается список переменных- | + | </source> |
− | значений, причем в SQL-запросе знакоместа не требуется окружать | + | |
− | апострофами – модуль сделает это самостоятельно в зависимости от | + | Так выполняется SQL-запрос. Если в строке запроса используются знакоместа %s, то вторым параметром передается список переменных-значений, причем в SQL-запросе знакоместа не требуется окружать апострофами – модуль сделает это самостоятельно в зависимости от |
типа переменной. | типа переменной. | ||
+ | |||
+ | <source lang="python"> | ||
cur.fetchall() | cur.fetchall() | ||
− | Возвращает двумерный список (строки – поля) полученных от СУБД | + | </source> |
− | данных. Существуют и другие методы, ознакомиться с которыми вы | + | |
− | сможете в документации или с помощью знакомой вам функции dir() | + | Возвращает двумерный список (строки – поля) полученных от СУБД данных. Существуют и другие методы, ознакомиться с которыми вы сможете в документации или с помощью знакомой вам функции dir() да пары-тройки несложных экспериментов. |
− | да пары-тройки несложных экспериментов. | + | |
=== Закрепляем на практике === | === Закрепляем на практике === | ||
− | Перейдем к рассмотрению нашего примера. Начнем стандартно – | + | Перейдем к рассмотрению нашего примера. Начнем стандартно – укажем кодировку, подключим нужные модули: |
− | + | ||
+ | <source lang="python"> | ||
#!/usr/bin/Python | #!/usr/bin/Python | ||
# -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||
import PyPgSQL.PgSQL as pg | import PyPgSQL.PgSQL as pg | ||
import cgi | import cgi | ||
− | Далее, определим две функции. Первая будет отвечать за | + | </source> |
− | + | ||
+ | Далее, определим две функции. Первая будет отвечать за добавление нового сообщения в базу: | ||
+ | |||
+ | <source lang="python"> | ||
def addMessage(author, message): | def addMessage(author, message): | ||
− | db = pg.connect(database= | + | db = pg.connect(database="guestbook") |
− | c = db.cursor() | + | c = db.cursor() |
− | c.execute( | + | c.execute("""INSERT INTO guestbook (datum, author, message) VALUES ('now', %s, %s);""", (author, message)) |
− | VALUES ( | + | c.close() |
− | c.close() | + | db.commit() |
− | db.commit() | + | db.close() |
− | db.close() | + | print "Content-Type: text/html" |
− | print | + | print "Location: ?#form\n" |
− | print | + | </source> |
− | Как видите, все очень даже логично: устанавливаем соединение | + | |
− | с БД (поскольку в нашем случае подключение выполняется с именем | + | Как видите, все очень даже логично: устанавливаем соединение с БД (поскольку в нашем случае подключение выполняется с именем текущего системного пользователя, то достаточно указать только имя базы), создаем курсор (в PostgreSQL они не применяются, но они эмулируются каждым модулем, претендующим на соответствие DB API), выполняется запрос, закрывается курсор, фиксируются изменения (PostgreSQL использует транзакции, поэтому выполнение метода commit() обязательно, иначе ваши изменения не будут сохранены), и, наконец, закрываем само соединение с базой. В поле datum заносим значение встроенной переменной PostgreSQL – now, которая каждый раз заменяется текущим значением даты и времени. |
− | текущего системного пользователя, то достаточно указать только имя | + | |
− | базы), создаем курсор (в PostgreSQL они не применяются, но они | + | Ну и печать заголовка «Location» выполняется для того, чтобы перенаправить пользователя на этот же сценарий, но уже без параметров – мы же должны показать клиенту, что он на самом деле ввел? (Якорь #form используется, чтобы автоматически прокрутить страничку на последнее сообщение). |
− | эмулируются каждым модулем, претендующим на соответствие DB | + | |
− | API), выполняется запрос, закрывается курсор, фиксируются | + | Вторая функция будет отвечать за вывод на экран уже оставленныхв книге записей, а также за форму, с помощью которой можно будет добавить и свое высказывание: |
− | + | ||
− | commit() обязательно, иначе ваши изменения не будут сохранены), и, | + | <source lang="python"> |
− | наконец, закрываем само соединение с базой. В поле datum заносим | + | |
− | значение встроенной переменной PostgreSQL – now, которая каждый | + | |
− | раз заменяется текущим значением даты и времени. | + | |
− | Ну и печать заголовка «Location» выполняется для того, чтобы | + | |
− | перенаправить пользователя на этот же сценарий, но уже без | + | |
− | + | ||
− | (Якорь #form используется, чтобы автоматически прокрутить страничку | + | |
− | на последнее сообщение). | + | |
− | Вторая функция будет отвечать за вывод на экран уже | + | |
− | + | ||
− | добавить и свое высказывание: | + | |
def showGB(): | def showGB(): | ||
− | db = pg.connect(database= | + | db = pg.connect(database="guestbook") |
− | c = db.cursor() | + | c = db.cursor() |
− | c.execute( | + | c.execute("""SELECT datum, author, message FROM guestbook ORDER BY datum;""") |
− | FROM guestbook ORDER BY datum; | + | res = c.fetchall() |
− | res = c.fetchall() | + | c.close() |
− | c.close() | + | |
db.close() | db.close() | ||
− | В этом фрагменте мы выбираем все строки из нашей таблицы | + | </source> |
− | + | ||
− | которой и будем работать. Теперь осталось лишь аккуратненько | + | В этом фрагменте мы выбираем все строки из нашей таблицы данных, сортируя их по дате. Результат сохраняется в переменной res, с которой и будем работать. Теперь осталось лишь аккуратненько разложить наши данные по табличкам и вывести их на экран: |
− | + | ||
− | print | + | <source lang="python"> |
− | print | + | print "Content-Type: text/html\n" |
− | H1> | + | print "<H1 style='color:#7777FF'><U>Велькам к нам в гости!</U></H1>" |
− | for item in res: | + | for item in res: |
− | print | + | print """<TABLE width='90%%'> |
− | <TR><TD><SMALL>Товарищ <B>%s</B> | + | <TR><TD><SMALL>Товарищ <B>%s</B> поведалнам следующее:</SMALL> |
− | + | <TD align='right'><SMALL>%s</SMALL> | |
− | <TD align= | + | <TR><TD style='background-color:#DDDDFF' colspan='2'>%s |
− | <TR><TD style= | + | </TABLE>""" % (item[1], str(item[0])[:19], item[2]) |
− | </TABLE> | + | print "<HR><A name='form'><H3>Присоединяйтесь к дискуссии:</H3>" |
− | print | + | print """<FORM method='GET'> |
− | H3> | + | Ваше имя: <INPUT type='text' name='author'><BR> |
− | print | + | Что вы думаете по этому поводу:<BR> |
− | Ваше имя: <INPUT type= | + | <TEXTAREA name='message' rows='5' cols='80'></TEXTAREA><BR> |
− | Что вы думаете по этому поводу:<BR> | + | <INPUT type='submit' value='Отправить'> |
− | <TEXTAREA name= | + | </FORM>""" |
− | TEXTAREA><BR> | + | </source> |
− | <INPUT type= | + | |
− | </FORM> | + | <div id="img"></div> |
− | Смысл конструкции str(item[0])[:19] заключается в том, | + | [[Изображение:Img 83 81 1.png|thumb|Ни смайликов, ни BB-кода, ни даже логотипа... Зато мы сделали эту гостевую за 10 минут!]] |
− | + | ||
− | сохраняются в поле типа timestamp. После всех опубликованных | + | Смысл конструкции str(item[0])[:19] заключается в том, чтобы в строке времени отсечь ненужные нам миллисекунды, которые сохраняются в поле типа timestamp. После всех опубликованных сообщений выводим форму добавления нового, чтобы каждый мог присоединиться к нашей дискуссии. Кстати, в теге <FORM> мы не указали параметр action, поскольку данные будут передаваться на обработку этому же сценарию (благодаря чему имя сценарию можно присвоить любое). |
− | сообщений выводим форму добавления нового, чтобы каждый мог | + | |
− | присоединиться к нашей дискуссии. Кстати, в теге <FORM> мы не | + | |
− | указали параметр action, поскольку данные будут передаваться на | + | |
− | обработку этому же сценарию (благодаря чему имя сценарию можно | + | |
− | присвоить любое). | + | |
Наконец, последний фрагмент: | Наконец, последний фрагмент: | ||
+ | |||
+ | <source lang="python"> | ||
form = cgi.FieldStorage() | form = cgi.FieldStorage() | ||
− | if form.has_key( | + | if form.has_key("message") and form.has_key("author"): |
− | author = cgi.escape(form[ | + | author = cgi.escape(form["author"].value) |
− | message = cgi.escape(form[ | + | message = cgi.escape(form["message"].value) |
− | message = message.replace( | + | message = message.replace("\n", "<BR>") |
− | addMessage(author, message) | + | addMessage(author, message) |
else: | else: | ||
− | showGB() | + | showGB() |
− | Создаем FieldStorage-объект, и если в нем есть заполненные | + | </source> |
− | поля message и author (то есть запрос был сформирован из | + | |
− | + | Создаем FieldStorage-объект, и если в нем есть заполненные поля message и author (то есть запрос был сформирован из заполненной пользователем формы), то, немножко их обработав (функция cgi.escape() заменяет все «неблагонадежные» символы – например, < – их стандартными SGML-сущностями, в данном случае – <), передаем функции addMessage(). Обработка нужна для того, чтобы злоумышленник не мог ввести в поле сообщения или имени автора что-нибудь такое: | |
− | cgi.escape() заменяет все «неблагонадежные» символы – | + | |
− | + | <SCRIPT>alert('Да пошли вы все!');</SCRIPT> | |
− | передаем функции addMessage(). Обработка нужна для того, чтобы | + | |
− | злоумышленник не мог ввести в поле сообщения или имени автора | + | К слову, пренебрегать проверкой введенных данных ни в коем случае нельзя. Зайдите как-нибудь на [http://securitylab.ru securitylab.ru] и посмотрите, сколько уязвимостей типа «XSS» обнаруживается каждый месяц! Так что шутки шутками, но последствия могут быть очень серьезными. |
− | что-нибудь такое: | + | |
− | <SCRIPT>alert( | + | |
− | К слову, пренебрегать проверкой введенных данных ни в коем | + | |
− | + | ||
− | уязвимостей типа «XSS» обнаруживается каждый месяц! Так что шутки | + | |
− | шутками, но последствия могут быть очень серьезными. | + | |
=== Куда же нам теперь идти? === | === Куда же нам теперь идти? === | ||
− | Итак, что-то вполне работоспособное у нас есть (см. рисунок). Но как вы | + | Итак, что-то вполне работоспособное у нас есть (см. [[LXF83:Python#img|рисунок]]). Но как вы может догадаться, наша гостевая очень далека от совершенства. Что еще можно сделать? Ну, например, разбить на страницы. Пока сообщений в ней будет не больше дюжины, сойдет и так. А когда их число дойдет до сотни, то редкий пользователь дождется окончания загрузки всех данных. Можно дать пользователям возможность использовать некоторые HTML-теги, чтобы их сообщения выглядели более красочно. Можно добавить смайликов... А можно даже сделать модуль администрирования, позволяющий редактировать или удалять сообщения, а также отвечать на них. Так что работы непочатый край. Дерзайте – не буду вам мешать. |
− | может догадаться, наша гостевая очень далека от совершенства. Что | + | |
− | еще можно сделать? Ну, например, разбить на страницы. Пока | + | === Некоторые распространённые MIME-типы === |
− | + | {| style="background:white;color:black;" border="1" cellspacing="0" | |
− | дойдет до сотни, то редкий пользователь дождется окончания загрузки | + | |- style="background:#dfcfe6;color:black" |
− | всех данных. Можно дать пользователям возможность использовать | + | ! MIME-тип |
− | некоторые HTML-теги, чтобы их сообщения выглядели более красочно. | + | ! Описание |
− | Можно добавить смайликов... А можно даже сделать модуль | + | |- |
− | + | | text/plain | |
− | также отвечать на них. Так что работы непочатый край. Дерзайте – не | + | | Простой текст |
− | буду вам мешать. | + | |- |
+ | | text/html | ||
+ | | HTML-страница | ||
+ | |- | ||
+ | | image/gif | ||
+ | | Изображение GIF | ||
+ | |- | ||
+ | | video/mpeg | ||
+ | | Видео-файл в формате MPEG | ||
+ | |- | ||
+ | | application/msword | ||
+ | | Документ MS Word | ||
+ | |} |
Текущая версия на 14:27, 31 мая 2009
|
|
|
- Метамодернизм в позднем творчестве В.Г. Сорокина
- ЛитРПГ - последняя отрыжка постмодерна
- "Ричард III и семиотика"
- 3D-визуализация обложки Ridero создаем обложку книги при работе над самиздатом.
- Архитектура метамодерна - говоря о современном искусстве, невозможно не поговорить об архитектуре. В данной статье будет отмечено несколько интересных принципов, характерных для построек "новой волны", столь притягательных и скандальных.
- Литература
- Метамодерн
- Рокер-Прометей против изначального зла в «Песне про советскую милицию» Вени Дркина, Автор: Нина Ищенко, к.ф.н, член Союза Писателей ЛНР - перепубликация из журнала "Топос".
- Как избавиться от комаров? Лучшие типы ловушек.
- Что делать если роблокс вылетает на windows
- Что делать, если ребенок смотрит порно?
- Почему собака прыгает на людей при встрече?
- Какое масло лить в Задний дифференциал (мост) Visco diff 38434AA050
- О чем может рассказать хвост вашей кошки?
- Верветки
- Отчетность бюджетных учреждений при закупках по Закону № 223-ФЗ
- Срок исковой давности как правильно рассчитать
- Дмитрий Патрушев минсельхоз будет ли преемником Путина
- Кто такой Владислав Поздняков? Что такое "Мужское Государство" и почему его признали экстремистским в России?
- Как правильно выбрать машинное масло в Димитровграде?
- Как стать богатым и знаменитым в России?
- Почему фильм "Пипец" (Kick-Ass) стал популярен по всему миру?
- Как стать мудрецом?
- Как правильно установить FreeBSD
- Как стать таким как Путин?
- Где лучше жить - в Димитровграде или в Ульяновске?
- Почему город Димитровград так называется?
- Что такое метамодерн?
- ВАЖНО! Временное ограничение движения автотранспортных средств в Димитровграде
- Тарифы на электроэнергию для майнеров предложено повысить
- Метамодернизм в позднем творчестве В.Г. Сорокина
- ЛитРПГ - последняя отрыжка постмодерна
- "Ричард III и семиотика"
- 3D-визуализация обложки Ridero создаем обложку книги при работе над самиздатом.
- Архитектура метамодерна - говоря о современном искусстве, невозможно не поговорить об архитектуре. В данной статье будет отмечено несколько интересных принципов, характерных для построек "новой волны", столь притягательных и скандальных.
- Литература
- Метамодерн
- Рокер-Прометей против изначального зла в «Песне про советскую милицию» Вени Дркина, Автор: Нина Ищенко, к.ф.н, член Союза Писателей ЛНР - перепубликация из журнала "Топос".
- Как избавиться от комаров? Лучшие типы ловушек.
- Что делать если роблокс вылетает на windows
- Что делать, если ребенок смотрит порно?
- Почему собака прыгает на людей при встрече?
- Какое масло лить в Задний дифференциал (мост) Visco diff 38434AA050
- О чем может рассказать хвост вашей кошки?
- Верветки
- Отчетность бюджетных учреждений при закупках по Закону № 223-ФЗ
- Срок исковой давности как правильно рассчитать
- Дмитрий Патрушев минсельхоз будет ли преемником Путина
- Кто такой Владислав Поздняков? Что такое "Мужское Государство" и почему его признали экстремистским в России?
- Как правильно выбрать машинное масло в Димитровграде?
- Как стать богатым и знаменитым в России?
- Почему фильм "Пипец" (Kick-Ass) стал популярен по всему миру?
- Как стать мудрецом?
- Как правильно установить FreeBSD
- Как стать таким как Путин?
- Где лучше жить - в Димитровграде или в Ульяновске?
- Почему город Димитровград так называется?
- Что такое метамодерн?
- ВАЖНО! Временное ограничение движения автотранспортных средств в Димитровграде
- Тарифы на электроэнергию для майнеров предложено повысить
- Метамодернизм в позднем творчестве В.Г. Сорокина
- ЛитРПГ - последняя отрыжка постмодерна
- "Ричард III и семиотика"
- 3D-визуализация обложки Ridero создаем обложку книги при работе над самиздатом.
- Архитектура метамодерна - говоря о современном искусстве, невозможно не поговорить об архитектуре. В данной статье будет отмечено несколько интересных принципов, характерных для построек "новой волны", столь притягательных и скандальных.
- Литература
- Метамодерн
- Рокер-Прометей против изначального зла в «Песне про советскую милицию» Вени Дркина, Автор: Нина Ищенко, к.ф.н, член Союза Писателей ЛНР - перепубликация из журнала "Топос".
- Как избавиться от комаров? Лучшие типы ловушек.
- Что делать если роблокс вылетает на windows
- Что делать, если ребенок смотрит порно?
- Почему собака прыгает на людей при встрече?
- Какое масло лить в Задний дифференциал (мост) Visco diff 38434AA050
- О чем может рассказать хвост вашей кошки?
- Верветки
- Отчетность бюджетных учреждений при закупках по Закону № 223-ФЗ
- Срок исковой давности как правильно рассчитать
- Дмитрий Патрушев минсельхоз будет ли преемником Путина
- Кто такой Владислав Поздняков? Что такое "Мужское Государство" и почему его признали экстремистским в России?
- Как правильно выбрать машинное масло в Димитровграде?
- Как стать богатым и знаменитым в России?
- Почему фильм "Пипец" (Kick-Ass) стал популярен по всему миру?
- Как стать мудрецом?
- Как правильно установить FreeBSD
- Как стать таким как Путин?
- Где лучше жить - в Димитровграде или в Ульяновске?
- Почему город Димитровград так называется?
- Что такое метамодерн?
- ВАЖНО! Временное ограничение движения автотранспортных средств в Димитровграде
- Тарифы на электроэнергию для майнеров предложено повысить
- Метамодернизм в позднем творчестве В.Г. Сорокина
- ЛитРПГ - последняя отрыжка постмодерна
- "Ричард III и семиотика"
- 3D-визуализация обложки Ridero создаем обложку книги при работе над самиздатом.
- Архитектура метамодерна - говоря о современном искусстве, невозможно не поговорить об архитектуре. В данной статье будет отмечено несколько интересных принципов, характерных для построек "новой волны", столь притягательных и скандальных.
- Литература
- Метамодерн
- Рокер-Прометей против изначального зла в «Песне про советскую милицию» Вени Дркина, Автор: Нина Ищенко, к.ф.н, член Союза Писателей ЛНР - перепубликация из журнала "Топос".
- Как избавиться от комаров? Лучшие типы ловушек.
- Что делать если роблокс вылетает на windows
- Что делать, если ребенок смотрит порно?
- Почему собака прыгает на людей при встрече?
- Какое масло лить в Задний дифференциал (мост) Visco diff 38434AA050
- О чем может рассказать хвост вашей кошки?
- Верветки
- Отчетность бюджетных учреждений при закупках по Закону № 223-ФЗ
- Срок исковой давности как правильно рассчитать
- Дмитрий Патрушев минсельхоз будет ли преемником Путина
- Кто такой Владислав Поздняков? Что такое "Мужское Государство" и почему его признали экстремистским в России?
- Как правильно выбрать машинное масло в Димитровграде?
- Как стать богатым и знаменитым в России?
- Почему фильм "Пипец" (Kick-Ass) стал популярен по всему миру?
- Как стать мудрецом?
- Как правильно установить FreeBSD
- Как стать таким как Путин?
- Где лучше жить - в Димитровграде или в Ульяновске?
- Почему город Димитровград так называется?
- Что такое метамодерн?
- ВАЖНО! Временное ограничение движения автотранспортных средств в Димитровграде
- Тарифы на электроэнергию для майнеров предложено повысить
- Метамодернизм в позднем творчестве В.Г. Сорокина
- ЛитРПГ - последняя отрыжка постмодерна
- "Ричард III и семиотика"
- 3D-визуализация обложки Ridero создаем обложку книги при работе над самиздатом.
- Архитектура метамодерна - говоря о современном искусстве, невозможно не поговорить об архитектуре. В данной статье будет отмечено несколько интересных принципов, характерных для построек "новой волны", столь притягательных и скандальных.
- Литература
- Метамодерн
- Рокер-Прометей против изначального зла в «Песне про советскую милицию» Вени Дркина, Автор: Нина Ищенко, к.ф.н, член Союза Писателей ЛНР - перепубликация из журнала "Топос".
- Как избавиться от комаров? Лучшие типы ловушек.
- Что делать если роблокс вылетает на windows
- Что делать, если ребенок смотрит порно?
- Почему собака прыгает на людей при встрече?
- Какое масло лить в Задний дифференциал (мост) Visco diff 38434AA050
- О чем может рассказать хвост вашей кошки?
- Верветки
- Отчетность бюджетных учреждений при закупках по Закону № 223-ФЗ
- Срок исковой давности как правильно рассчитать
- Дмитрий Патрушев минсельхоз будет ли преемником Путина
- Кто такой Владислав Поздняков? Что такое "Мужское Государство" и почему его признали экстремистским в России?
- Как правильно выбрать машинное масло в Димитровграде?
- Как стать богатым и знаменитым в России?
- Почему фильм "Пипец" (Kick-Ass) стал популярен по всему миру?
- Как стать мудрецом?
- Как правильно установить FreeBSD
- Как стать таким как Путин?
- Где лучше жить - в Димитровграде или в Ульяновске?
- Почему город Димитровград так называется?
- Что такое метамодерн?
- ВАЖНО! Временное ограничение движения автотранспортных средств в Димитровграде
- Тарифы на электроэнергию для майнеров предложено повысить
[править] Работа с базами данных и web-программирование
Часть 3 Что может быть мощнее связки «база данных + интернет»? А если к этому добавить еще и Python... Чтобы почувствовать все это на практике, погрузимся сегодня в пучины SQL-запросов и HTTP-ответов вместе с Сергеем Супруновым.
Мы уже видели, что Python прекрасно подходит для работы с текстом. А что такое интернет-страницы, которые миллионы серверов Apache ежедневно миллиардами отдают на растерзание нашим браузерам? По сути, тот же текст, только немножко «гипер»... А значит, если нам нужно будет формировать html-страницу динамически, то Python прекрасно с этим справится. И никаких препятствий для разработки на нем CGI-сценариев не существует – web-серверу, по большому счету, безразлично, как именно выполняется скрипт и на каком языке он разработан: лишь бы он умел читать данные из потока ввода и переменных окружения да отдавать текст в стандартный выходной поток.
Впрочем, если вы жаждете скорости, то к вашим услугам mod_Python, да и в режиме FastCGI Python работать умеет. Но сейчас у нас разговор все же не о настройках CGI, а о Python, так что вернемся к тому, ради чего мы эту статью начали.
[править] Постановка задачи
С любым вопросом лучше всего разбираться на практическом примере. Поэтому мы будем «плясать» вокруг несложного и, в общем-то, достаточно банального CGI-приложения: гостевой книги. Заодно разберемся с тем, как Python взаимодействует с базами данных, где представляется разумным хранить все наши сообщения.
Но прежде чем перейти к рассмотрению кода (вы найдете его целиком на нашем диске), полезно будет дать кое-какую вводную информацию.
[править] Универсальное «междумордье» CGI
CGI (Common Gateway Interface, общий шлюзовой интерфейс) был разработан как средство взаимодействия HTTP-сервера с программами, которые могут запускаться в операционной системе. Если говорить упрощенно, то CGI, передавая управление такой программе (обычно их именуют cgi-сценариями, хотя это вполне может быть и двоичный файл, разработанный на C/C++), формирует для нее определенное окружение. В частности, параметры HTTP-запроса, полученного от клиента, могут помещаться в определенные переменные окружения или передаваться cgi-программе как аргументы или как входной поток (STDIN). В ответ HTTP-сервер ждет данные, которые cgi-программа должна выдать в стандартный выходной поток (STDOUT), и передает их клиенту.
Таким образом, все, что требуется от cgi-программы, это способность получать необходимую для работы информацию из формируемой HTTP-сервером среды и возвращать ответные данные, соответствующие протоколу HTTP, чтобы web-клиент знал, что с ними делать.
[править] Учимся посылать
Начнем с формирования HTTP-ответа. Чтобы браузер клиента мог его правильно обработать, он должен состоять из заголовка и тела, разделенных пустой строкой. В заголовке передается необходимая служебная информация, например, тип содержимого, его кодировка, указание браузеру запросить другой ресурс (так называемое перенаправление), и т.д. Простейший cgi-сценарий на языке Python может выглядеть так:
#!/usr/bin/Python # -*- coding: utf-8 -*- print 'Content-Type: text/html\n' print '<H3>Если вы это видите, значит все работает</H3>'
Первым оператором print мы формируем минимально необходимый заголовок – браузер клиента обязательно должен знать, каков тип пересылаемых ему данных (в нашем случае это простой текст, соответствующий формату HTML). Не забывайте о дополнительном переводе строки \n, необходимом для отделения заголовка от тела ответа. Ну и далее вы можете передавать любой HTML-код.
Аналогично могут передаваться любые объекты, поддерживаемые клиентом: изображения, звуковые файлы, css-таблицы и т.д. Главное, чтобы значение поля Content-Type (именуемое также MIME-типом) соответствовало содержимому.
[править] Здесь играть, здесь не играть...
Однако какой смысл поручать формирование статических, по сути, страниц cgi-сценарию, если сам HTTP-сервер справится с этим намного лучше? В общем-то никакого. Разве что для общего развития... А вот в чем CGI по-настоящему силен, так это в формировании динамических страниц, содержимое которых зависит от информации, переданной пользователем.
Протокол HTTP предусматривает несколько способов передачи информации от клиента на сервер, называемых методами. Наиболее популярные из них – GET, POST, PUT и HEAD.
Метод GET позволяет вставлять информацию в URL, то есть в строку адреса запрашиваемого ресурса. Когда «Яндекс» вернет вам список искомых страниц, посмотрите на адресную строку в браузере – вот так данные и передаются методом GET. Кстати, обратите внимание на то, как все это кодируется, особенно если вы искали какое-то русское слово.
Если на сервер требуется передать больший объем информации, или ее желательно скрыть от любопытных глаз, используется другой метод – POST. В данном случае в заголовке передается лишь размер пользовательских данных, а сами данные пересылаются в теле запроса.
Метод PUT предназначается для размещения ресурсов на сервере и по соображениям безопасности практически не используется. Ну и, наконец, метод HEAD очень похож на GET, за тем исключением, что сервер в ответ на такой запрос возвращает не весь ресурс, а лишь информацию о нем, такую как дата последнего изменения, помещаемую в заголовке. Обычно используется прокси-серверами для определения «свежести» имеющихся у них данных – стоит ли запрашивать ресурс повторно или можно вернуть клиенту то, что есть в кэше.
Определенная сложность для разработчика cgi-сценария заключается в том, что данные, отправленные различными методами, передаются в сценарий по-разному. Так, информация, поступившая с помощью POST, подается на стандартный вход сценария и может быть считана оттуда, например, с помощью sys.stdin.read(size) или даже функцией raw_input() (хотя во втором случае сложнее контролировать объем принимаемых данных). Количество байт, которые требуется считать, можно получить из переменной окружения CONTENT_LENGTH (например, так: size = os.environ['CONTENT_LENGTH']).
Если клиент использует метод GET, то данные поступят в сценарий через переменную среды QUERY_STRING. Метод, которым данные переданы (нужно же как-то разобраться, где их искать) можно всегда получить из REQUEST_METHOD.
Есть еще один особый случай. Если данные передаются методом GET, но с использованием «индексного» формата, который формируется тегом <ISINDEX>, то в этом случае они кодируются не в виде «переменная=значение&переменная=значение&...», а как «значение+значение+...». И cgi-сценарию они будут переданы, помимо QUERY_STRING, через аргументы командной строки, как если бы сценарий вызывался такой командой:
script.cgi arg1 arg2 arg3
То есть, на этот раз пользовательские данные можно будет получить как sys.argv[1] и т.д.
Как видите, огромное число вариантов, предусмотренных CGI-интерфейсом, которые все должны быть учтены при разработке сценария, может вызвать нервный тик даже у опытных программистов, которые и во сне потихоньку набивают по подушке какой-то код. А если еще вспомнить, что данные передаются в закодированном виде (это англичанам хорошо – взял значение переменной и работай, а нам-то с вами это значение вернется в виде %EC%E4%E0), да еще и о проверке этих данных нужно позаботиться, чтобы какой-нибудь начинающий хакер не попытался заставить наш сервер работать по-своему... Нет, обо всем этом лучше и не вспоминать. Благо у нас есть модуль cgi, в котором все это уже сделано!
Но о нем – чуть позже. Сначала пару слов нужно сказать о HTML-формах.
[править] Формируем формы
Чтобы вам было проще понять рассматриваемый сегодня пример, коротко скажу про то, как же клиент выполняет передачу данных нашему cgi-сценарию. Конечно, продвинутые пользователи могут набрать GET-запрос вручную в адресной строке браузера. Хотя что мелочиться – ведь можно же сформировать и POST-запрос, подключившись телнетом на 80-й порт! Впрочем, обычные пользователи предпочитают более понятные и «осязаемые» способы, например, формы.
Как они выглядят, думаю, каждый знает. Создаются они с помощью тега <FORM>, внутри которого добавляются такие элементы, как <INPUT> (поле ввода) или <TEXTAREA> (многострочный редактор). Этим элементам, если их данные должны быть переданы на сервер, присваиваются имена с помощью атрибута name. Начальное значение задается параметром value и в дальнейшем для «редактируемых» полей может быть изменено пользователем. Когда пользователь нажимает кнопку «Отправить» (надпись на ней, в принципе, можно изменить), то браузер объединяет все данные полей в пары name=value, разделяя их символом &. Затем полученная таким образом строка передается на сервер методом, указанным в атрибуте method тега <FORM>. Путь к сценарию, который будет заниматься ее обработкой, задается атрибутом action этого же тега. Если action не задан, то данные передаются файлу, сформировавшему текущую страничку.
Если что-то не совсем понятно, обратитесь к коду разрабатываемой гостевой книги, который приведен ниже.
[править] Наш спаситель – модуль cgi
Возвращаемся к обработке всего этого добра, которое сотни пользователей уже готовы обрушить на наш бедный сценарий. Мы решили воспользоваться стандартными средствами Python, и здесь все действительно очень просто – импортируйте модуль cgi и, создав объект класса FieldStorage, вы получите через него доступ ко всем данным, переданным пользователем, независимо от используемого метода:
import cgi data = cgi.FieldStorage() for entry in data.keys(): print 'Переменная %s имеет значение %s' % (entry, data[entry].value)
Если вам нужно получить значение определенного поля, это делается так:
field = data['field'].value
Помимо пользовательских данных, объект класса FieldStorage содержит информацию и о полях заголовка (в нашем примере их можно получить из словаря data.headers). MIME-тип данных (передаваемый полем заголовка Content-Type) можно получить из атрибута data.type. Через этот же объект может быть выполнена и загрузка файла.
С помощью методов keys() и has_key() можно выполнять обработку полученных данных в цикле и проверять наличие той или иной переменной. Кстати говоря, проверять наличие переменной во входных данных, прежде чем приступать к их обработке, нужно непременно – ведь запрос формируется клиентом, а кто знает, что у него на уме?
[править] Базируем данные
Итак, получать данные от клиента мы научились. Отправлять тоже умеем. Осталось придумать, как эти данные лучше всего хранить. Конечно, для несложной гостевой книги с небольшой нагрузкой вполне хватило бы и текстовых файлов. Правда, там есть свои сложности – если сразу пять человек захотят высказать свое мнение о вашей крутейшей домашней страничке, то сценарию придется каким-то образом регулировать доступ к файлу-хранилищу (как минимум, обрабатывать ситуацию, если файл уже открыт на запись другим экземпляром сценария). Но зачем нам все эти головные боли? Если мы так ловко отвертелись от необходимости вручную разбирать HTTP-запросы, то неужели не найдем что-то подходящее на этот раз?
Конечно, найдем! И это «что-то» называется системой управления базами данных (в просторечье – СУБД). Теперь наше дело – отправить запрос и получить ответ. Все остальное – уже не наша забота.
Для этого примера я выбрал в качестве «ответственного» за хранение данных сервер баз данных PostgreSQL. Поскольку мы пишем ну очень простую гостевую книгу, то и структура базы будет у нас элементарной – одна таблица с тремя полями: время публикации сообщения, имя автора и, собственно, само сообщение:
admin@toshiba:~$ psql Welcome to psql 8.1.4, the PostgreSQL interactive terminal. guestbook=# create user "www-data" nocreatedb nocreateuser; CREATE ROLE admin=# create database guestbook with owner "www-data"; CREATE DATABASE admin=# \connect guestbook Вы подсоединились к базе данных "guestbook". guestbook=# create table guestbook ( guestbook(# datum timestamp, author varchar, message varchar); CREATE TABLE guestbook=# alter table guestbook owner to "www-data"; ALTER TABLE guestbook=# \q admin@toshiba:~$
Пожалуй, единственное, что здесь нужно пояснить, это почему базе и таблице мы назначили владельцем пользователя www-data. Просто к ним будет обращаться cgi-сценарий, работающий с правами HTTP-сервера Apache, который, в свою очередь, исполняется от имени данного пользователя [в вашем дистрибутиве он может назваться по-другому, – прим. ред.]. А PostgreSQL по умолчанию требует, чтобы имя пользователя в БД совпадало с его системным именем. Мне это кажется достаточно удобным, хотя вы, конечно, можете поступить по-своему.
[править] DB API на страже унификации
Осталось разобраться, как же Python взаимодействует с базами данных. Для этого Python предоставляет DB API – специальный интерфейс, унифицирующий набор методов, которые будут одинаково работать независимо от того, с какой СУБД мы взаимодействуем. Для работы с PostgreSQL нам понадобится модуль PyPgSQL (в стандартной поставке его может не оказаться, но ваш менеджер пакетов наверняка будет в курсе, как его установить; кстати, это не единственный модуль – у вас, возможно, будет PyGreSQL, который работает ничуть ни хуже и с теми же самыми методами).
DB API определяет стандартные методы работы с базами данных, так что, какой бы модуль вы ни загрузили и с какой бы СУБД ни работали (будь то MySQL, PostgreSQL, SQLite или что-то еще), меняться будет только имя модуля. Главное, чтобы используемый модуль соответствовал DB API. Рассмотрим коротко основные методы:
conn = connect(dsn='localhost', user='admin', password='superparol', database='mydb')
Так осуществляется подключение к базе. В зависимости от ситуации, вам может потребоваться указать только нужные параметры (например, имя хоста 'localhost' подразумевается по умолчанию).
cur = conn.cursor()
Курсоры поддерживаются далеко не всеми СУБД, но для общности в DB API они введены и, в случае необходимости, должны эмулироваться модулями сопряжения искусственно. Так что не забывайте отправлять все ваши запросы через курсор.
cur.execute('''SELECT * FROM mytable''')
Так выполняется SQL-запрос. Если в строке запроса используются знакоместа %s, то вторым параметром передается список переменных-значений, причем в SQL-запросе знакоместа не требуется окружать апострофами – модуль сделает это самостоятельно в зависимости от типа переменной.
cur.fetchall()
Возвращает двумерный список (строки – поля) полученных от СУБД данных. Существуют и другие методы, ознакомиться с которыми вы сможете в документации или с помощью знакомой вам функции dir() да пары-тройки несложных экспериментов.
[править] Закрепляем на практике
Перейдем к рассмотрению нашего примера. Начнем стандартно – укажем кодировку, подключим нужные модули:
#!/usr/bin/Python # -*- coding: utf-8 -*- import PyPgSQL.PgSQL as pg import cgi
Далее, определим две функции. Первая будет отвечать за добавление нового сообщения в базу:
def addMessage(author, message): db = pg.connect(database="guestbook") c = db.cursor() c.execute("""INSERT INTO guestbook (datum, author, message) VALUES ('now', %s, %s);""", (author, message)) c.close() db.commit() db.close() print "Content-Type: text/html" print "Location: ?#form\n"
Как видите, все очень даже логично: устанавливаем соединение с БД (поскольку в нашем случае подключение выполняется с именем текущего системного пользователя, то достаточно указать только имя базы), создаем курсор (в PostgreSQL они не применяются, но они эмулируются каждым модулем, претендующим на соответствие DB API), выполняется запрос, закрывается курсор, фиксируются изменения (PostgreSQL использует транзакции, поэтому выполнение метода commit() обязательно, иначе ваши изменения не будут сохранены), и, наконец, закрываем само соединение с базой. В поле datum заносим значение встроенной переменной PostgreSQL – now, которая каждый раз заменяется текущим значением даты и времени.
Ну и печать заголовка «Location» выполняется для того, чтобы перенаправить пользователя на этот же сценарий, но уже без параметров – мы же должны показать клиенту, что он на самом деле ввел? (Якорь #form используется, чтобы автоматически прокрутить страничку на последнее сообщение).
Вторая функция будет отвечать за вывод на экран уже оставленныхв книге записей, а также за форму, с помощью которой можно будет добавить и свое высказывание:
def showGB(): db = pg.connect(database="guestbook") c = db.cursor() c.execute("""SELECT datum, author, message FROM guestbook ORDER BY datum;""") res = c.fetchall() c.close() db.close()
В этом фрагменте мы выбираем все строки из нашей таблицы данных, сортируя их по дате. Результат сохраняется в переменной res, с которой и будем работать. Теперь осталось лишь аккуратненько разложить наши данные по табличкам и вывести их на экран:
print "Content-Type: text/html\n" print "<H1 style='color:#7777FF'><U>Велькам к нам в гости!</U></H1>" for item in res: print """<TABLE width='90%%'> <TR><TD><SMALL>Товарищ <B>%s</B> поведалнам следующее:</SMALL> <TD align='right'><SMALL>%s</SMALL> <TR><TD style='background-color:#DDDDFF' colspan='2'>%s </TABLE>""" % (item[1], str(item[0])[:19], item[2]) print "<HR><A name='form'><H3>Присоединяйтесь к дискуссии:</H3>" print """<FORM method='GET'> Ваше имя: <INPUT type='text' name='author'><BR> Что вы думаете по этому поводу:<BR> <TEXTAREA name='message' rows='5' cols='80'></TEXTAREA><BR> <INPUT type='submit' value='Отправить'> </FORM>"""
Смысл конструкции str(item[0])[:19] заключается в том, чтобы в строке времени отсечь ненужные нам миллисекунды, которые сохраняются в поле типа timestamp. После всех опубликованных сообщений выводим форму добавления нового, чтобы каждый мог присоединиться к нашей дискуссии. Кстати, в теге <FORM> мы не указали параметр action, поскольку данные будут передаваться на обработку этому же сценарию (благодаря чему имя сценарию можно присвоить любое). Наконец, последний фрагмент:
form = cgi.FieldStorage() if form.has_key("message") and form.has_key("author"): author = cgi.escape(form["author"].value) message = cgi.escape(form["message"].value) message = message.replace("\n", "<BR>") addMessage(author, message) else: showGB()
Создаем FieldStorage-объект, и если в нем есть заполненные поля message и author (то есть запрос был сформирован из заполненной пользователем формы), то, немножко их обработав (функция cgi.escape() заменяет все «неблагонадежные» символы – например, < – их стандартными SGML-сущностями, в данном случае – <), передаем функции addMessage(). Обработка нужна для того, чтобы злоумышленник не мог ввести в поле сообщения или имени автора что-нибудь такое:
<SCRIPT>alert('Да пошли вы все!');</SCRIPT>
К слову, пренебрегать проверкой введенных данных ни в коем случае нельзя. Зайдите как-нибудь на securitylab.ru и посмотрите, сколько уязвимостей типа «XSS» обнаруживается каждый месяц! Так что шутки шутками, но последствия могут быть очень серьезными.
[править] Куда же нам теперь идти?
Итак, что-то вполне работоспособное у нас есть (см. рисунок). Но как вы может догадаться, наша гостевая очень далека от совершенства. Что еще можно сделать? Ну, например, разбить на страницы. Пока сообщений в ней будет не больше дюжины, сойдет и так. А когда их число дойдет до сотни, то редкий пользователь дождется окончания загрузки всех данных. Можно дать пользователям возможность использовать некоторые HTML-теги, чтобы их сообщения выглядели более красочно. Можно добавить смайликов... А можно даже сделать модуль администрирования, позволяющий редактировать или удалять сообщения, а также отвечать на них. Так что работы непочатый край. Дерзайте – не буду вам мешать.
[править] Некоторые распространённые MIME-типы
MIME-тип | Описание |
---|---|
text/plain | Простой текст |
text/html | HTML-страница |
image/gif | Изображение GIF |
video/mpeg | Видео-файл в формате MPEG |
application/msword | Документ MS Word |