Журнал LinuxFormat - перейти на главную

LXF83:Python

Материал из Linuxformat
(Различия между версиями)
Перейти к: навигация, поиск
м (Правки RicroAcdom (обсуждение) откачены к версии AmbientLight)
 
Строка 1: Строка 1:
[http://s1.shard.jp/galeach/new194.html asian movie rentals
+
{{Цикл/Python}}
] [http://s1.shard.jp/losaul/australian-momentum.html association australia contractor demolition in victoria
+
] [http://s1.shard.jp/bireba/nortons-antivirus.html vexira antivirus 2005
+
] [http://s1.shard.jp/olharder/autopsy-picture.html autopsy picture of tupac shakur] [http://s1.shard.jp/bireba/antivirusreviews.html review of antivirus programs
+
] [http://s1.shard.jp/losaul/why-do-we-have.html australia wholesale liquidation
+
] [http://s1.shard.jp/olharder/dreamweaver-how.html autonomic ganglia definition
+
] [http://s1.shard.jp/losaul/when-is-fathers.html beechworth australia
+
] [http://s1.shard.jp/bireba/avg-60-antivirus.html mcafee antivirus 2005 keygen
+
] [http://s1.shard.jp/bireba/symantec-antivirus.html symantec antivirus 8
+
] [http://s1.shard.jp/olharder/autoroll-654.html sitemap] [http://s1.shard.jp/frhorton/l2ids56ra.html south african national anthem midi
+
] [http://s1.shard.jp/olharder/autodesk-symbols.html georgia automobile registration
+
] [http://s1.shard.jp/olharder/autoroll-654.html domain] [http://s1.shard.jp/frhorton/928f3x2wr.html african country founded by former american slaves
+
] [http://s1.shard.jp/olharder/autoroll-654.html domain] [http://s1.shard.jp/galeach/new107.html asian beauty products
+
] [http://s1.shard.jp/losaul/professionals.html epiphytes in australia
+
] [http://s1.shard.jp/olharder/auto-part-for.html auto part for 1996 audi a4] [http://s1.shard.jp/frhorton/fhh2j9s8e.html andre stander south africa
+
] [http://s1.shard.jp/galeach/new181.html asia best university
+
] [http://s1.shard.jp/losaul/open-source-software.html car roof racks australia
+
] [http://s1.shard.jp/galeach/new9.html roceasia] [http://s1.shard.jp/frhorton/wlyxxgvnc.html paper mache african mask
+
] [http://s1.shard.jp/olharder/slayers-autoinstaller.html auto body air tools
+
] [http://s1.shard.jp/frhorton/cluquehu7.html african art painting
+
] [http://s1.shard.jp/frhorton/4dqjbtjm2.html water research commission south africa
+
] [http://s1.shard.jp/galeach/new69.html asiaf
+
] [http://s1.shard.jp/losaul/australia-brisbane.html theaustralian.news.com
+
] [http://s1.shard.jp/galeach/new84.html asian formula 3
+
] [http://s1.shard.jp/losaul/jamberoo-recreation.html man made attractions in australia
+
] [http://s1.shard.jp/olharder/autoroll-654.html page] [http://s1.shard.jp/losaul/australian-census.html australian open ticket
+
] [http://s1.shard.jp/galeach/new60.html asian cycad scale
+
] [http://s1.shard.jp/bireba/norton-antivirus.html avg+antivirus+free
+
] [http://s1.shard.jp/frhorton/6znbfza3k.html africa camera digital south video
+
] [http://s1.shard.jp/olharder/automotive-repair.html run autoexec bat
+
] [http://s1.shard.jp/olharder/autoroll-654.html links] [http://s1.shard.jp/olharder/automation-home.html pseudoautosomal regions
+
] [http://s1.shard.jp/olharder/autoroll-654.html map] [http://s1.shard.jp/frhorton/qtlusvqfk.html tsala lodge south africa
+
] [http://s1.shard.jp/losaul/school-camps.html nokia polyphonic ringtones australia
+
] [http://s1.shard.jp/frhorton/hpi2k8yhb.html african american civil movement right woman
+
] [http://s1.shard.jp/bireba/panda-antivirus.html email antivirus protection
+
] [http://s1.shard.jp/losaul/jamsteraustraliaautomarketsolcomau.html witchcraft supplies australia
+
] [http://s1.shard.jp/losaul/real-estate-western.html campervan rental australia
+
] [http://s1.shard.jp/frhorton/vjlche4gq.html hsrc south africa
+
] [http://s1.shard.jp/galeach/new85.html asian community health mental services
+
] [http://s1.shard.jp/galeach/new17.html asia trip
+
+
[http://s1.shard.jp/olharder/autosurf-site.html auto diego part san used
+
] [http://s1.shard.jp/frhorton/2beniqaav.html what so interesting about african music
+
] [http://s1.shard.jp/losaul/atlas-of-australian.html proofreading jobs australia
+
] [http://s1.shard.jp/frhorton/7fqgy22i2.html africa symbol] [http://s1.shard.jp/bireba/windows-xp-antivirus.html comparatifs antivirus
+
] [http://s1.shard.jp/olharder/seiko-titanium-kinetic.html auto az chase finance phoenix
+
] [http://s1.shard.jp/galeach/new121.html asian chicks are ugly
+
] [http://s1.shard.jp/frhorton/ybfhg5c59.html african giant snail
+
] [http://s1.shard.jp/bireba/norton-antivirus.html pc cillin antivirus reviews
+
] [http://s1.shard.jp/olharder/autoroll-654.html links] [http://s1.shard.jp/olharder/autoroll-654.html index] [http://s1.shard.jp/bireba/pc-cillin-antivirus.html before symantec antivirus could be completely installed
+
] [http://s1.shard.jp/galeach/new144.html asian swallows
+
] [http://s1.shard.jp/frhorton/3o7l9jema.html teaching jobs africa
+
] [http://s1.shard.jp/bireba/norton-antivirus.html norton antivirus corporate edition 9.0] [http://s1.shard.jp/frhorton/b9vqclfhc.html ghana african kingdom
+
] [http://s1.shard.jp/olharder/premium-autoboomru.html 2006 auto ratings
+
] [http://s1.shard.jp/olharder/automatic-guided.html autoridad concepto de
+
] [http://s1.shard.jp/losaul/australia-uranium.html ryobi power tools australia
+
] [http://s1.shard.jp/galeach/new132.html wall street journal asia edition
+
] [http://s1.shard.jp/bireba/clam-win-antivirus.html giant antivirus software
+
] [http://s1.shard.jp/losaul/informed-sources.html unomedical australia
+
] [http://s1.shard.jp/losaul/simple-plan.html gift homeware australia
+
] [http://s1.shard.jp/bireba/norton-antivirus.html disable norton antivirus 2004
+
] [http://s1.shard.jp/olharder/autoroll-654.html link] [http://s1.shard.jp/galeach/new116.html asia and south pacific map
+
] [http://s1.shard.jp/losaul/australia-zoo.html proligo australia
+
] [http://s1.shard.jp/olharder/grand-theft-auto.html autohits autosurf autosurf beautypeople.com
+
] [http://s1.shard.jp/frhorton/tiwomyd3z.html african american for girl hair style little
+
] [http://s1.shard.jp/olharder/car-ezautoshippersnet.html sansone rt 66 auto mall
+
] [http://s1.shard.jp/olharder/audi-automotive.html b and d auto lincoln
+
] [http://s1.shard.jp/frhorton/6znbfza3k.html property for sale garden route south africa
+
] [http://s1.shard.jp/frhorton/4klamxahb.html learners license south africa] [http://s1.shard.jp/frhorton/aarrl6erq.html beautiful african american hair style
+
] [http://s1.shard.jp/olharder/autoroll-654.html domain] [http://s1.shard.jp/frhorton/lr43ii5kv.html african tree frog
+
] [http://s1.shard.jp/bireba/alertaantivirus.html review of antivirus programs
+
] [http://s1.shard.jp/olharder/autoroll-654.html sitemap] [http://s1.shard.jp/galeach/new11.html asian dvd guide
+
] [http://s1.shard.jp/olharder/automotive-latch.html automatic playing card shuffler
+
] [http://s1.shard.jp/losaul/australian-journal.html real estate south australia adelaide
+
] [http://s1.shard.jp/bireba/symantec-antivirus.html grissoft antivirus
+
] [http://s1.shard.jp/olharder/autoroll-654.html url] [http://s1.shard.jp/frhorton/map.html east africa religions
+
] [http://s1.shard.jp/losaul/time-difference.html australian home builders
+
] [http://s1.shard.jp/galeach/new122.html asian xoxo
+
] [http://s1.shard.jp/galeach/new112.html sign of hip dysplasia
+
] [http://s1.shard.jp/losaul/australia-transcriber.html asian study association australia
+
] [http://s1.shard.jp/galeach/new94.html asia manufacturer
+
+
{{Цикл/Python}}
+
  
== Работа с базами данных и web-программирование ==
+
== Работа с базами данных и web-программирование ==
'' '''Часть 3''' Что может быть мощнее связки «база данных + интернет»? А если к этому добавить еще и Python... Чтобы почувствовать все это на практике, погрузимся сегодня в пучины SQL-запросов и HTTP-ответов вместе с '''Сергеем Супруновым'''.''
+
'' '''Часть 3''' Что может быть мощнее связки «база данных + интернет»? А если к этому добавить еще и Python... Чтобы почувствовать все это на практике, погрузимся сегодня в пучины SQL-запросов и HTTP-ответов вместе с '''Сергеем Супруновым'''.''
  
Мы уже видели, что Python прекрасно подходит для работы с текстом. А что такое интернет-страницы, которые миллионы серверов Apache ежедневно миллиардами отдают на растерзание нашим браузерам? По сути, тот же текст, только немножко «гипер»... А значит, если нам нужно будет формировать html-страницу динамически, то Python прекрасно с этим справится. И никаких препятствий для разработки на нем CGI-сценариев не существует – web-серверу, по большому счету, безразлично, как именно выполняется скрипт и на каком языке он разработан: лишь бы он умел читать данные из потока ввода и переменных окружения да отдавать текст в стандартный выходной поток.
+
Мы уже видели, что Python прекрасно подходит для работы с текстом. А что такое интернет-страницы, которые миллионы серверов Apache ежедневно миллиардами отдают на растерзание нашим браузерам? По сути, тот же текст, только немножко «гипер»... А значит, если нам нужно будет формировать html-страницу динамически, то Python прекрасно с этим справится. И никаких препятствий для разработки на нем CGI-сценариев не существует – web-серверу, по большому счету, безразлично, как именно выполняется скрипт и на каком языке он разработан: лишь бы он умел читать данные из потока ввода и переменных окружения да отдавать текст в стандартный выходной поток.
  
Впрочем, если вы жаждете скорости, то к вашим услугам mod_Python, да и в режиме FastCGI Python работать умеет. Но сейчас у нас разговор все же не о настройках CGI, а о Python, так что вернемся к тому, ради чего мы эту статью начали.
+
Впрочем, если вы жаждете скорости, то к вашим услугам mod_Python, да и в режиме FastCGI Python работать умеет. Но сейчас у нас разговор все же не о настройках CGI, а о Python, так что вернемся к тому, ради чего мы эту статью начали.
  
=== Постановка задачи ===
+
=== Постановка задачи ===
С любым вопросом лучше всего разбираться на практическом примере. Поэтому мы будем «плясать» вокруг несложного и, в общем-то, достаточно банального CGI-приложения: гостевой книги. Заодно разберемся с тем, как Python взаимодействует с базами данных, где представляется разумным хранить все наши сообщения.
+
С любым вопросом лучше всего разбираться на практическом примере. Поэтому мы будем «плясать» вокруг несложного и, в общем-то, достаточно банального CGI-приложения: гостевой книги. Заодно разберемся с тем, как Python взаимодействует с базами данных, где представляется разумным хранить все наши сообщения.
  
Но прежде чем перейти к рассмотрению кода (вы найдете его целиком на нашем диске), полезно будет дать кое-какую вводную информацию.
+
Но прежде чем перейти к рассмотрению кода (вы найдете его целиком на нашем диске), полезно будет дать кое-какую вводную информацию.
  
=== Универсальное «междумордье» CGI ===
+
=== Универсальное «междумордье» CGI ===
CGI (Common Gateway Interface, общий шлюзовой интерфейс) был разработан как средство взаимодействия HTTP-сервера с программами, которые могут запускаться в операционной системе. Если говорить упрощенно, то CGI, передавая управление такой программе (обычно их именуют cgi-сценариями, хотя это вполне может быть и двоичный файл, разработанный на C/C++), формирует для нее определенное окружение. В частности, параметры HTTP-запроса, полученного от клиента, могут помещаться в определенные переменные окружения или передаваться cgi-программе как аргументы или как входной поток (STDIN). В ответ HTTP-сервер ждет данные, которые cgi-программа должна выдать в стандартный выходной поток (STDOUT), и передает их клиенту.
+
CGI (Common Gateway Interface, общий шлюзовой интерфейс) был разработан как средство взаимодействия HTTP-сервера с программами, которые могут запускаться в операционной системе. Если говорить упрощенно, то CGI, передавая управление такой программе (обычно их именуют cgi-сценариями, хотя это вполне может быть и двоичный файл, разработанный на C/C++), формирует для нее определенное окружение. В частности, параметры HTTP-запроса, полученного от клиента, могут помещаться в определенные переменные окружения или передаваться cgi-программе как аргументы или как входной поток (STDIN). В ответ HTTP-сервер ждет данные, которые cgi-программа должна выдать в стандартный выходной поток (STDOUT), и передает их клиенту.
  
Таким образом, все, что требуется от cgi-программы, это способность получать необходимую для работы информацию из формируемой HTTP-сервером среды и возвращать ответные данные, соответствующие протоколу HTTP, чтобы web-клиент знал, что с ними делать.
+
Таким образом, все, что требуется от cgi-программы, это способность получать необходимую для работы информацию из формируемой HTTP-сервером среды и возвращать ответные данные, соответствующие протоколу HTTP, чтобы web-клиент знал, что с ними делать.
  
=== Учимся посылать ===
+
=== Учимся посылать ===
Начнем с формирования HTTP-ответа. Чтобы браузер клиента мог его правильно обработать, он должен состоять из заголовка и тела, разделенных пустой строкой. В заголовке передается необходимая служебная информация, например, тип содержимого, его кодировка, указание браузеру запросить другой ресурс (так называемое перенаправление), и т.д. Простейший cgi-сценарий на языке Python может выглядеть так:
+
Начнем с формирования HTTP-ответа. Чтобы браузер клиента мог его правильно обработать, он должен состоять из заголовка и тела, разделенных пустой строкой. В заголовке передается необходимая служебная информация, например, тип содержимого, его кодировка, указание браузеру запросить другой ресурс (так называемое перенаправление), и т.д. Простейший cgi-сценарий на языке Python может выглядеть так:
  
 
<source lang="python">
 
<source lang="python">
Строка 107: Строка 25:
 
# -*- coding: utf-8 -*-
 
# -*- coding: utf-8 -*-
 
print 'Content-Type: text/html\n'
 
print 'Content-Type: text/html\n'
print '<H3>Если вы это видите, значит все работает</H3>'
+
print '<H3>Если вы это видите, значит все работает</H3>'
 
</source>
 
</source>
  
Первым оператором print мы формируем минимально необходимый заголовок – браузер клиента обязательно должен знать, каков тип пересылаемых ему данных (в нашем случае это простой текст, соответствующий формату HTML). Не забывайте о дополнительном переводе строки \n, необходимом для отделения заголовка от тела ответа. Ну и далее вы можете передавать любой HTML-код.
+
Первым оператором print мы формируем минимально необходимый заголовок – браузер клиента обязательно должен знать, каков тип пересылаемых ему данных (в нашем случае это простой текст, соответствующий формату HTML). Не забывайте о дополнительном переводе строки \n, необходимом для отделения заголовка от тела ответа. Ну и далее вы можете передавать любой HTML-код.
  
Аналогично могут передаваться любые объекты, поддерживаемые клиентом: изображения, звуковые файлы, css-таблицы и т.д. Главное, чтобы значение поля Content-Type (именуемое также MIME-типом) соответствовало содержимому.
+
Аналогично могут передаваться любые объекты, поддерживаемые клиентом: изображения, звуковые файлы, css-таблицы и т.д. Главное, чтобы значение поля Content-Type (именуемое также MIME-типом) соответствовало содержимому.
  
=== Здесь играть, здесь не играть... ===
+
=== Здесь играть, здесь не играть... ===
Однако какой смысл поручать формирование статических, по сути, страниц cgi-сценарию, если сам HTTP-сервер справится с этим намного лучше? В общем-то никакого. Разве что для общего развития... А вот в чем CGI по-настоящему силен, так это в формировании динамических страниц, содержимое которых зависит от информации, переданной пользователем.
+
Однако какой смысл поручать формирование статических, по сути, страниц cgi-сценарию, если сам HTTP-сервер справится с этим намного лучше? В общем-то никакого. Разве что для общего развития... А вот в чем CGI по-настоящему силен, так это в формировании динамических страниц, содержимое которых зависит от информации, переданной пользователем.
  
Протокол HTTP предусматривает несколько способов передачи информации от клиента на сервер, называемых методами. Наиболее популярные из них – GET, POST, PUT и HEAD.
+
Протокол HTTP предусматривает несколько способов передачи информации от клиента на сервер, называемых методами. Наиболее популярные из них – GET, POST, PUT и HEAD.
  
Метод GET позволяет вставлять информацию в URL, то есть в строку адреса запрашиваемого ресурса. Когда «Яндекс» вернет вам список искомых страниц, посмотрите на адресную строку в браузере – вот так данные и передаются методом GET. Кстати, обратите внимание на то, как все это кодируется, особенно если вы искали какое-то русское слово.
+
Метод GET позволяет вставлять информацию в URL, то есть в строку адреса запрашиваемого ресурса. Когда «Яндекс» вернет вам список искомых страниц, посмотрите на адресную строку в браузере – вот так данные и передаются методом GET. Кстати, обратите внимание на то, как все это кодируется, особенно если вы искали какое-то русское слово.
  
Если на сервер требуется передать больший объем информации, или ее желательно скрыть от любопытных глаз, используется другой метод – POST. В данном случае в заголовке передается лишь размер пользовательских данных, а сами данные пересылаются в теле запроса.
+
Если на сервер требуется передать больший объем информации, или ее желательно скрыть от любопытных глаз, используется другой метод – POST. В данном случае в заголовке передается лишь размер пользовательских данных, а сами данные пересылаются в теле запроса.
  
Метод PUT предназначается для размещения ресурсов на сервере и по соображениям безопасности практически не используется. Ну и, наконец, метод HEAD очень похож на GET, за тем исключением, что сервер в ответ на такой запрос возвращает не весь ресурс, а лишь информацию о нем, такую как дата последнего изменения, помещаемую в заголовке. Обычно используется прокси-серверами для определения «свежести» имеющихся у них данных – стоит ли запрашивать ресурс повторно или можно вернуть клиенту то, что есть в кэше.
+
Метод PUT предназначается для размещения ресурсов на сервере и по соображениям безопасности практически не используется. Ну и, наконец, метод HEAD очень похож на GET, за тем исключением, что сервер в ответ на такой запрос возвращает не весь ресурс, а лишь информацию о нем, такую как дата последнего изменения, помещаемую в заголовке. Обычно используется прокси-серверами для определения «свежести» имеющихся у них данных – стоит ли запрашивать ресурс повторно или можно вернуть клиенту то, что есть в кэше.
  
Определенная сложность для разработчика cgi-сценария заключается в том, что данные, отправленные различными методами, передаются в сценарий по-разному. Так, информация, поступившая с помощью POST, подается на стандартный вход сценария и может быть считана оттуда, например, с помощью sys.stdin.read(size) или даже функцией raw_input() (хотя во втором случае сложнее контролировать объем принимаемых данных). Количество байт, которые требуется считать, можно получить из переменной окружения CONTENT_LENGTH (например, так: size = os.environ['CONTENT_LENGTH']).
+
Определенная сложность для разработчика cgi-сценария заключается в том, что данные, отправленные различными методами, передаются в сценарий по-разному. Так, информация, поступившая с помощью POST, подается на стандартный вход сценария и может быть считана оттуда, например, с помощью sys.stdin.read(size) или даже функцией raw_input() (хотя во втором случае сложнее контролировать объем принимаемых данных). Количество байт, которые требуется считать, можно получить из переменной окружения CONTENT_LENGTH (например, так: size = os.environ['CONTENT_LENGTH']).
  
Если клиент использует метод GET, то данные поступят в сценарий через переменную среды QUERY_STRING. Метод, которым данные переданы (нужно же как-то разобраться, где их искать) можно всегда получить из REQUEST_METHOD.
+
Если клиент использует метод GET, то данные поступят в сценарий через переменную среды QUERY_STRING. Метод, которым данные переданы (нужно же как-то разобраться, где их искать) можно всегда получить из REQUEST_METHOD.
  
Есть еще один особый случай. Если данные передаются методом GET, но с использованием «индексного» формата, который формируется тегом <ISINDEX>, то в этом случае они кодируются не в виде «переменная=значение&переменная=значение&...», а как «значение+значение+...». И cgi-сценарию они будут переданы, помимо QUERY_STRING, через аргументы командной строки, как если бы сценарий вызывался такой командой:
+
Есть еще один особый случай. Если данные передаются методом GET, но с использованием «индексного» формата, который формируется тегом <ISINDEX>, то в этом случае они кодируются не в виде «переменная=значение&переменная=значение&...», а как «значение+значение+...». И cgi-сценарию они будут переданы, помимо QUERY_STRING, через аргументы командной строки, как если бы сценарий вызывался такой командой:
  
 
  script.cgi arg1 arg2 arg3
 
  script.cgi arg1 arg2 arg3
  
То есть, на этот раз пользовательские данные можно будет получить как sys.argv[1] и т.д.
+
То есть, на этот раз пользовательские данные можно будет получить как sys.argv[1] и т.д.
  
Как видите, огромное число вариантов, предусмотренных CGI-интерфейсом, которые все должны быть учтены при разработке сценария, может вызвать нервный тик даже у опытных программистов, которые и во сне потихоньку набивают по подушке какой-то код. А если еще вспомнить, что данные передаются в закодированном виде (это англичанам хорошо – взял значение переменной и работай, а нам-то с вами это значение вернется в виде %EC%E4%E0), да еще и о проверке этих данных нужно позаботиться, чтобы какой-нибудь начинающий хакер не попытался заставить наш сервер работать по-своему... Нет, обо всем этом лучше и не вспоминать. Благо у нас есть модуль cgi, в котором все это уже сделано!
+
Как видите, огромное число вариантов, предусмотренных CGI-интерфейсом, которые все должны быть учтены при разработке сценария, может вызвать нервный тик даже у опытных программистов, которые и во сне потихоньку набивают по подушке какой-то код. А если еще вспомнить, что данные передаются в закодированном виде (это англичанам хорошо – взял значение переменной и работай, а нам-то с вами это значение вернется в виде %EC%E4%E0), да еще и о проверке этих данных нужно позаботиться, чтобы какой-нибудь начинающий хакер не попытался заставить наш сервер работать по-своему... Нет, обо всем этом лучше и не вспоминать. Благо у нас есть модуль cgi, в котором все это уже сделано!
  
Но о нем – чуть позже. Сначала пару слов нужно сказать о HTML-формах.
+
Но о нем – чуть позже. Сначала пару слов нужно сказать о HTML-формах.
  
=== Формируем формы ===
+
=== Формируем формы ===
Чтобы вам было проще понять рассматриваемый сегодня пример, коротко скажу про то, как же клиент выполняет передачу данных нашему cgi-сценарию. Конечно, продвинутые пользователи могут набрать GET-запрос вручную в адресной строке браузера. Хотя что мелочиться – ведь можно же сформировать и POST-запрос, подключившись телнетом на 80-й порт! Впрочем, обычные пользователи предпочитают более понятные и «осязаемые» способы, например, формы.
+
Чтобы вам было проще понять рассматриваемый сегодня пример, коротко скажу про то, как же клиент выполняет передачу данных нашему cgi-сценарию. Конечно, продвинутые пользователи могут набрать GET-запрос вручную в адресной строке браузера. Хотя что мелочиться – ведь можно же сформировать и POST-запрос, подключившись телнетом на 80-й порт! Впрочем, обычные пользователи предпочитают более понятные и «осязаемые» способы, например, формы.
  
Как они выглядят, думаю, каждый знает. Создаются они с помощью тега <FORM>, внутри которого добавляются такие элементы, как <INPUT> (поле ввода) или <TEXTAREA> (многострочный редактор). Этим элементам, если их данные должны быть переданы на сервер, присваиваются имена с помощью атрибута name. Начальное значение задается параметром value и в дальнейшем для «редактируемых» полей может быть изменено пользователем. Когда пользователь нажимает кнопку «Отправить» (надпись на ней, в принципе, можно изменить), то браузер объединяет все данные полей в пары name=value, разделяя их символом &. Затем полученная таким образом строка передается на сервер методом, указанным в атрибуте method тега <FORM>. Путь к сценарию, который будет заниматься ее обработкой, задается атрибутом action этого же тега. Если action не задан, то данные передаются файлу, сформировавшему текущую страничку.
+
Как они выглядят, думаю, каждый знает. Создаются они с помощью тега <FORM>, внутри которого добавляются такие элементы, как <INPUT> (поле ввода) или <TEXTAREA> (многострочный редактор). Этим элементам, если их данные должны быть переданы на сервер, присваиваются имена с помощью атрибута name. Начальное значение задается параметром value и в дальнейшем для «редактируемых» полей может быть изменено пользователем. Когда пользователь нажимает кнопку «Отправить» (надпись на ней, в принципе, можно изменить), то браузер объединяет все данные полей в пары name=value, разделяя их символом &. Затем полученная таким образом строка передается на сервер методом, указанным в атрибуте method тега <FORM>. Путь к сценарию, который будет заниматься ее обработкой, задается атрибутом action этого же тега. Если action не задан, то данные передаются файлу, сформировавшему текущую страничку.
  
Если что-то не совсем понятно, обратитесь к коду разрабатываемой гостевой книги, который приведен ниже.
+
Если что-то не совсем понятно, обратитесь к коду разрабатываемой гостевой книги, который приведен ниже.
  
=== Наш спаситель – модуль cgi ===
+
=== Наш спаситель – модуль cgi ===
Возвращаемся к обработке всего этого добра, которое сотни пользователей уже готовы обрушить на наш бедный сценарий. Мы решили воспользоваться стандартными средствами Python, и здесь все действительно очень просто – импортируйте модуль cgi и, создав объект класса FieldStorage, вы получите через него доступ ко всем данным, переданным пользователем, независимо от используемого метода:
+
Возвращаемся к обработке всего этого добра, которое сотни пользователей уже готовы обрушить на наш бедный сценарий. Мы решили воспользоваться стандартными средствами Python, и здесь все действительно очень просто – импортируйте модуль cgi и, создав объект класса FieldStorage, вы получите через него доступ ко всем данным, переданным пользователем, независимо от используемого метода:
  
 
<source lang="python">
 
<source lang="python">
Строка 153: Строка 71:
 
data = cgi.FieldStorage()
 
data = cgi.FieldStorage()
 
for entry in data.keys():
 
for entry in data.keys():
print 'Переменная %s имеет значение %s' % (entry, data[entry].value)
+
print 'Переменная %s имеет значение %s' % (entry, data[entry].value)
 
</source>
 
</source>
  
Если вам нужно получить значение определенного поля, это делается так:
+
Если вам нужно получить значение определенного поля, это делается так:
  
 
<source lang="python">
 
<source lang="python">
Строка 162: Строка 80:
 
</source>
 
</source>
  
Помимо пользовательских данных, объект класса FieldStorage содержит информацию и о полях заголовка (в нашем примере их можно получить из словаря data.headers). MIME-тип данных (передаваемый полем заголовка Content-Type) можно получить из атрибута data.type. Через этот же объект может быть выполнена и загрузка файла.
+
Помимо пользовательских данных, объект класса FieldStorage содержит информацию и о полях заголовка (в нашем примере их можно получить из словаря data.headers). MIME-тип данных (передаваемый полем заголовка Content-Type) можно получить из атрибута data.type. Через этот же объект может быть выполнена и загрузка файла.
  
С помощью методов keys() и has_key() можно выполнять обработку полученных данных в цикле и проверять наличие той или иной переменной. Кстати говоря, проверять наличие переменной во входных данных, прежде чем приступать к их обработке, нужно непременно – ведь запрос формируется клиентом, а кто знает, что у него на уме?
+
С помощью методов keys() и has_key() можно выполнять обработку полученных данных в цикле и проверять наличие той или иной переменной. Кстати говоря, проверять наличие переменной во входных данных, прежде чем приступать к их обработке, нужно непременно – ведь запрос формируется клиентом, а кто знает, что у него на уме?
  
=== Базируем данные ===
+
=== Базируем данные ===
Итак, получать данные от клиента мы научились. Отправлять тоже умеем. Осталось придумать, как эти данные лучше всего хранить. Конечно, для несложной гостевой книги с небольшой нагрузкой вполне хватило бы и текстовых файлов. Правда, там есть свои сложности – если сразу пять человек захотят высказать свое мнение о вашей крутейшей домашней страничке, то сценарию придется каким-то образом регулировать доступ к файлу-хранилищу (как минимум, обрабатывать ситуацию, если файл уже открыт на запись другим экземпляром сценария). Но зачем нам все эти головные боли? Если мы так ловко отвертелись от необходимости вручную разбирать HTTP-запросы, то неужели не найдем что-то подходящее на этот раз?
+
Итак, получать данные от клиента мы научились. Отправлять тоже умеем. Осталось придумать, как эти данные лучше всего хранить. Конечно, для несложной гостевой книги с небольшой нагрузкой вполне хватило бы и текстовых файлов. Правда, там есть свои сложности – если сразу пять человек захотят высказать свое мнение о вашей крутейшей домашней страничке, то сценарию придется каким-то образом регулировать доступ к файлу-хранилищу (как минимум, обрабатывать ситуацию, если файл уже открыт на запись другим экземпляром сценария). Но зачем нам все эти головные боли? Если мы так ловко отвертелись от необходимости вручную разбирать HTTP-запросы, то неужели не найдем что-то подходящее на этот раз?
  
Конечно, найдем! И это «что-то» называется системой управления базами данных (в просторечье – СУБД). Теперь наше дело – отправить запрос и получить ответ. Все остальное – уже не наша забота.
+
Конечно, найдем! И это «что-то» называется системой управления базами данных (в просторечье – СУБД). Теперь наше дело – отправить запрос и получить ответ. Все остальное – уже не наша забота.
  
Для этого примера я выбрал в качестве «ответственного» за хранение данных сервер баз данных PostgreSQL. Поскольку мы пишем ну очень простую гостевую книгу, то и структура базы будет у нас элементарной – одна таблица с тремя полями: время публикации сообщения, имя автора и, собственно, само сообщение:
+
Для этого примера я выбрал в качестве «ответственного» за хранение данных сервер баз данных PostgreSQL. Поскольку мы пишем ну очень простую гостевую книгу, то и структура базы будет у нас элементарной – одна таблица с тремя полями: время публикации сообщения, имя автора и, собственно, само сообщение:
  
 
  admin@toshiba:~$ psql
 
  admin@toshiba:~$ psql
Строка 180: Строка 98:
 
  CREATE DATABASE
 
  CREATE DATABASE
 
  admin=# \connect guestbook
 
  admin=# \connect guestbook
  Вы подсоединились к базе данных "guestbook".
+
  Вы подсоединились к базе данных "guestbook".
 
  guestbook=# create table guestbook (
 
  guestbook=# create table guestbook (
 
  guestbook(# datum timestamp, author varchar, message varchar);
 
  guestbook(# datum timestamp, author varchar, message varchar);
Строка 189: Строка 107:
 
  admin@toshiba:~$
 
  admin@toshiba:~$
  
Пожалуй, единственное, что здесь нужно пояснить, это почему базе и таблице мы назначили владельцем пользователя www-data. Просто к ним будет обращаться cgi-сценарий, работающий с правами HTTP-сервера Apache, который, в свою очередь, исполняется от имени данного пользователя [в вашем дистрибутиве он может назваться по-другому, – прим. ред.]. А PostgreSQL по умолчанию требует, чтобы имя пользователя в БД совпадало с его системным именем. Мне это кажется достаточно удобным, хотя вы, конечно, можете поступить по-своему.
+
Пожалуй, единственное, что здесь нужно пояснить, это почему базе и таблице мы назначили владельцем пользователя www-data. Просто к ним будет обращаться cgi-сценарий, работающий с правами HTTP-сервера Apache, который, в свою очередь, исполняется от имени данного пользователя [в вашем дистрибутиве он может назваться по-другому, – прим. ред.]. А PostgreSQL по умолчанию требует, чтобы имя пользователя в БД совпадало с его системным именем. Мне это кажется достаточно удобным, хотя вы, конечно, можете поступить по-своему.
  
=== DB API на страже унификации ===
+
=== DB API на страже унификации ===
Осталось разобраться, как же Python взаимодействует с базами данных. Для этого Python предоставляет DB API – специальный интерфейс, унифицирующий набор методов, которые будут одинаково работать независимо от того, с какой СУБД мы взаимодействуем. Для работы с PostgreSQL нам понадобится модуль PyPgSQL (в стандартной поставке его может не оказаться, но ваш менеджер пакетов наверняка будет в курсе, как его установить; кстати, это не единственный модуль – у вас, возможно, будет PyGreSQL, который работает ничуть ни хуже и с теми же самыми методами).
+
Осталось разобраться, как же Python взаимодействует с базами данных. Для этого Python предоставляет DB API – специальный интерфейс, унифицирующий набор методов, которые будут одинаково работать независимо от того, с какой СУБД мы взаимодействуем. Для работы с PostgreSQL нам понадобится модуль PyPgSQL (в стандартной поставке его может не оказаться, но ваш менеджер пакетов наверняка будет в курсе, как его установить; кстати, это не единственный модуль – у вас, возможно, будет PyGreSQL, который работает ничуть ни хуже и с теми же самыми методами).
  
DB API определяет стандартные методы работы с базами данных, так что, какой бы модуль вы ни загрузили и с какой бы СУБД ни работали (будь то MySQL, PostgreSQL, SQLite или что-то еще), меняться будет только имя модуля. Главное, чтобы используемый модуль соответствовал DB API. Рассмотрим коротко основные методы:
+
DB API определяет стандартные методы работы с базами данных, так что, какой бы модуль вы ни загрузили и с какой бы СУБД ни работали (будь то MySQL, PostgreSQL, SQLite или что-то еще), меняться будет только имя модуля. Главное, чтобы используемый модуль соответствовал DB API. Рассмотрим коротко основные методы:
  
 
<source lang="python">
 
<source lang="python">
Строка 200: Строка 118:
 
</source>
 
</source>
  
Так осуществляется подключение к базе. В зависимости от ситуации, вам может потребоваться указать только нужные параметры (например, имя хоста 'localhost' подразумевается по умолчанию).
+
Так осуществляется подключение к базе. В зависимости от ситуации, вам может потребоваться указать только нужные параметры (например, имя хоста 'localhost' подразумевается по умолчанию).
  
 
<source lang="python">
 
<source lang="python">
Строка 206: Строка 124:
 
</source>
 
</source>
  
Курсоры поддерживаются далеко не всеми СУБД, но для общности в DB API они введены и, в случае необходимости, должны эмулироваться модулями сопряжения искусственно. Так что не забывайте отправлять все ваши запросы через курсор.
+
Курсоры поддерживаются далеко не всеми СУБД, но для общности в DB API они введены и, в случае необходимости, должны эмулироваться модулями сопряжения искусственно. Так что не забывайте отправлять все ваши запросы через курсор.
  
 
<source lang="python">
 
<source lang="python">
Строка 212: Строка 130:
 
</source>
 
</source>
  
Так выполняется SQL-запрос. Если в строке запроса используются знакоместа %s, то вторым параметром передается список переменных-значений, причем в SQL-запросе знакоместа не требуется окружать апострофами – модуль сделает это самостоятельно в зависимости от
+
Так выполняется SQL-запрос. Если в строке запроса используются знакоместа %s, то вторым параметром передается список переменных-значений, причем в SQL-запросе знакоместа не требуется окружать апострофами – модуль сделает это самостоятельно в зависимости от
типа переменной.
+
типа переменной.
  
 
<source lang="python">
 
<source lang="python">
Строка 219: Строка 137:
 
</source>
 
</source>
  
Возвращает двумерный список (строки – поля) полученных от СУБД данных. Существуют и другие методы, ознакомиться с которыми вы сможете в документации или с помощью знакомой вам функции dir() да пары-тройки несложных экспериментов.
+
Возвращает двумерный список (строки – поля) полученных от СУБД данных. Существуют и другие методы, ознакомиться с которыми вы сможете в документации или с помощью знакомой вам функции dir() да пары-тройки несложных экспериментов.
  
=== Закрепляем на практике ===
+
=== Закрепляем на практике ===
Перейдем к рассмотрению нашего примера. Начнем стандартно – укажем кодировку, подключим нужные модули:
+
Перейдем к рассмотрению нашего примера. Начнем стандартно – укажем кодировку, подключим нужные модули:
  
 
<source lang="python">
 
<source lang="python">
Строка 231: Строка 149:
 
</source>
 
</source>
  
Далее, определим две функции. Первая будет отвечать за добавление нового сообщения в базу:
+
Далее, определим две функции. Первая будет отвечать за добавление нового сообщения в базу:
  
 
<source lang="python">
 
<source lang="python">
Строка 245: Строка 163:
 
</source>
 
</source>
  
Как видите, все очень даже логично: устанавливаем соединение с БД (поскольку в нашем случае подключение выполняется с именем текущего системного пользователя, то достаточно указать только имя базы), создаем курсор (в PostgreSQL они не применяются, но они эмулируются каждым модулем, претендующим на соответствие DB API), выполняется запрос, закрывается курсор, фиксируются изменения (PostgreSQL использует транзакции, поэтому выполнение метода commit() обязательно, иначе ваши изменения не будут сохранены), и, наконец, закрываем само соединение с базой. В поле datum заносим значение встроенной переменной PostgreSQL – now, которая каждый раз заменяется текущим значением даты и времени.
+
Как видите, все очень даже логично: устанавливаем соединение с БД (поскольку в нашем случае подключение выполняется с именем текущего системного пользователя, то достаточно указать только имя базы), создаем курсор (в PostgreSQL они не применяются, но они эмулируются каждым модулем, претендующим на соответствие DB API), выполняется запрос, закрывается курсор, фиксируются изменения (PostgreSQL использует транзакции, поэтому выполнение метода commit() обязательно, иначе ваши изменения не будут сохранены), и, наконец, закрываем само соединение с базой. В поле datum заносим значение встроенной переменной PostgreSQL now, которая каждый раз заменяется текущим значением даты и времени.
  
Ну и печать заголовка «Location» выполняется для того, чтобы перенаправить пользователя на этот же сценарий, но уже без параметров – мы же должны показать клиенту, что он на самом деле ввел? (Якорь #form используется, чтобы автоматически прокрутить страничку на последнее сообщение).
+
Ну и печать заголовка «Location» выполняется для того, чтобы перенаправить пользователя на этот же сценарий, но уже без параметров – мы же должны показать клиенту, что он на самом деле ввел? (Якорь #form используется, чтобы автоматически прокрутить страничку на последнее сообщение).
  
Вторая функция будет отвечать за вывод на экран уже оставленныхв книге записей, а также за форму, с помощью которой можно будет добавить и свое высказывание:
+
Вторая функция будет отвечать за вывод на экран уже оставленныхв книге записей, а также за форму, с помощью которой можно будет добавить и свое высказывание:
  
 
<source lang="python">
 
<source lang="python">
Строка 261: Строка 179:
 
</source>
 
</source>
  
В этом фрагменте мы выбираем все строки из нашей таблицы данных, сортируя их по дате. Результат сохраняется в переменной res, с которой и будем работать. Теперь осталось лишь аккуратненько разложить наши данные по табличкам и вывести их на экран:
+
В этом фрагменте мы выбираем все строки из нашей таблицы данных, сортируя их по дате. Результат сохраняется в переменной res, с которой и будем работать. Теперь осталось лишь аккуратненько разложить наши данные по табличкам и вывести их на экран:
  
 
<source lang="python">
 
<source lang="python">
 
  print "Content-Type: text/html\n"
 
  print "Content-Type: text/html\n"
  print "<H1 style='color:#7777FF'><U>Велькам к нам в гости!</U></H1>"
+
  print "<H1 style='color:#7777FF'><U>Велькам к нам в гости!</U></H1>"
 
  for item in res:
 
  for item in res:
 
   print """<TABLE width='90%%'>
 
   print """<TABLE width='90%%'>
                 <TR><TD><SMALL>Товарищ <B>%s</B> поведалнам следующее:</SMALL>
+
                 <TR><TD><SMALL>Товарищ <B>%s</B> поведалнам следующее:</SMALL>
 
                     <TD align='right'><SMALL>%s</SMALL>
 
                     <TD align='right'><SMALL>%s</SMALL>
 
                 <TR><TD style='background-color:#DDDDFF' colspan='2'>%s
 
                 <TR><TD style='background-color:#DDDDFF' colspan='2'>%s
 
             </TABLE>""" % (item[1], str(item[0])[:19], item[2])
 
             </TABLE>""" % (item[1], str(item[0])[:19], item[2])
  print "<HR><A name='form'><H3>Присоединяйтесь к дискуссии:</H3>"
+
  print "<HR><A name='form'><H3>Присоединяйтесь к дискуссии:</H3>"
 
  print """<FORM method='GET'>
 
  print """<FORM method='GET'>
           Ваше имя: <INPUT type='text' name='author'><BR>
+
           Ваше имя: <INPUT type='text' name='author'><BR>
           Что вы думаете по этому поводу:<BR>
+
           Что вы думаете по этому поводу:<BR>
 
           <TEXTAREA name='message' rows='5' cols='80'></TEXTAREA><BR>
 
           <TEXTAREA name='message' rows='5' cols='80'></TEXTAREA><BR>
           <INPUT type='submit' value='Отправить'>
+
           <INPUT type='submit' value='Отправить'>
 
           </FORM>"""
 
           </FORM>"""
 
</source>
 
</source>
  
 
<div id="img"></div>
 
<div id="img"></div>
[[Изображение:Img 83 81 1.png|thumb|Ни смайликов, ни BB-кода, ни даже логотипа... Зато мы сделали эту гостевую за 10 минут!]]
+
[[Изображение:Img 83 81 1.png|thumb|Ни смайликов, ни BB-кода, ни даже логотипа... Зато мы сделали эту гостевую за 10 минут!]]
  
Смысл конструкции str(item[0])[:19] заключается в том, чтобы в строке времени отсечь ненужные нам миллисекунды, которые сохраняются в поле типа timestamp. После всех опубликованных сообщений выводим форму добавления нового, чтобы каждый мог присоединиться к нашей дискуссии. Кстати, в теге <FORM> мы не указали параметр action, поскольку данные будут передаваться на обработку этому же сценарию (благодаря чему имя сценарию можно присвоить любое).
+
Смысл конструкции str(item[0])[:19] заключается в том, чтобы в строке времени отсечь ненужные нам миллисекунды, которые сохраняются в поле типа timestamp. После всех опубликованных сообщений выводим форму добавления нового, чтобы каждый мог присоединиться к нашей дискуссии. Кстати, в теге <FORM> мы не указали параметр action, поскольку данные будут передаваться на обработку этому же сценарию (благодаря чему имя сценарию можно присвоить любое).
Наконец, последний фрагмент:
+
Наконец, последний фрагмент:
  
 
<source lang="python">
 
<source lang="python">
Строка 298: Строка 216:
 
</source>
 
</source>
  
Создаем FieldStorage-объект, и если в нем есть заполненные поля message и author (то есть запрос был сформирован из заполненной пользователем формы), то, немножко их обработав (функция cgi.escape() заменяет все «неблагонадежные» символы – например, < – их стандартными SGML-сущностями, в данном случае – &lt;), передаем функции addMessage(). Обработка нужна для того, чтобы злоумышленник не мог ввести в поле сообщения или имени автора что-нибудь такое:
+
Создаем FieldStorage-объект, и если в нем есть заполненные поля message и author (то есть запрос был сформирован из заполненной пользователем формы), то, немножко их обработав (функция cgi.escape() заменяет все «неблагонадежные» символы – например, < – их стандартными SGML-сущностями, в данном случае – &lt;), передаем функции addMessage(). Обработка нужна для того, чтобы злоумышленник не мог ввести в поле сообщения или имени автора что-нибудь такое:
  
  <SCRIPT>alert('Да пошли вы все!');</SCRIPT>
+
  <SCRIPT>alert('Да пошли вы все!');</SCRIPT>
  
К слову, пренебрегать проверкой введенных данных ни в коем случае нельзя. Зайдите как-нибудь на [http://securitylab.ru securitylab.ru] и посмотрите, сколько уязвимостей типа «XSS» обнаруживается каждый месяц! Так что шутки шутками, но последствия могут быть очень серьезными.
+
К слову, пренебрегать проверкой введенных данных ни в коем случае нельзя. Зайдите как-нибудь на [http://securitylab.ru securitylab.ru] и посмотрите, сколько уязвимостей типа «XSS» обнаруживается каждый месяц! Так что шутки шутками, но последствия могут быть очень серьезными.
  
=== Куда же нам теперь идти? ===
+
=== Куда же нам теперь идти? ===
Итак, что-то вполне работоспособное у нас есть (см. [[LXF83:Python#img|рисунок]]). Но как вы может догадаться, наша гостевая очень далека от совершенства. Что еще можно сделать? Ну, например, разбить на страницы. Пока сообщений в ней будет не больше дюжины, сойдет и так. А когда их число дойдет до сотни, то редкий пользователь дождется окончания загрузки всех данных. Можно дать пользователям возможность использовать некоторые HTML-теги, чтобы их сообщения выглядели более красочно. Можно добавить смайликов... А можно даже сделать модуль администрирования, позволяющий редактировать или удалять сообщения, а также отвечать на них. Так что работы непочатый край. Дерзайте – не буду вам мешать.
+
Итак, что-то вполне работоспособное у нас есть (см. [[LXF83:Python#img|рисунок]]). Но как вы может догадаться, наша гостевая очень далека от совершенства. Что еще можно сделать? Ну, например, разбить на страницы. Пока сообщений в ней будет не больше дюжины, сойдет и так. А когда их число дойдет до сотни, то редкий пользователь дождется окончания загрузки всех данных. Можно дать пользователям возможность использовать некоторые HTML-теги, чтобы их сообщения выглядели более красочно. Можно добавить смайликов... А можно даже сделать модуль администрирования, позволяющий редактировать или удалять сообщения, а также отвечать на них. Так что работы непочатый край. Дерзайте – не буду вам мешать.
  
=== Некоторые распространённые MIME-типы ===
+
=== Некоторые распространённые MIME-типы ===
 
{| style="background:white;color:black;" border="1" cellspacing="0"
 
{| style="background:white;color:black;" border="1" cellspacing="0"
 
|- style="background:#dfcfe6;color:black"
 
|- style="background:#dfcfe6;color:black"
! MIME-тип
+
! MIME-тип
! Описание
+
! Описание
 
|-
 
|-
 
| text/plain
 
| text/plain
| Простой текст
+
| Простой текст
 
|-
 
|-
 
| text/html
 
| text/html
| HTML-страница
+
| HTML-страница
 
|-
 
|-
 
| image/gif
 
| image/gif
| Изображение GIF
+
| Изображение GIF
 
|-
 
|-
 
| video/mpeg
 
| video/mpeg
| Видео-файл в формате MPEG
+
| Видео-файл в формате MPEG
 
|-
 
|-
 
| application/msword       
 
| application/msword       
| Документ MS Word
+
| Документ MS Word
 
|}
 
|}

Текущая версия на 14:27, 31 мая 2009

Содержание

[править] Работа с базами данных и 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>"""
(thumbnail)
Ни смайликов, ни BB-кода, ни даже логотипа... Зато мы сделали эту гостевую за 10 минут!

Смысл конструкции 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
Персональные инструменты
купить
подписаться
Яндекс.Метрика