Материал из Intuit
Перейти к: навигация, поиск
Разработка Web-приложений
Аннотация: Одна из главных сфер применения языка Python — web-приложения — представляется в этой лекции на конкретных примерах. Кроме того, делается акцент на типичных слабых местах безопасности web-приложений.
Содержание
- 1 НЕТ ДАННЫХ
- 2 CGI-сценарии
- 2.1 Модуль cgi
- 2.2 Что после CGI?
- 3 Среды разработки
- 3.1 Zope и его объектная модель
- 3.2 Заключение
НЕТ ДАННЫХ
Под web-приложением будет пониматься программа, основной интерфейс пользователя которой работает в стандартном WWW-браузере под управлением HTML и XML-документов. Для улучшение качества интерфейса пользователя часто применяют JavaScript, однако это несколько снижает универсальность интерфейса. Следует заметить, что интерфейс можно построить на Java- или Flash-апплетах, однако, такие приложения сложно назвать web-приложениями, так как Java или Flash могут использовать собственные протоколы для общения с сервером, а не стандартный для WWW протокол HTTP.
При создании web-приложений стараются отделить Форму (внешний вид, стиль), Содержание и Логику обработки данных. Современные технологии построения web-сайтов дают возможность подойти достаточно близко к этому идеалу. Тем не менее, даже без применения многоуровневых приложений можно придерживаться стиля, позволяющего изменять любой из этих аспектов, не затрагивая (или почти не затрагивая) двух других. Рассуждения на эту тему будут продолжены в разделе, посвященном средам разработки.
CGI-сценарии
Классический путь создания приложений для WWW — написание CGI-сценариев (иногда говорят — скриптов). CGI (Common Gateway Interface, общий шлюзовой интерфейс) — это стандарт, регламентирующий взаимодействие сервера с внешними приложениями. В случае с WWW, web-сервер может направить запрос на генерацию страницы по определенному сценарию. Этот сценарий, получив на вход данные от web-сервера (тот, в свою очередь, мог получить их от пользователя), генерирует готовый объект (изображение, аудиоданные, таблицу стилей и т.п.).
При вызове сценария Web-сервер передает ему информацию через стандартный ввод, переменные окружения и, для ISINDEX, через аргументы командной строки (они доступны через sys.argv
).
Два основных метода передачи данных из заполненной в браузере формы Web-серверу (и CGI-сценарию) — GET
и POST
. В зависимости от метода данные передаются по-разному. В первом случае они кодируются и помещаются прямо в URL, например: http://host/cgi-bin/a.cgi?a=1&b=3
. Сценарий получает их в переменной окружения с именем QUERY_STRING
. В случае метода POST
они передаются на стандартный ввод.
Для корректной работы сценарии помещаются в предназначенный для этого каталог на web-сервере (обычно он называется cgi-bin
) или, если это разрешено конфигурацией сервера, в любом месте среди документов HTML. Сценарий должен иметь признак исполняемости. В системе Unix его можно установить с помощью команды chmod a+x
.
Следующий простейший сценарий выводит значения из словаря os.environ
и позволяет увидеть, что же было ему передано:
Пример #!/usr/bin/python import os print """Content-Type: text/plain %s""" % os.environ
С помощью него можно увидеть установленные Web-сервером переменные окружения. Выдаваемый CGI-сценарием web-серверу файл содержит заголовочную часть, в которой указаны поля с мета-информацией (тип содержимого, время последнего обновления документа, кодировка и т.п.).
Основные переменные окружения, достаточные для создания сценариев:
Пример QUERY_STRING
Строка запроса.
Пример REMOTE_ADDR
IP-адрес клиента.
Пример REMOTE_USER
Имя клиента (если он был идентифицирован).
Пример SCRIPT_NAME
Имя сценария.
Пример SCRIPT_FILENAME
Имя файла со сценарием.
Пример SERVER_NAME
Имя сервера.
Пример HTTP_USER_AGENT
Название броузера клиента.
Пример REQUEST_URI
Строка запроса (URI).
Пример HTTP_ACCEPT_LANGUAGE
Желательный язык документа.
Вот что может содержать словарь os.environ
в CGI-сценарии:
Пример { 'DOCUMENT_ROOT': '/var/www/html', 'SERVER_ADDR': '127.0.0.1', 'SERVER_PORT': '80', 'GATEWAY_INTERFACE': 'CGI/1.1', 'HTTP_ACCEPT_LANGUAGE': 'en-us, en;q=0.50', 'REMOTE_ADDR': '127.0.0.1', 'SERVER_NAME': 'rnd.onego.ru', 'HTTP_CONNECTION': 'close', 'HTTP_USER_AGENT': 'Mozilla/5.0 (X11; U; Linux i586; en-US; rv:1.0.1) Gecko/20021003', 'HTTP_ACCEPT_CHARSET': 'ISO-8859-1, utf-8;q=0.66, *;q=0.66', 'HTTP_ACCEPT': 'text/xml,application/xml,application/xhtml+xml, text/html;q=0.9,text/plain;q=0.8,video/x-mng,image/png,image/jpeg, image/gif;q=0.2,text/css,*/*;q=0.1', 'REQUEST_URI': '/cgi-bin/test.py?a=1', 'PATH': '/sbin:/usr/sbin:/bin:/usr/bin:/usr/X11R6/bin', 'QUERY_STRING': 'a=1&b=2', 'SCRIPT_FILENAME': '/var/www/cgi-bin/test.py', 'HTTP_KEEP_ALIVE': '300', 'HTTP_HOST': 'localhost', 'REQUEST_METHOD': 'GET', 'SERVER_SIGNATURE': 'Apache/1.3.23 Server at rnd.onego.ru Port 80', 'SCRIPT_NAME': '/cgi-bin/test.py', 'SERVER_ADMIN': 'root@localhost', 'SERVER_SOFTWARE': 'Apache/1.3.23 (Unix) (Red-Hat/Linux) mod_python/2.7.8 Python/1.5.2 PHP/4.1.2', 'SERVER_PROTOCOL': 'HTTP/1.0', 'REMOTE_PORT': '39251' }
Следующий CGI-сценарий выдает черный квадрат (в нем используется модуль Image
для обработки изображений):
Пример #!/usr/bin/python import sys print """Content-Type: image/jpeg """ import Image i = Image.new("RGB", (10,10)) i.im.draw_rectangle((0,0,10,10), 1) i.save(sys.stdout, "jpeg")
Модуль cgi
В Python имеется поддержка CGI в виде модуля cgi
. Следующий пример показывает некоторые из его возможностей:
Пример #!/usr/bin/python # -*- coding: cp1251 -*- import cgi, os # анализ запроса f = cgi.FieldStorage() if f.has_key("a"): a = f["a"].value else: a = "0" # обработка запроса b = str(int(a)+1) mytext = open(os.environ["SCRIPT_FILENAME"]).read() mytext_html = cgi.escape(mytext) # формирование ответа print """Content-Type: text/html <html><head><title>Решение примера: %(b)s = %(a)s + 1</title></head> <body> %(b)s <table width="80%%"><tr><td> <form action="me.cgi" method="GET"> <input type="text" name="a" value="0" size="6"> <input type="submit" name="b" value="Обработать"> </form></td></tr></table> <pre> %(mytext_html)s </pre> </body></html>""" % vars()
В этом примере к заданному в форме числу прибавляется 1. Кроме того, выводится исходный код самого сценария. Следует заметить, что для экранирования символов >, <, &
использована функция cgi.escape()
. Для формирования Web-страницы применена операция форматирования. В качестве словаря для выполнения подстановок использован словарь vars()
со всеми локальными переменными. Знаки процента пришлось удвоить, чтобы они не интерпретировались командой форматирования. Стоит обратить внимание на то, как получено значение от пользователя. Объект FieldStorage
«почти» словарь, с тем исключением, что для получения обычного значения нужно дополнительно посмотреть атрибут value
. Дело в том, что в сценарий могут передаваться не только текстовые значения, но и файлы, а также множественные значения с одним и тем же именем.
Осторожно!
При обработке входных значений CGI-сценариев нужно внимательно и скрупулезно проверять допустимые значения. Лучше считать, что клиент может передать на вход все, что угодно. Из этого всего необходимо выбрать и проверить только то, что ожидает сценарий.
Например, не следует подставлять полученные от пользователя данные в путь к файлу, в качестве аргументов к функции eval()
и ей подобных; параметров командной строки; частей в SQL-запросах к базе данных. Также не стоит вставлять полученные данные напрямую в формируемые страницы, если эти страницы будет видеть не только клиент, заказавший URL (например, такая ситуация обычна в web-чатах, форумах, гостевых книгах), и даже в том случае, если единственный читатель этой информации — администратор сайта. Тот, кто смотрит страницы с непроверенным HTML-кодом, поступившим напрямую от пользователя, рискуют обработать в своем браузере зловредный код, использующий брешь в его защите.
Даже если CGI-сценарий используется исключительно другими сценариями через запрос на URL, нужно проверять входные значения столь же тщательно, как если бы данные вводил пользователь. (Так как недоброжелатель может подать на web-сервер любые значения).
В примере выше проверка на допустимость произведена при вызове функции int():
если было бы задано нечисловое значение, сценарий аварийно завершился, а пользователь увидел Internal Server Error
.
После анализа входных данных можно выделить фазу их обработки. В этой части CGI-сценария вычисляются переменные для дальнейшего вывода. Здесь необходимо учитывать не только значения переданных переменных, но и факт их присутствия или отсутствия, так как это тоже может влиять на логику сценария.
И, наконец, фаза вывода готового объекта (текста, HTML-документа, изображения, мультимедиа-объекта и т.п.). Проще всего заранее подготовить шаблон страницы (или ее крупных частей), а потом просто заполнить содержимым из переменных.
В приведенных примерах имена появлялись в строке запроса только один раз. Некоторые формы порождают несколько значений для одного имени. Получить все значения можно с помощью метода getlist():
Пример lst = form.getlist("fld")
Список lst
будет содержать столько значений, сколько полей с именем fld
получено из web-формы (он может быть и пустым, если ни одно поле с заданным именем не было заполнено).
В некоторых случаях необходимо передать на сервер файлы (сделать upload). Следующий пример и комментарий к нему помогут разобраться с этой задачей:
Пример #!/usr/bin/env python import cgi form = cgi.FieldStorage() file_contents = "" if form.has_key("filename"): fileitem = form["filename"] if fileitem.file: file_contents = """<P>Содержимое переданного файла: <PRE>%s</PRE>""" % fileitem.file.read() print """Content-Type: text/html <HTML><HEAD><TITLE>Загрузка файла</TITLE></HEAD> <BODY><H1>Загрузка файла</H1> <P><FORM ENCTYPE="multipart/form-data" ACTION="getfile.cgi" METHOD="POST"> <br>Файл: <INPUT TYPE="file" NAME="filename"> <br><INPUT TYPE="submit" NAME="button" VALUE="Передать файл"> </FORM> %s </BODY></HTML>""" % file_contents
В начале следует рассмотреть web-форму, которая приведена в конце сценария: именно она будет выводиться пользователю при обращении по CGI-сценарию. Форма имеет поле типа file
, которое в web-броузере представляется полоской ввода и кнопкой «Browse». Нажимая на кнопку «Browse», пользователь выбирает файл, доступный в ОС на его компьютере. После этого он может нажать кнопку «Передать файл» для передачи файла на сервер.
Для отладки CGI-сценария можно использовать модуль cgitb
. При возникновении ошибки этот модуль выдаст красочную HTML-страницу с указанием места возбуждения исключения. В начале отлаживаемого сценария нужно поставить
Пример import cgitb cgitb.enable(1)
Или, если не нужно показывать ошибки в браузере:
Пример import cgitb cgitb.enable(0, logdir="/tmp")
Только необходимо помнить, что следует убрать эти строки, когда сценарий будет отлажен, так как он выдает кусочки кода сценария. Это может быть использовано злоумышленниками, с тем чтобы найти уязвимости в CGI-сценарии или подсмотреть пароли (если таковые присутствуют в сценарии).
Что после CGI?
К сожалению, строительство интерактивного и посещаемого сайта на основе CGI имеет свои ограничения, главным образом, связанные с производительностью. Ведь для каждого запроса нужно вызвать как минимум один сценарий (а значит — запустить интерпретатор Python), из него, возможно, сделать соединение с базой данных и т.д. Время запуска интерпретатора Python достаточно невелико, тем не менее, на занятом сервере оно может оказывать сильное влияние на загрузку процессора.
Желательно, чтобы интерпретатор уже находился в оперативной памяти, и были доступны соединения с базой данных.
Такие технологии существуют и обычно опираются на модули, встраиваемые в web-сервер.
Для ускорения работы CGI используются различные схемы, например, FastCGI или PCGI (Persistent CGI). В данной лекции предлагается к рассмотрению специальным модуль для web-сервера Apache, называемый mod_python
.
Пусть модуль установлен на web-сервере в соответствии с инструкциями, данными в его документации.
Модуль mod_python
позволяет сценарию-обработчику вклиниваться в процесс обработки HTTP-запроса сервером Apache на любом этапе, для чего сценарий должен иметь определенным образом названные функции.
Сначала нужно выделить каталог, в котором будет работать сценарий-обработчик. Пусть это каталог /var/www/html/mywebdir
. Для того чтобы web-сервер знал, что в этом каталоге необходимо применять mod_python
, следует добавить в файл конфигурации Apache следующие строки:
Пример <Directory "/var/www/html/mywebdir"> AddHandler python-program .py PythonHandler mprocess </Directory>
После этого необходимо перезапустить web-сервер и, если все прошло без ошибок, можно приступать к написанию обработчика mprocess.py
. Этот сценарий будет реагировать на любой запрос вида http://localhost/*.py.
Следующий сценарий mprocess.py
выведет в браузере страницу со словами Hello, world!
:
Пример from mod_python import apache def handler(req): req.content_type = "text/html" req.send_http_header() req.write("""<HTML><HEAD><TITLE>Hello, world!</TITLE></HEAD> <BODY>Hello, world!</BODY></HTML>""") return apache.OK
Отличия сценария-обработчика от CGI-сценария:
- Сценарий-обработчик не запускается при каждом HTTP-запросе: он уже находится в памяти, и из него вызываются необходимые функции-обработчики (в приведенном примере такая функция всего одна —
handler()
). Каждый процесс-потомок web-сервера может иметь свою копию сценария и интерпретатора Python. - Как следствие п.1 различные HTTP-запросы делят одни и те же глобальные переменные. Например, таким образом можно инициализировать соединение с базой данных и применять его во всех запросах (хотя в некоторых случаях потребуются блокировки, исключающие одновременное использование соединения разными потоками (нитями) управления).
- Обработчик задействуется при обращении к любому «файлу» с расширением
py
, тогда как CGI-сценарий обычно запускается при обращении по конкретному имени. - В сценарии-обработчике нельзя рассчитывать на то, что он увидит модули, расположенные в том же каталоге. Возможно, придется добавить некоторые каталоги в
sys.path
. - Текущий рабочий каталог (его можно узнать с помощью функции
os.getcwd()
) также не находится в одном каталоге с обработчиком. - #!-строка в первой строке сценария не определяет версию интерпретатора Python. Работает версия, для которой был скомпилирован
mod_python
. - Все необходимые параметры передаются в обработчик в виде Request-объекта. Возвращаемые значения также передаются через этот объект.
- Web-сервер замечает, что сценарий-обработчик изменился, но не заметит изменений в импортируемых в него модулях. Команда
touch
mprocess.py
обновит дату изменения файла сценария. - Отображение
os.environ
в обработчике может быть обрезанным. Кроме того, вызываемые из сценария-обработчика другие программы его не наследуют, как это происходит при работе с CGI-сценариями. Переменные можно получить другим путем:req.add_common_vars(); params = req.subprocess_env.
- Так как сценарий-обработчик не является «одноразовым», как CGI-сценарий, из-за ошибок программирования (как самого сценария, так и других компонентов) могут возникать утечки памяти (программа не освобождает ставшую ненужной память). Следует установить значение параметра
MaxRequestsPerChild
(максимальное число запросов, обрабатываемое одним процессом-потомком) больше нуля.
Другой возможный обработчик — сценарий идентификации:
Пример def authenhandler(req): password = req.get_basic_auth_pw() user = req.connection.user if user == "user1" and password == "secret": return apache.OK else: return apache.HTTP_UNAUTHORIZED
Эту функцию следует добавить в модуль mprocess.py
, который был рассмотрен ранее. Кроме того, нужно дополнить конфигурацию, назначив обработчик для запросов идентификации (PythonAuthenHandler
), а также обычные для Apache директивы AuthType
, AuthName
, require
, определяющие способ авторизации:
Пример <Directory "/var/www/html/mywebdir"> AddHandler python-program .py PythonHandler mprocess PythonAuthenHandler mprocess AuthType Basic AuthName "My page" require valid-user </Directory>
Разумеется, это — всего лишь пример. В реальности идентификация может быть устроена намного сложнее.
Другие возможные обработчики (по документации к mod_python
можно уточнить, в какие моменты обработки запроса они вызываются):
Пример PythonPostReadRequestHandler
Обработка полученного запроса сразу после его получения.
Пример PythonTransHandler
Позволяет изменить URI запроса (в том числе имя виртуального сервера).
Пример PythonHeaderParserHandler
Обработка полей запроса.
Пример PythonAccessHandler
Обработка ограничений доступа (например, по IP-адресу).
Пример PythonAuthenHandler
Идентификация пользователя.
Пример PythonTypeHandler
Определение и/или настройка типа документа, языка и т.д.
Пример PythonFixupHandler
Изменение полей непосредственно перед вызовом обработчиков содержимого.
Пример PythonHandler
Основной обработчик запроса.
Пример PythonInitHandler
PythonPostReadRequestHandler
или PythonHeaderParserHandler
в зависимости от нахождения в конфигурации web-сервера.
Пример PythonLogHandler
Управление записью в логи.
Пример PythonCleanupHandler
Обработчик, вызываемый непосредственно перед уничтожением Request-объекта.
Некоторые из этих обработчиков работают только глобально, так как при вызове даже каталог их приложения может быть неизвестен (таков, например, PythonPostReadRequestHandler
).
С помощью mod_python
можно строить web-сайты с динамическим содержимым и контролировать некоторые аспекты работы web-сервера Apache через Python-сценарии.
Среды разработки
Для создания Web-приложений применяются и более сложные средства, чем web-сервер с расположенными на нем статическими документами и CGI-сценариями. В зависимости от назначения такие программные системы называются серверами web-приложений, системами управления содержимым (CMS, Content Management System), системы web-публикации и средствами для создания WWW-порталов. Причем CMS-система может быть выполнена как web-приложение, а средства для создания порталов могут базироваться на системах web-публикации, для которых CMS-система является подсистемой. Поэтому, выбирая систему для конкретных нужд, стоит уточнить, какие функции она должна выполнять.
Язык Python, хотя и уступает PHP по количеству созданных на нем web-систем, имеет несколько достаточно популярных приложений. Самым ярким примером средства для создания сервера web-приложений является Zope (произносится «зоп») (см. [1]) (Z Object Publishing Environment, среда публикации объектов). Zope имеет встроенный web-сервер, но может работать и с другими Web-серверами, например, Apache. На основе Zope можно строить web-порталы, например, с помощью Plone/Zope, но можно разрабатывать и собственные web-приложения. При этом Zope позволяет разделить Форму, Содержание и Логику до такой степени, что Содержанием могут заниматься одни люди (менеджеры по содержимому), Формой — другие (web-дизайнеры), а Логикой — третьи (программисты). В случае с Zope Логику можно задать с помощью языка Python (или, как вариант, Perl), Форма может быть создана в графических или специализированных web-редакторах, а работа с содержимым организована через Web-формы самого Zope.
Zope и его объектная модель
В рамках этой лекции невозможно детально рассмотреть такой инструмент как Zope, поэтому стоит лишь заметить, что он достаточно интересен не только в качестве среды разработки web-приложений, но и с точки зрения подходов. Например, уникальная объектно-ориентированная модель Zope позволяет довольно гибко описывать требуемое приложение.
Zope включает в себя следующие возможности:
- Web-сервер. Zope может работать с Web-серверами через CGI или использовать свой встроенный Web-сервер (ZServer).
- Среда разработчика выполнена как Web-приложение. Zope позволяет создавать Web-приложения через Web-интерфейс.
- Поддержка сценариев. Zope поддерживает несколько языков сценариев: Python, Perl и собственный DTML (Document Template Markup Language, язык разметки шаблона документа).
- База данных объектов. Zope использует в своей работе устойчивые объекты, хранимые в специальной базе данных (ZODB). Имеется достаточно простой интерфейс для управления этой базой данных.
- Интеграция с реляционными базами данных. Zope может хранить свои объекты и другие данные в реляционных СУБД: Oracle, PostgreSQL, MySQL, Sybase и т.п.
В ряду других подобных систем Zope на первый взгляд кажется странным и неприступным, однако тем, кто с ним «на ты», он открывает большие возможности.
Разработчики Zope исходили из лежащей в основе WWW объектной модели, в которой загрузку документа по URI можно сравнить с отправкой сообщения объекту. Объекты Zope разложены по папкам (folders), к которым привязаны политики доступа для пользователей, имеющих определенные роли. В качестве объектов могут выступать документы, изображения, мультимедиа-файлы, адаптеры к базам данных и т.п.
Документы Zope можно писать на языке DTML — дополнении HTML с синтаксисом для включения значений подобно SSI (Server-Side Include). Например, для вставки переменной с названием документа можно использовать
Пример <!- #var document_title ->
Следует заметить, что объекты Zope могут иметь свои атрибуты, а также методы, в частности, написанные на языке Python. Переменные же могут появляться как из заданных пользователем значений, так и из других источников данных (например, из базы данных посредством выполнения выборки функцией SELECT).
Сейчас для описания документа Zope все чаще применяется ZPT (Zope Page Templates, шаблоны страниц Zope), которые в свою очередь используют TAL (Template Attribute Language, язык шаблонных атрибутов). Он позволяет заменять, повторять или пропускать элементы документа описываемого шаблоном документа. «Операторами» языка TAL являются XML-атрибуты из пространства имен TAL. Пространство имен сегодня описывается следующим идентификатором:
Пример xmlns:tal="http://xml.zope.org/namespaces/tal"
Оператор TAL имеет имя и значение (что выражается именем и значением атрибута). Внутри значения обычно записано TAL-выражение, синтаксис которого описывается другим языком — TALES (Template Attribute Language Expression Syntax, синтаксис выражений TAL).
Таким образом, ZPT наполняет содержимым шаблоны, интерпретируя атрибуты TAL. Например, чтобы Zope подставил название документа (тег TITLE
), шаблон может иметь следующий код:
Пример <title tal:content="here/title">Doc Title</title>
Стоит заметить, что приведенный код сойдет за код на HTML, то есть, Web-дизайнер может на любом этапе работы над проектом редактировать шаблон в HTML-редакторе (при условии, что тот сохраняет незнакомые атрибуты из пространства имен tal). В этом примере here/title
является выражением TALES. Текст Doc Title
служит ориентиром для web-дизайнера и заменяется значением выражения here/title
, то есть, будет взято свойство title
документа Zope.
Примечание:
В Zope объекты имеют свойства. Набор свойств зависит от типа объекта, но может быть расширен в индивидуальном порядке. Свойство id
присутствует всегда, свойство title
обычно тоже указывается.
В качестве более сложного примера можно рассмотреть организацию повтора внутри шаблона (для опробования этого примера в Zope нужно добавить объект Page Template):
Пример <ul> <li tal:define="s modules/string" tal:repeat="el python:s.digits"> <a href="DUMMY" tal:attributes="href string:/digit/$el" tal:content="el">SELECTION</a> </li> </ul>
Этот шаблон породит следующий результат:
Пример <ul> <li><a href="/digit/0">0</a></li> <li><a href="/digit/1">1</a></li> <li><a href="/digit/2">2</a></li> <li><a href="/digit/3">3</a></li> <li><a href="/digit/4">4</a></li> <li><a href="/digit/5">5</a></li> <li><a href="/digit/6">6</a></li> <li><a href="/digit/7">7</a></li> <li><a href="/digit/8">8</a></li> <li><a href="/digit/9">9</a></li> </ul>
Здесь нужно обратить внимание на два основных момента:
- в шаблоне можно использовать выражения Python (в данном примере переменная
s
определена как модуль Python) и переменную-счетчик циклаel
, которая проходит итерации по строкеstring.digits
. - с помощью TAL можно задавать не только содержимое элемента, но и атрибута тега (в данном примере использовался атрибут
href
).
Детали можно узнать по документации. Стоит лишь заметить, что итерация может происходить по самым разным источникам данных: содержимому текущей папки, выборке из базы данных или, как в приведенном примере, по объекту Python.
Любой программист знает, что программирование тем эффективнее, чем лучше удалось «расставить скобки», выведя «общий множитель за скобки». Другими словами, хорошие программисты должны быть достаточно «ленивы», чтобы найти оптимальную декомпозицию решаемой задачи. При проектировании динамического web-сайта Zope позволяет разместить «множители» и «скобки» так, чтобы достигнуть максимального повторного использования кода (как разметки, так и сценариев). Помогает этому уникальный подход к построению взаимоотношений между объектами: заимствование (acquisition).
Пусть некоторый объект (документ, изображение, сценарий, подключение к базе данных и т.п.) расположен в папке Example. Теперь объекты этой папки доступны по имени из любых нижележащих папок. Даже политики безопасности заимствуются более глубоко вложенными папками от папок, которые ближе к корню. Заимствование является очень важной концепцией Zope, без понимания которой Zope сложно грамотно применять, и наоборот, ее понимание позволяет экономить силы и время, повторно используя объекты в разработке.
Самое интересное, что заимствовать объекты можно также из параллельных папок. Пусть, например, рядом с папкой Example находится папка Zigzag, в которой лежит нужный объект (его наименование note). При этом в папке Example программиста интересует объект index_html
, внутри которого вызывается note. Обычный путь к объекту index_html
будет происходить по URI вроде http://zopeserver/Example/
. А вот если нужно использовать note из Zigzag (и в папке Example его нет), то URI будет: http://zopeserver/Zigzag/Example/
. Таким образом, указание пути в Zope отличается от традиционного пути, скажем, в Unix: в пути могут присутствовать «зигзаги» через параллельные папки, дающие возможность заимствовать объекты из этих папок. Таким образом, можно сделать конкретную страницу, комбинируя один или несколько независимых аспектов.
Заключение
В этой лекции были рассмотрены различные подходы к использованию Python в web-приложениях. Самый простой способ реализации web-приложения — использование CGI-сценариев. Более сложным является использование специальных модулей для web-сервера, таких как mod_python
. Наконец, существуют технологии вроде Zope, которые предоставляют специализированные сервисы, позволяющие создавать web-приложения.
Watch Now This tutorial has a related video course created by the Real Python team. Watch it together with the written tutorial to deepen your understanding: Deploy Your Python Script on the Web With Flask
You wrote a Python script that you’re proud of, and now you want to show it off to the world. But how? Most people won’t know what to do with your .py
file. Converting your script into a Python web application is a great solution to make your code usable for a broad audience.
In this tutorial, you’ll learn how to go from a local Python script to a fully deployed Flask web application that you can share with the world.
By the end of this tutorial, you’ll know:
- What web applications are and how you can host them online
- How to convert a Python script into a Flask web application
- How to improve user experience by adding HTML to your Python code
- How to deploy your Python web application to Google App Engine
In addition to walking through an example project, you’ll find a number of exercises throughout the tutorial. They’ll give you a chance to solidify what you’re learning through extra practice. You can also download the source code that you’ll use to build your web application by clicking the link below:
Brush Up on the Basics
In this section, you’ll get a theoretical footing in the different topics that you’ll work with during the practical part of this tutorial:
- What types of Python code distribution exist
- Why building a web application can be a good choice
- What a web application is
- How content gets delivered over the Internet
- What web hosting means
- Which hosting providers exist and which one to use
Brushing up on these topics can help you feel more confident when writing Python code for the Web. However, if you’re already familiar with them, then feel free to skip ahead, install the Google Cloud SDK, and start building your Python web app.
Distribute Your Python Code
Bringing your code to your users is called distribution. Traditionally, there are three different approaches you can use to distribute your code so that others can work with your programs:
- Python library
- Standalone program
- Python web application
You’ll take a closer look at each of these approaches below.
Python Library
If you’ve worked with Python’s extensive package ecosystem, then you’ve likely installed Python packages with pip
. As a programmer, you might want to publish your Python package on PyPI to allow other users to access and use your code by installing it using pip
:
$ python3 -m pip install <your-package-name>
After you’ve successfully published your code to PyPI, this command will install your package, including its dependencies, on any of your users’ computers, provided that they have an Internet connection.
If you don’t want to publish your code as a PyPI package, then you can still use Python’s built-in sdist
command to create a source distribution or a Python wheel to create a built distribution to share with your users.
Distributing your code like this keeps it close to the original script you wrote and adds only what’s necessary for others to run it. However, using this approach also means that your users will need to run your code with Python. Many people who want to use your script’s functionality won’t have Python installed or won’t be familiar with the processes required to work directly with your code.
A more user-friendly way to present your code to potential users is to build a standalone program.
Standalone Program
Computer programs come in different shapes and forms, and there are multiple options for transforming your Python scripts into standalone programs. Below you’ll read about two possibilities:
- Packaging your code
- Building a GUI
Programs such as PyInstaller, py2app, py2exe, or Briefcase can help with packaging your code. They turn Python scripts into executable programs that can be used on different platforms without requiring your users to explicitly run the Python interpreter.
While packaging your code can resolve dependency problems, your code still just runs on the command line. Most people are used to working with programs that provide a graphical user interface (GUI). You can make your Python code accessible to more people by building a GUI for it.
While a standalone GUI desktop program can make your code accessible to a wider audience, it still presents a hurdle for people to get started. Before running your program, potential users have a few steps to get through. They need to find the right version for their operating system, download it, and successfully install it. Some may give up before they make it all the way.
It makes sense that many developers instead build web applications that can be accessed quickly and run on an Internet browser.
Python Web Application
The advantage of web applications is that they’re platform independent and can be run by anyone who has access to the Internet. Their code is implemented on a back-end server, where the program processes incoming requests and responds through a shared protocol that’s understood by all browsers.
Python powers many large web applications and is a common choice as a back-end language. Many Python-driven web applications are planned from the start as web applications and are built using Python web frameworks such as Flask, which you’ll use in this tutorial.
However, instead of the web-first approach described above, you’re going to take a different angle. After all, you weren’t planning to build a web application. You just created a useful Python script, and now you want to share with the world. To make it accessible to a broad range of users, you’ll refactor it into a web application and then deploy it to the Internet.
It’s time to go over what a web application is and how it’s different from other content on the Web.
Learn About Python Web Applications
Historically, websites had fixed content that was the same for every user who accessed that page. These web pages are called static because their content doesn’t change when you interact with them. When serving a static web page, a web server responds to your request by sending back the content of that page, regardless of who you are or what other actions you took.
You can browse an example of a static website at the first URL that ever went online, as well as the pages it links to:
Such static websites aren’t considered applications since their content isn’t generated dynamically by code. While static sites used to make up all of the Internet, most websites today are true web applications, which offer dynamic web pages that can change the content they deliver.
For instance, a webmail application allows you to interact with it in many ways. Depending on your actions, it can display different types of information, often while staying in a single page:
Python-driven web applications use Python code to determine what actions to take and what content to show. Your code is run by the web server that hosts your website, which means that your users don’t need to install anything. All they need to interact with your code is a browser and an Internet connection.
Getting Python to run on a website can be complicated, but there are a number of different web frameworks that automatically take care of the details. As mentioned above, you’ll build a basic Flask application in this tutorial.
In the upcoming section, you’ll get a high-level perspective on the main processes that need to happen to run your Python code on a server and deliver a response to your users.
Review the HTTP Request-Response Cycle
Serving dynamic content over the Internet involves a lot of different pieces, and they all have to communicate with one another to function correctly. Here’s a generalized overview of what takes place when a user interacts with a web application:
-
Sending: First, your user makes a request for a particular web page on your web app. They can do this, for example, by typing a URL into their browser.
-
Receiving: This request gets received by the web server that hosts your website.
-
Matching: Your web server now uses a program to match the user’s request to a particular portion of your Python script.
-
Running: The appropriate Python code is called up by that program. When your code runs, it writes out a web page as a response.
-
Delivering: The program then delivers this response back to your user through the web server.
-
Viewing: Finally, the user can view the web server’s response. For example, the resulting web page can be displayed in a browser.
This is a general process of how content is delivered over the Internet. The programming language used on the server, as well as the technologies used to establish that connection, can differ. However, the concept used to communicate across HTTP requests and responses remains the same and is called the HTTP Request-Response Cycle.
To allow Flask to handle requests on the server side, you’ll need to find a place where your Python code can live online. Storing your code online to run a web application is called web hosting, and there are a number of providers offering both paid and free web hosting.
Choose a Hosting Provider: Google App Engine
When choosing a web hosting provider, you need to confirm that it supports running Python code. Many of them cost money, but this tutorial will stick with a free option that’s professional and highly scalable yet still reasonable to set up: Google App Engine.
There are a number of other free options, such as PythonAnywhere, Repl.it, or Heroku that you can explore later on. Using Google App Engine will give you a good start in learning about deploying Python code to the web as it strikes a balance between abstracting away complexity and allowing you to customize the setup.
Google App Engine is part of the Google Cloud Platform (GCP), which is run by Google and represents one of the big cloud providers, along with Microsoft Azure and Amazon Web Services (AWS).
To get started with GCP, download and install the Google Cloud SDK for your operating system. For additional guidance beyond what you’ll find in this tutorial, you can consult Google App Engine’s documentation.
The Google Cloud SDK installation also includes a command-line program called gcloud
, which you’ll later use to deploy your web app. Once you’re done with the installation, you can verify that everything worked by typing the following command into your console:
You should receive a text output in your terminal that looks similar to the one below:
app-engine-python 1.9.91
bq 2.0.62
cloud-datastore-emulator 2.1.0
core 2020.11.13
gsutil 4.55
Your version numbers will probably be different, but as long as the gcloud
program is successfully found on your computer, your installation was successful.
With this high-level overview of concepts in mind and the Google Cloud SDK installed, you’re ready to set up a Python project that you’ll later deploy to the Internet.
Build a Basic Python Web Application
Google App Engine requires you to use a web framework for creating your web application in a Python 3 environment. Since you’re trying to use a minimal setup to get your local Python code up on the Internet, a microframework such as Flask is a good choice. A minimal implementation of Flask is so small that you might not even notice that you’re using a web framework.
The application you’re going to create will rely on several different files, so the first thing you need to do is to create a project folder to hold all these files.
Set Up Your Project
Create a project folder and give it a name that’s descriptive of your project. For this practice project, call the folder hello-app
. You’ll need three files inside this folder:
main.py
contains your Python code wrapped in a minimal implementation of the Flask web framework.requirements.txt
lists all the dependencies your code needs to work properly.app.yaml
helps Google App Engine decide which settings to use on its server.
While three files might sound like a lot, you’ll see that this project uses fewer than ten lines of code across all three files. This represents the minimal setup you need to provide to Google App Engine for any Python project you may launch. The rest will be your own Python code. You can download the complete source code that you’ll use in this tutorial by clicking the link below:
Next, you’ll take a look at the content of each of the files starting with the most complex one, main.py
.
Create main.py
main.py
is the file that Flask uses to deliver your content. At the top of the file, you import the Flask
class on line 1, then you create an instance of a Flask app on line 3:
1from flask import Flask
2
3app = Flask(__name__)
4
5@app.route("/")
6def index():
7 return "Congratulations, it's a web app!"
After you create the Flask app
, you write a Python decorator on line 5 called @app.route
that Flask uses to connect URL endpoints with code contained in functions. The argument to @app.route
defines the URL’s path component, which is the root path ("/"
) in this case.
The code on lines 6 and 7 makes up index()
, which is wrapped by the decorator. This function defines what should be executed if the defined URL endpoint is requested by a user. Its return value determines what a user will see when they load the page.
In other words, if a user types the base URL of your web app into their browser, then Flask runs index()
and the user sees the returned text. In this case, that text is just one sentence: Congratulations, it's a web app!
You can render more complex content, and you can also create more than one function so that users can visit different URL endpoints in your app to receive different responses. However, for this initial implementation, it’s fine to stick with this short and encouraging success message.
Create requirements.txt
The next file to look at is requirements.txt
. Since Flask is the only dependency of this project, that’s all you need to specify:
If your app has other dependencies, then you’ll need to add them to your requirements.txt
file as well.
Google App Engine will use requirements.txt
to install the necessary Python dependencies for your project when setting it up on the server. This is similar to what you would do after creating and activating a new virtual environment locally.
Create app.yaml
The third file, app.yaml
, helps Google App Engine set up the right server environment for your code. This file requires only one line, which defines the Python runtime:
The line shown above clarifies that the right runtime for your Python code is Python 3.8. This is enough for Google App Engine to do the necessary setup on its servers.
You can use Google App Engine’s app.yaml
file for additional setup, such as adding environment variables to your application. You can also use it to define the path to static content for your app, such as images, CSS or JavaScript files. This tutorial won’t go into these additional settings, but you can consult Google App Engine’s documentation on the app.yaml
Configuration File if you want to add such functionality.
These nine lines of code complete the necessary setup for this app. Your project is now ready for deployment.
However, it’s good practice to test your code before putting it into production so you can catch potential errors. Next, you’ll check whether everything works as expected locally before deploying your code to the Internet.
Test Locally
Flask comes packaged with a development web server. You can use this development server to double-check that your code works as expected. To be able to run the Flask development server locally, you need to complete two steps. Google App Engine will do the same steps on its servers once you deploy your code:
- Set up a virtual environment.
- Install the
flask
package.
To set up a Python 3 virtual environment, navigate to your project folder on your terminal and type the following command:
This will create a new virtual environment named venv
using the version of Python 3 that you have installed on your system. Next, you need to activate the virtual environment by sourcing the activation script:
$ source venv/bin/activate
After executing this command, your prompt will change to indicate that you’re now operating from within the virtual environment. After you successfully set up and activate your virtual environment, you’re ready to install Flask:
$ python3 -m pip install -r requirements.txt
This command fetches all packages listed in requirements.txt
from PyPI and installs them in your virtual environment. In this case, the only package installed will be Flask.
Wait for the installation to complete, then open up main.py
and add the following two lines of code at the bottom of the file:
if __name__ == "__main__":
app.run(host="127.0.0.1", port=8080, debug=True)
These two lines tell Python to start Flask’s development server when the script is executed from the command line. It’ll be used only when you run the script locally. When you deploy the code to Google App Engine, a professional web server process, such as Gunicorn, will serve the app instead. You won’t need to change anything to make this happen.
You can now start Flask’s development server and interact with your Python app in your browser. To do so, you need to run the Python script that starts the Flask app by typing the following command:
Flask starts up the development server, and your terminal will display output similar to the text shown below:
* Serving Flask app "main" (lazy loading)
* Environment: production
WARNING: This is a development server.
Do not use it in a production deployment.
Use a production WSGI server instead.
* Debug mode: on
* Running on http://127.0.0.1:8080/ (Press CTRL+C to quit)
* Restarting with stat
* Debugger is active!
* Debugger PIN: 315-059-987
This output tells you three important pieces of information:
-
WARNING
: This is Flask’s development server, which means you don’t want to use it to serve your code in production. Google App Engine will handle that for you instead. -
Running on http://127.0.0.1:8080/
: This is the URL where you can find your app. It’s the URL for your localhost, which means the app is running on your own computer. Navigate to that URL in your browser to see your code live. -
Press CTRL+C to quit
: The same line also tells you that you can exit the development server by pressing Ctrl+C on your keyboard.
Follow the instructions and open a browser tab at http://127.0.0.1:8080/
. You should see a page displaying the text that your function returns: Congratulations, it's a web app!
You can use Flask’s development server to inspect any changes that you make to the code of your Python app. The server listens to changes you make in the code and will automatically reload to display them. If your app doesn’t render as you expect it to on the development server, then it won’t work in production either. So make sure that it looks good before you deploy it.
Also keep in mind that even if it works well locally, it might not work quite the same once deployed. This is because there are other factors involved when you deploy your code to Google App Engine. However, for a basic app such as the one you’re building in this tutorial, you can be confident that it’ll work in production if it works well locally.
Change the return value of index()
and confirm that you can see the change reflected in your browser. Play around with it. What happens when you change the return value of index()
to HTML code, such as <h1>Hello</h1>
, instead of using a plain text string?
After having checked your setup and the code’s functionality on your local development server, you’re prepared to deploy it to Google App Engine.
Deploy Your Python Web Application
It’s finally time to bring your app online. But first, your code needs a place to live on Google’s servers, and you need to make sure that it gets there safely. In this section of the tutorial, you’ll work on completing the necessary deployment setups both in the cloud and locally.
Set Up on Google App Engine
Read through the setup process below step by step. You can compare what you see in your browser with the screenshots. The project name used in the example screenshots is hello-app
.
Start by signing in to the Google Cloud Platform. Navigate to the dashboard view, where you’ll see a toolbar at the top of the window. Select the downward-facing arrow button toward the left side of the toolbar. This will pop up a modal containing a list of your Google projects:
The modal displays a list of your projects. The list may be empty if you haven’t created any projects yet. On the top right of that modal, find the NEW PROJECT button and click it:
Clicking NEW PROJECT will redirect you to a new page where you can decide on a name for your project. This name will appear in the URL of your application, which will look similar to http://your-application-name.nw.r.appspot.com
. Use hello-app
as the name for this project to stay consistent with the tutorial:
You can see your project ID below the Project name input field. The project ID consists of the name you entered and a number that Google App Engine adds. In the case of this tutorial, you can see that the project ID is hello-app-295110
. Copy your personal project ID since you’ll need it later on for deploying.
You can now click CREATE and wait for the project to be set up on Google App Engine’s side. Once that’s done, a notification will pop up telling you that a new project has been created. It also gives you the option to select it. Go ahead and do that by clicking SELECT PROJECT:
Clicking SELECT PROJECT will redirect you to the main page of your new Google Cloud Platform project. It looks like this:
From here, you want to switch to the dashboard of Google App Engine. You can do that by clicking the hamburger menu on the top left, scrolling down to select App Engine in the first list, then selecting Dashboard on the top of the next pop-up list:
This will finally redirect you to the Google App Engine dashboard view of your new project. Since the project is empty so far, the page will look similar to this:
When you see this page, it means you have completed setting up a new project on Google App Engine. You’re now ready to head back to the terminal on your computer and complete the local steps necessary to deploy your app to this project.
Set Up Locally for Deployment
After successfully installing the Google Cloud SDK, you have access to the gcloud
command-line interface. This program comes with helpful instructions that guide you through deploying your web app. Start by typing the command that was suggested to you when you created a new project on the Google App Engine website:
As you can see in the bottom-right corner of the page, Google App Engine suggests a terminal command to deploy your code to this project. Open up your terminal, navigate to your project folder, then run the suggested command:
When you execute this command without any previous setup, the program will respond with an error message:
ERROR: (gcloud.app.deploy)
You do not currently have an active account selected.
Please run:
$ gcloud auth login
to obtain new credentials.
If you have already logged in with a different account:
$ gcloud config set account ACCOUNT
to select an already authenticated account to use.
You receive this error message because you can’t deploy any code to your Google App Engine account unless you prove to Google that you’re the owner of that account. You’ll need to authenticate with your Google App Engine account from your local computer.
The gcloud
command-line app already provided you with the command that you need to run. Type it into your terminal:
This will start the authentication process by generating a validation URL and opening it up in your browser. Complete the process by selecting your Google account in the browser window and granting Google Cloud SDK the necessary privileges. After you do this, you can return to your terminal, where you’ll see some information about the authentication process:
Your browser has been opened to visit:
https://accounts.google.com/o/oauth2/auth?client_id=<yourid>
You are now logged in as [<your@email.com>].
Your current project is [None]. You can change this setting by running:
$ gcloud config set project PROJECT_ID
If you see this message, then the authentication was successful. You can also see that the command-line program again offers you helpful information about your next step.
It tells you that there is currently no project set, and that you can set one by running gcloud config set project PROJECT_ID
. Now you’ll need the project ID that you noted earlier.
Be sure to replace hello-app-295110
with your own project ID when running the suggested command:
$ gcloud config set project hello-app-295110
Your terminal will print out a short feedback message that the project property has been updated. After successfully authenticating and setting the default project to your project ID, you have completed the necessary setup steps.
Run the Deployment Process
Now you’re ready to try the initial deployment command a second time:
The gcloud
app fetches your authentication credentials as well as the project ID information from the default configuration that you just set up and allows you to proceed. Next, you need to select a region where your application should be hosted:
You are creating an app for project [hello-app-295110].
WARNING: Creating an App Engine application for a project is
irreversible and the region cannot be changed.
More information about regions is at
<https://cloud.google.com/appengine/docs/locations>.
Please choose the region where you want your App Engine application
located:
[1] asia-east2
[2] asia-northeast1
[3] asia-northeast2
[4] asia-northeast3
[5] asia-south1
[6] asia-southeast2
[7] australia-southeast1
[8] europe-west
[9] europe-west2
[10] europe-west3
[11] europe-west6
[12] northamerica-northeast1
[13] southamerica-east1
[14] us-central
[15] us-east1
[16] us-east4
[17] us-west2
[18] us-west3
[19] us-west4
[20] cancel
Please enter your numeric choice:
Enter one of the numbers that are listed on the left side and press Enter.
After you enter a number, the CLI will continue with the setup process. Before deploying your code to Google App Engine, it’ll show you an overview of what the deployment will look like and ask you for a final confirmation:
Creating App Engine application in project [hello-app-295110]
and region [europe-west]....done.
Services to deploy:
descriptor: [/Users/realpython/Documents/helloapp/app.yaml]
source: [/Users/realpython/Documents/helloapp]
target project: [hello-app-295110]
target service: [default]
target version: [20201109t112408]
target url: [https://hello-app-295110.ew.r.appspot.com]
Do you want to continue (Y/n)?
After you confirm the setup by typing Y, your deployment will finally be on its way. Your terminal will show you some more information and a small loading animation while Google App Engine sets up your project on its servers:
Beginning deployment of service [default]...
Created .gcloudignore file. See `gcloud topic gcloudignore` for details.
╔════════════════════════════════════════════════════════════╗
╠═ Uploading 3 files to Google Cloud Storage ═╣
╚════════════════════════════════════════════════════════════╝
File upload done.
Updating service [default]...⠼
Since this is the first deployment of your web app, it may take a few minutes to complete. Once the deployment is finished, you’ll see another helpful output in the console. It’ll look similar to the one below:
Deployed service [default] to [https://hello-app-295110.ew.r.appspot.com]
You can stream logs from the command line by running:
$ gcloud app logs tail -s default
To view your application in the web browser run:
$ gcloud app browse
You can now navigate to the mentioned URL in your browser, or type the suggested command gcloud app browse
to access your live web app. You should see the same short text response that you saw earlier when running the app on your localhost: Congratulations, it's a web app!
Notice that this website has a URL that you can share with other people, and they’ll be able to access it. You now have a live Python web application!
Change the return value of index()
again and deploy your app a second time using the gcloud app deploy
command. Confirm that you can see the change reflected on the live website in your browser.
With this, you’ve completed the necessary steps to get your local Python code up on the web. However, the only functionality that you’ve put online so far is printing out a string of text.
Time to step it up! Following the same process, you’ll bring more interesting functionality online in the next section. You’ll refactor the code of a local temperature converter script into a Flask web app.
Convert a Script Into a Web Application
Since this tutorial is about creating and deploying Python web applications from code you already have, the Python code for the temperature converter script is provided for you here:
def fahrenheit_from(celsius):
"""Convert Celsius to Fahrenheit degrees."""
try:
fahrenheit = float(celsius) * 9 / 5 + 32
fahrenheit = round(fahrenheit, 3) # Round to three decimal places
return str(fahrenheit)
except ValueError:
return "invalid input"
if __name__ == "__main__":
celsius = input("Celsius: ")
print("Fahrenheit:", fahrenheit_from(celsius))
This is a short script that allows a user to convert a Celsius temperature to the equivalent Fahrenheit temperature.
Save the code as a Python script and give it a spin. Make sure that it works as expected and that you understand what it does. Feel free to improve the code.
With this working script in hand, you’ll now need to change the code to integrate it into your Flask app. There are two main points to consider for doing that:
- Execution: How will the web app know when to run the code?
- User input: How will the web app collect user input?
You already learned how to tell Flask to execute a specific piece of code by adding the code to a function that you assign a route to. Start by tackling this task first.
Add Code as a Function
Flask separates different tasks into different functions that are each assigned a route through the @app.route
decorator. When the user visits the specified route via its URL, the code inside the corresponding function gets executed.
Start by adding fahrenheit_from()
to your main.py
file and wrapping it with the @app.route
decorator:
from flask import Flask
app = Flask(__name__)
@app.route("/")
def index():
return "Congratulations, it's a web app!"
@app.route("/")
def fahrenheit_from(celsius):
"""Convert Celsius to Fahrenheit degrees."""
try:
fahrenheit = float(celsius) * 9 / 5 + 32
fahrenheit = round(fahrenheit, 3) # Round to three decimal places
return str(fahrenheit)
except ValueError:
return "invalid input"
if __name__ == "__main__":
app.run(host="127.0.0.1", port=8080, debug=True)
So far, you’ve only copied the code of your Python script into a function in your Flask app and added the @app.route
decorator.
However, there’s already a problem with this setup. What happens when you run the code in your development server? Give it a try.
Currently, both of your functions are triggered by the same route ("/"
). When a user visits that route, Flask picks the first function that matches it and executes that code. In your case, this means that fahrenheit_from()
never gets executed because index()
matches the same route and gets called first.
Your second function will need its own unique route to be accessible. Additionally, you still need to allow your users to provide input to your function.
Pass Values to Your Code
You can solve both of these tasks by telling Flask to treat any remaining part of the URL following the base URL as a value and pass it on to your function. This requires only a small change to the parameter of the @app.route
decorator before fahrenheit_from()
:
@app.route("/<celsius>")
def fahrenheit_from(celsius):
# -- snip --
The angle bracket syntax (<>
) tells Flask to capture any text following the base URL ("/"
) and pass it on to the function the decorator wraps as the variable celsius
. Note that fahrenheit_from()
requires celsius
as an input.
Head back to your web browser and try out the new functionality using Flask’s development server. You’re now able to access both of your functions through your web app using different URL endpoints:
- Index (
/
): If you go to the base URL, then you’ll see the short encouraging message from before. - Celsius (
/42
): If you add a number after the forward slash, then you’ll see the converted temperature appear in your browser.
Play around with it some more and try entering different inputs. Even the error handling from your script is still functional and displays a message when a user enters a nonnumeric input. Your web app handles the same functionality as your Python script did locally, only now you can deploy it to the Internet.
Refactor Your Code
Flask is a mature web framework that allows you to hand over a lot of tasks to its internals. For example, you can let Flask take care of type checking the input to your function and returning an error message if it doesn’t fit. All this can be done with a concise syntax inside of the parameter to @app.route
. Add the following to your path capturer:
@app.route("/<int:celsius>")
Adding int:
before the variable name tells Flask to check whether the input it receives from the URL can be converted to an integer. If it can, then the content is passed on to fahrenheit_from()
. If it can’t, then Flask displays a Not Found
error page.
After applying Flask’s type check, you can now safely remove the try
… except
block in fahrenheit_from()
. Only integers will ever be passed on to the function by Flask:
from flask import Flask
app = Flask(__name__)
@app.route("/")
def index():
return "Congratulations, it's a web app!"
@app.route("/<int:celsius>")
def fahrenheit_from(celsius):
"""Convert Celsius to Fahrenheit degrees."""
fahrenheit = float(celsius) * 9 / 5 + 32
fahrenheit = round(fahrenheit, 3) # Round to three decimal places
return str(fahrenheit)
if __name__ == "__main__":
app.run(host="127.0.0.1", port=8080, debug=True)
With this, you’ve completed converting your temperature conversion script into a web app. Confirm that everything works as expected locally, then deploy your app again to Google App Engine.
Refactor index()
. It should return text that explains how to use the temperature converter web app. Keep in mind that you can use HTML tags in the return string. The HTML will render properly on your landing page.
After successfully deploying your temperature conversion web app to the Internet, you now have a link that you can share with other people and allow them to convert Celsius temperatures to Fahrenheit temperatures.
However, the interface still looks quite basic and the web app functions more like an API than a front-end web app. Many users might not know how to interact with your Python web application in its current state. This shows you the limitations of using pure Python for web development.
If you want to create more intuitive interfaces, then you’ll need to start using at least a little bit of HTML.
In the next section, you’ll keep iterating over your code and use HTML to create an input box that allows users to enter a number directly on the page rather than through the URL.
Improve the User Interface of Your Web Application
In this section, you’ll learn how to add an HTML <form>
input element to your web app to allow users to interact with it in a straightforward manner that they’re used to from other online applications.
To improve the user interface and user experience of your web app, you’ll need to work with languages other than Python, namely front-end languages such as HTML, CSS, and JavaScript. This tutorial avoids going into these as much as possible, to remain focused on using Python.
However, if you want to add an input box to your web app, then you’ll need to use some HTML. You’ll implement only the absolute minimum to get your web app looking and feeling more like a website that users will be familiar with. You’ll use the HTML <form>
element to collect their input.
After the update to your web app, you’ll have a text field where the user can input a temperature in degrees Celsius. There will be a Convert button to convert the user-supplied Celsius temperature into degrees Fahrenheit:
The converted result will be displayed on the next line and will be updated whenever the user clicks Convert.
You’ll also change the functionality of the app so that both the form and the conversion result are displayed on the same page. You’ll refactor the code so that you only need a single URL endpoint.
Collect User Input
Start by creating a <form>
element on your landing page. Copy the following few lines of HTML into the return statement of index()
, replacing the text message from before:
@app.route("/")
def index():
return """<form action="" method="get">
<input type="text" name="celsius">
<input type="submit" value="Convert">
</form>"""
When you reload your page at the base URL, you’ll see an input box and a button. The HTML renders correctly. Congratulations, you just created an input form!
What happens when you enter a value and then click Convert? While the page looks just the same, you might notice that the URL changed. It now displays a query parameter with a value after the base URL.
For example, if you entered 42
into the text box and clicked the button, then your URL would look like this: http://127.0.0.1:8080/?celsius=42
. This is good news! The value was successfully recorded and added as a query parameter to the HTTP GET request. Seeing this URL means that you’re once again requesting the base URL, but this time with some extra values that you’re sending along.
However, nothing currently happens with that extra value. While the form is set up as it should be, it’s not yet correctly connected to the code functionality of your Python web app.
In order to understand how to make that connection, you’ll read about each piece of the <form>
element to see what the different parts are all about. You’ll look at the following three elements and their attributes separately:
<form>
element- Input box
- Submit button
Each of these are separate HTML elements. While this tutorial aims to keep the focus on Python rather than HTML, it’ll still be helpful to have a basic understanding of what goes on in this block of HTML code. Start by looking at the outermost HTML element.
<form>
Element
The <form>
element creates an HTML form. The other two <input>
elements are wrapped inside it:
<form action="" method="get">
<input type="text" name="celsius" />
<input type="submit" value="Convert" />
</form>
The <form>
element also contains two HTML attributes called action
and method
:
-
action
determines where the data that the user submits will be sent. You’re leaving the value as an empty string here, which makes your browser direct the request to the same URL it was called from. In your case, that’s the empty base URL. -
method
defines what type of HTTP request the form produces. Using the default of"get"
creates an HTTP GET request. This means that the user-submitted data will be visible in the URL query parameters. If you were submitting sensitive data or communicating with a database, then you would need to use an HTTP POST request instead.
After inspecting the <form>
element and its attributes, your next step is to take a closer look at the first of the two <input>
elements.
Input Box
The second HTML element is an <input>
element that’s nested inside the <form>
element:
<form action="" method="get">
<input type="text" name="celsius" />
<input type="submit" value="Convert" />
</form>
The first <input>
element has two HTML attributes:
-
type
defines what type of<input>
element should be created. There are many to choose from, such as checkboxes and drop-down elements. In this case, you want the user to enter a number as text, so you’re setting the type to"text"
. -
name
defines what the value the user enters will be referred to as. You can think of it as the key to a dictionary, where the value is whatever the user inputs into the text box. You saw this name show up in the URL as the key of the query parameter. You’ll need this key later to retrieve the user-submitted value.
HTML <input>
elements can have different shapes, and some of them require different attributes. You’ll see an example of this when looking at the second <input>
element, which creates a Submit button and is the last HTML element that makes up your code snippet.
Submit Button
The second <input>
element creates the button that allows your users to submit their input:
<form action="" method="get">
<input type="text" name="celsius" />
<input type="submit" value="Convert" />
</form>
This element also has two HTML attributes, which are named type
and value
:
-
type
defines what sort of input element will be created. Using the value"submit"
creates a button that allows you to send the bundled-up form data onwards. -
value
defines what text the button should display. Feel free to change it to see how the button displays your changed text.
With this short overview of the different HTML elements and their attributes in mind, you now have a better understanding of what you’re adding to your Python code and what the elements are used for.
The information that you’ll need to connect your form submission to your Flask code is the first <input>
element’s name
value, celsius
, which you’ll use to access the submitted value in your function.
Next, you’ll learn how to change your Python code to correctly process the submitted form input.
Receive User Input
In the action
attribute of your <form>
element, you specified that the data of your HTML form should be sent back to the same URL it came from. Now you need to include the functionality to fetch the value in index()
. For this, you need to accomplish two steps:
-
Import Flask’s
request
object: Like many web frameworks, Flask passes HTTP requests along as global objects. In order to be able to use this globalrequest
object, you first need to import it. -
Fetch the value: The
request
object contains the submitted value and gives you access to it through a Python dictionary syntax. You need to fetch it from the global object to be able to use it in your function.
Rewrite your code and add these two changes now. You’ll also want to add the captured value at the end of the form string to display it after the form:
from flask import Flask
from flask import request
app = Flask(__name__)
@app.route("/")
def index():
celsius = request.args.get("celsius", "")
return (
"""<form action="" method="get">
<input type="text" name="celsius">
<input type="submit" value="Convert">
</form>"""
+ celsius
)
@app.route("/<int:celsius>")
def fahrenheit_from(celsius):
"""Convert Celsius to Fahrenheit degrees."""
fahrenheit = float(celsius) * 9 / 5 + 32
fahrenheit = round(fahrenheit, 3) # Round to three decimal places
return str(fahrenheit)
if __name__ == "__main__":
app.run(host="127.0.0.1", port=8080, debug=True)
The request.args
dictionary contains any data submitted with an HTTP GET request. If your base URL gets called initially, without a form submission, then the dictionary will be empty and you’ll return an empty string as the default value instead. If the page gets called through submitting the form, then the dictionary will contain a value under the celsius
key, and you can successfully fetch it and add it to the returned string.
Give it a spin! You’re now able to enter a number and see it displayed right underneath the form’s button. If you enter a new number, then the old one gets replaced. You’re correctly sending and receiving the data that your users are submitting.
Before you move on to integrate the submitted value with your temperature converter code, are there any potential problems you can think of with this implementation?
What happens when you enter a string instead of a number? Give it a try.
Now enter the short HTML code <marquee>BUY USELESS THINGS!!!</marquee>
and press Convert.
Currently, your web app accepts any kind of input, be it a number, a string, or even HTML or JavaScript code. This is extremely dangerous because your users might accidentally or intentionally break your web app by entering specific types of content.
Most of the time you should allow Flask to take care of these security issues automatically by using a different project setup. However, you’re in this situation now, so it’s good idea to find out how you can manually make the form you created input safe.
Escape User Input
Taking input from a user and displaying that input back without first investigating what you’re about to display is a huge security hole. Even without malicious intent, your users might do unexpected things that cause your application to break.
Try to hack your unescaped input form by adding some HTML text to it. Instead of entering a number, copy the following line of HTML code, paste it into your input box, and click Convert:
<marquee><a href="https://www.realpython.com">CLICK ME</a></marquee>
Flask inserts the text directly into HTML code, which causes this text input to get interpreted as HTML tags. Because of that, your browser renders the code dutifully, as it would with any other HTML. Instead of displaying back the input as text, you suddenly have to deal with a stylish educational spam link that time-traveled here right from the ’90s:
While this example is harmless and goes away with a refresh of your page, you can imagine how this might present a security problem when other types of content are added in this way. You don’t want to open up the possibility of your users editing aspects of your web app that aren’t meant to be edited.
To avoid this, you can use Flask’s built-in escape()
, which converts the special HTML characters <
, >
, and &
into equivalent representations that can be displayed correctly.
You’ll first need to import escape
into your Python script to use this functionality. Then, when you submit the form, you can convert any special HTML characters and make your form input ’90s hacker–proof:
from flask import Flask
from flask import request, escape
app = Flask(__name__)
@app.route("/")
def index():
celsius = str(escape(request.args.get("celsius", "")))
return (
"""<form action="" method="get">
<input type="text" name="celsius">
<input type="submit" value="Convert">
</form>"""
+ celsius
)
@app.route("/<int:celsius>")
def fahrenheit_from(celsius):
"""Convert Celsius to Fahrenheit degrees."""
fahrenheit = float(celsius) * 9 / 5 + 32
fahrenheit = round(fahrenheit, 3) # Round to three decimal places
return str(fahrenheit)
if __name__ == "__main__":
app.run(host="127.0.0.1", port=8080, debug=True)
Refresh your development server and try submitting some HTML code. Now it’ll be displayed back to you as the text string that you entered.
After learning how to collect user input and also how to escape it, you’re finally ready to implement the temperature conversion functionality and show a user the Fahrenheit equivalent of the Celsius temperature they entered.
Process User Input
Since this approach uses only one URL endpoint, you can’t rely on Flask to type check the user input via URL path component capturing as you did earlier on. This means you’ll want to reintroduce your try
… except
block from the initial fahrenheit_from()
of the original code.
This time, fahrenheit_from()
won’t be associated with an @app.route
decorator. Go ahead and delete that line of code. You’ll call fahrenheit_from()
explicitly from index()
instead of asking Flask to execute it when a specific URL endpoint is accessed.
After deleting the decorator from fahrenheit_from()
and reintroducing the try
… except
block, you’ll next add a conditional statement to index()
that checks whether the global request
object contains a celsius
key. If it does, then you want to call fahrenheit_from()
to calculate the corresponding Fahrenheit degrees. If it doesn’t, then you assign an empty string to the fahrenheit
variable instead.
Doing this allows you to add the value of fahrenheit
to the end of your HTML string. The empty string won’t be visible on your page, but if the user submitted a value, then it’ll show up underneath the form.
After applying these final changes, you complete the code for your temperature converter Flask app:
1from flask import Flask
2from flask import request
3
4app = Flask(__name__)
5
6@app.route("/")
7def index():
8 celsius = request.args.get("celsius", "")
9 if celsius:
10 fahrenheit = fahrenheit_from(celsius)
11 else:
12 fahrenheit = ""
13 return (
14 """<form action="" method="get">
15 Celsius temperature: <input type="text" name="celsius">
16 <input type="submit" value="Convert to Fahrenheit">
17 </form>"""
18 + "Fahrenheit: "
19 + fahrenheit
20 )
21
22def fahrenheit_from(celsius):
23 """Convert Celsius to Fahrenheit degrees."""
24 try:
25 fahrenheit = float(celsius) * 9 / 5 + 32
26 fahrenheit = round(fahrenheit, 3) # Round to three decimal places
27 return str(fahrenheit)
28 except ValueError:
29 return "invalid input"
30
31if __name__ == "__main__":
32 app.run(host="127.0.0.1", port=8080, debug=True)
Since there have been quite a few changes, here’s a step-by-step review of the edited lines:
-
Line 2: You’re not using
flask.escape()
anymore, so you can remove it from the import statement. -
Lines 8, 11, and 12: As before, you’re fetching the user-submitted value through Flask’s global
request
object. By using the dictionary method.get()
, you assure that an empty string gets returned if the key isn’t found. That’ll be the case if the page is loaded initially and the user hasn’t submitted the form yet. This is implemented in lines 11 and 12. -
Line 19: By returning the form with the default empty string stuck to the end, you avoid displaying anything before the form has been submitted.
-
Lines 9 and 10: After your users enter a value and click Convert, the same page gets loaded again. This time around,
request.args.get("celsius", "")
finds thecelsius
key and returns the associated value. This makes the conditional statement evaluate toTrue
, and the user-provided value is passed tofahrenheit_from()
. -
Lines 24 to 29:
fahrenheit_from()
checks if the user supplied a valid input. If the provided value can be converted to afloat
, then the function applies the temperature conversion code and returns the temperature in Fahrenheit. If it can’t be converted, then aValueError
exception is raised, and the function returns the string"invalid input"
instead. -
Line 19: This time, when you concatenate the
fahrenheit
variable to the end of the HTML string, it points to the return value offahrenheit_from()
. This means that either the converted temperature or the error message string will be added to your HTML. -
Lines 15 and 18: To make the page easier to use, you also add the descriptive labels
Celsius temperature
andFahrenheit
to this same HTML string.
Your page will render correctly even though the way you’re adding these strings doesn’t represent valid HTML. This works thanks to the power of modern browsers.
Keep in mind that if you’re interested in diving deeper into web development, then you’ll need to learn HTML. But for the sake of getting your Python script deployed online, this will do just fine.
You should now be able to use your temperature conversion script inside your browser. You can supply a Celsius temperature through the input box, click the button, and see the converted Fahrenheit result appear on the same web page. Since you’re using the default HTTP GET request, you can also see the submitted data appear in the URL.
Deploy your finished application again to Google App Engine using the gcloud app deploy
command. Once the deployment is done, go to the provided URL or run gcloud app browse
to see your Python web application live on the Internet. Test it out by adding different types of input. Once you’re satisfied, share your link with the world.
The URL of your temperature converter web application still looks something like https://hello-app-295110.ew.r.appspot.com/
. This doesn’t reflect the current functionality of your app.
Revisit the deployment instructions, create a new project on Google App Engine with a better fitting name, and deploy your app there. This will give you practice in creating projects and deploying your Flask apps to Google App Engine.
At this point, you’ve successfully converted your Python script into a Python web app and deployed it to Google App Engine for online hosting. You can use the same process to convert more of your Python scripts into web apps.
Create your own poem generator that allows users to create short poems using a web form. Your web application should use a single page with a single form that accepts GET requests. You can use this example code to get started, or you can write your own.
If you want to learn more about what you can do with Google App Engine, then you can read about using static files and add a CSS file to your Python web application to improve its overall appearance.
Hosting your code online can make it accessible to more people over the Internet. Go ahead and convert your favorite scripts into Flask applications and show them to the world.
Conclusion
You covered a lot of ground in this tutorial! You started with a local Python script and transformed it into a user-friendly, fully deployed Flask application that’s now hosted on Google App Engine.
While working through this tutorial, you learned:
- How web applications provide data over the Internet
- How to refactor your Python script so you can host it online
- How to create a basic Flask application
- How to manually escape user input
- How to deploy your code to Google App Engine
You can now take your local Python scripts and make them available online for the whole world to use. If you’d like to download the complete code for the application you built in this tutorial, then you can click the link below:
If you want to learn more about web development with Python, then you’re now well equipped to experiment with Python web frameworks such as Flask and Django. Keep up the good work!
Watch Now This tutorial has a related video course created by the Real Python team. Watch it together with the written tutorial to deepen your understanding: Deploy Your Python Script on the Web With Flask
Мы живём в золотой век web-разработки, имея в своём распоряжении гибкие веб-технологии и массу полезных инструментов и интерфейсов, позволяющих реализовывать замыслы практически любой сложности. И язык программирования Python — один из них.
Python и web
Популярность Питона в веб-разработке подтверждается, к примеру, простым взглядом на рейтинг портала HotFrameworks.com. Там можно увидеть, что один из самых популярных фреймворков для создания веб-приложений предназначен именно для Пайтона (разумеется, речь идёт о Django, который на момент написания материала находился на 6 месте).
Говоря о вебе, скажем, что Python применяется для обработки бэкенда и маршрутизации, где еще в роли конкурентов выступают PHP и Ruby. Ну а web-страницы всё равно следует отображать посредством HTML/CSS, причём функциональная часть фронтенда, как и прежде, реализуется на JavaScript.
Таким образом, без взаимодействия разных технологий и интерфейсов (interface) не обойтись. Но это не проблема, ведь Python-фреймворки позволяют существенно упрощать это взаимодействие. Тот же Django включает в себя систему шаблонов, обеспечивающих написание специальных HTML-файлов, которые могут вставлять Python-код, взаимодействуя с данными из бэкенда. Этот тип взаимодействия принято называть Full Stack-фреймворком. С его помощью вы сможете работать с шаблонами web-страниц, запросами маршрутизации, хранилищами баз данных, системами, обрабатывающими HTTP-запросы, и т. д.
Но существуют и не Full Stack-фреймворки, а так называемые микрофреймворки, обрабатывающие лишь базовую логику. Они более гибкие, изучаются в кратчайшие сроки, но потребуют от вас каждый раз изобретать велосипед, ведь для сторонних работ их придётся объединять с шаблонизаторами, сторонними БД и прочее.
Пайтон и другие языки
Может возникнуть закономерный, но риторический вопрос: «Если я использую JavaScript для внешнего интерфейса, почему я не могу применять тот же JavaScript для бэкенда?» И это действительно так, но то же самое можно сказать и про PHP, ведь язык востребован, существует десятилетиями и имеет большое количество технической документации. Следовательно, для бэкенда прекрасно подходит и PHP.
Вывод прост: большинство задач при разработке веба вы сможете решить с применением любого языка. По-настоящему важно лишь то, насколько удобно вам это делать на выбранном языке. Таким образом, если вы предпочитаете Питон, зачем от него отказываться и тратить время на изучение чего-нибудь другого? Сегодня использование этого языка для веба обычное явление (it’s common).
Frameworks
Мы уже говорили о том, что веб-разработка на Python предполагает использование специальных фреймворков. Их много, и каждый по-своему хорош. Обычно проблем с документацией, сообществами и официальной поддержкой не возникает.
Наиболее известны фреймворки Django и Flask. Мы уже о них писали, поэтому повторяться не будем. Разве что, упомянем такой фреймворк, как Pyramid — некий компромиссе между Flask и Django. Да, Pyramid не настолько функционален, но удобен и прост, плюс имеет достаточно средств для организации большинства web-приложений. Также он имеет широкую библиотеку как официальных, так и неофициальных плагинов, используя которые, вы успешно реализуете необходимые замыслы.
Python и real life
А как быть с реальным применением для написания веба? Тут всё очень неплохо, достаточно вспомнить лишь следующие проекты, реализованные посредством Python. Просто загибайте пальцы:
— YouTube,
— Google,
— Reddit,
— Instagram,
— DropBox,
— FireFox,
— Pinterest и многие другие.
Да, в некоторых проектах Python применяется лишь частично, но его влияние на web всё же велико.
CGI: исполнение простейшего скрипта
Создавать динамические страницы на Питоне можно посредством скриптов CGI. Скрипты CGI представляют собой исполняемые файлы — они выполняются web-сервером, если в URL запрашивается соответствующий скрипт. В этой части статьи мы расскажем про то, как запустить скрипт на локальном сервере и вывести на экран простейшие данные, к примеру, Hello world,.
Настраиваем локальный сервер
В «Пайтон» CGI-сервер уже встроен, поэтому настроить его несложно. Команда ниже позволит запустить его из консоли (если у вас Linux). Причем запускать команду следует из папки, в которой желаем работать:
Если у вас операционная система Windows, запуск Python-файла будет еще проще. Важно, чтобы файл находился в той же папке, в которой планируется работа:
Следующее, что нужно сделать, — открыть браузер и набрать в адресной строке localhost:8000.
Итог должен быть приблизительно следующим:
Hello world
Далее в папке, где запускался сервер, следует создать папку cgi-bin. Потом следует создать скрипт hello.py, куда будут включены следующие данные:
Первая строка свидетельствует, что это Python-скрипт, вторая печатает заголовок, который значит, что это будет html-файл (веб-браузер различает файлы по заголовкам). Третья строка отделяет заголовки от тела ответа, четвертая — печатает «Hello world».
Останется перейти на localhost:8000/cgi-bin/hello.py, чтобы убедиться, что выводятся данные «Hello world».
Если по каким-то причинам что-то не работает, проверьте, правильно ли выставлены права на выполнение.
Когда мы говорим о вебе, вышеописанные действия — лишь начало пути. Если хотите двигаться дальше, надо будет:
1) позаботиться об обработке данных и cookies;
2) для обеспечения возможности работы с пользовательскими данными, эти данные надо будет где-либо хранить. Проще всего — хранить данные в файлах (но это не самое практичное и безопасное решение). Лучший способ — хранение данных в базе данных;
3) и вот только после этого можно будет приступить к публикации простейшего приложения в интернете. То есть речь идет о запуске cgi на виртуальном сервере, а не локальном.
Обучение
Может появиться вопрос: «Где научиться Python именно в контексте веб-разработки?» Что же, у нас есть на него ответ. В OTUS уже не первый год существует курс, посвящённый именно этому направлению в программировании. Однако никто не мешает вам освоить и общий курс продвинутой разработки на Python. Или даже овладеть профессией с нуля, изучив «Подготовительный курс по Python-разработке». Выбор за вами!
Источники:
• http://www.makeuseof.com/tag/python-web-development-build/;
• https://pythonworld.ru/web.
Одна из няшних самых горячих тем сегодня — это «веб-приложения.» В отличие от традиционного «исполнимого» программного обеспечения, которое выполняется в определенном месте на настольном компьютере, веб приложение выполняется на централизованном сервере и предоставляет его возможности через Интернет, обычно через протокол HTTP и обычный веб-браузер. Веб приложения все более и более популярны, потому что они могут быть легоко доступны — просто введите URL в браузер — и приложение доступно одновременно целому ряду пользователей. Некоторые веб приложения обеспечивают электронную коммерцию (eBay), некоторые служат для развлечений (как например Yahoo! Games), и другие, как например Salesforce.com, управляют информацией предприятия.
В то время как Java, Perl, и PHP часто восхваляются как идеальные языки программирования для разработки веб приложений, возможности Python в этой области такие же. Действительно, Python отлично для создания динамического содержимого веб страниц.
Самый простой путь создания веб приложение на Python — использовать Common Gateway Interface (CGI). CGI — это протокол, который описывает, как соединить клиентов с веб приложениями.
Обычно, когда вы запрашиваете статическую страницу у веб-сервера, он находит файл, что вы запросили и посылает файл обратно в ответ. Например, запрос на http://www.example.com/contact.html возвращает страницу HTML contact.html. Однако, если запрос ссылается на сценарий CGI, то вместо возвращения сценария (его кода как содержимого), сценарий запускается и результат работы сценария присылается в ответ. Сценарии CGI генерируют содержимое динамически в ответ на запрос (и его параметры, как вы вскоре увидите).
Однажды поняв, как работает CGI, создание динамического содержимого становится простым использованием команды print. И в противоположность его репутации, CGI не обязательно медленен. Даже если интерпретатор Python запускается для каждого вызова сценария, рекомендуется попробовать CGI перед выбором более сложной веб структуры приложения.
Углубимся в программирование CGI на Python. Первая из двух частей объясняет основы CGI, описывает, как посылаются формы HTML и объясняет, как обработать введенные в форму данные.
Весь код в этой статье должен работать в Python 2.2 и более поздних версиях
Заголовки и концы строк
Половина работы при написания веб приложения — это возвращение правильных заголовков в ответ на запрос. Пересылка действительных заголовков важна не только для информации, которую получает клиент — если ваша программа не выдает действительных заголовков, веб-сервер предполагает, что ваш сценарий выполнился неуспешно и показывает устрашащую Ошибку 500… Внутренняя Ошибка Сервера.
Есть уйма различных заголовков, которые вы можете послать. Как минимум вы должны послать заголовок (фактически, во многих ситуациях это, возможно, является единственным заголовкмв, который вам нужно послать) Content-Type и вы должны завершить ваш список заголовков пустой линией.
Все заголовки выглядят как «тип_заголовка: имя_заголовока-значениеrn». Однако, большинство клиентов и серверов позволяют лишь n, то есть то, что вы получите в качестве нормального завершения строки на системах типа UNIX.
Hello World
Составим обязательную для новичков программу «Hello, World» как CGI скрипт:
#!/usr/bin/python import sys try: import cgitb cgitb.enable() except ImportError: sys.stderr = sys.stdout def cgiprint(inline=''): sys.stdout.write(inline) sys.stdout.write('rn') sys.stdout.flush() contentheader = 'Content-Type: text/html' thepage = '''< html>< head> < title>%s< /title> < /head>< body> %s < /body>< /html> ''' h1 = '< h1>%s< /h1>' if __name__ == '__main__': cgiprint(contentheader) # заголовок содержимого cgiprint() # завершить заголовки пустой строкой title = 'Hello World' headline = h1 % 'Hello World' print thepage % (title, headline)
Давайте пройдемся по коду.
Если вы запускаете сценарий CGI на системе Linux или Unix, вы должны включать обязательную линию (#!/usr/bin/python) «shebang» в строке 1, чтобы сказать сценарию, где найти Python.
Следующая часть сценария — это try/except блок, который пытается импортировать модуль cgitb. Обычно ошибки в программе на Python посылаются в sys.stderr. Однако, когда запускается CGI скрипт, sys.stderr транслирует в журнал регистрации ошибок сервера. Но постоянный поиск ошибок в журнале регистрации ошибок — это неудобно при отлаживании. Вместо этого cgitb выводит сообщения об ошибках, в том числе полезную информацию подобно значениям переменных, в браузер. (Этот модуль был только введен в Python 2.2.) Если импорт не удается, stderr присоединяется к stdout, который делает подобную, но не такую эффективную работу. (Не используйте модуль cgitb в производственных приложениях. Показываемая им информация включает детали о вашей системе, которая, возможно, полезна для возможного взломщика.)
Потом, cgiprint() выпускает две строки заголовков и должным образом завершает заголовки правильными окончаниями строк. (cgiprint() должна использоваться только для строк заголовков.) cgiprint() посылает заголовок Content-Type. Поскольку сценарий возвращает веб-страницу (которая является формой текста) тип/подтип заголовка — это text/html. Посылается только один заголовок, затем заголовки завершаются пустой строкой.
cgiprint() сбрасывает буфер выходного потока, используя sys.stdout.flush(). Большинство серверов буферизует выдачу сценариев, пока сценарий не завершен. Для сценариев, которые долго выполняются, буферизация выхода, возможно, расстроит вашего пользователя. Вы можете или регулярно сбрасывать ваш буфер, или запускать Python в небуферизованном режиме. Вариант командной строки: -u, который вы можете вставить как #!/usr/bin/python -u.
В конце концов, сценарий посылает маленькую страницу HTML, которая должна выглядеть знакомо, если вы использовали HTML до этого момента.
Пользовательский интерфейс и формы HTML
При написании CGI скриптов, ваш интерфейс пользователя — это веб-браузер. Объединеняя Javascript, Динамический HTML (DHTML) и HTML формы, вы можете создавать богатые возможностями веб приложения.
Основными элементами HTML, используемыми для общения с CGI скриптами, являются формы и элементы формы для пользовательского ввода, в том числе текстовые поля, радиокнопки, переключатели, меню и так далее.
Пример формы
Типичная, простая форма HTML, возможно, кодировалась бы подобно этому:
< form action="/cgi-bin/formprocessor.py" method="get"> What is Your Name : < input name="param1" type="text" value="Joe Bloggs" />< br /> < input name="param2" type="radio" value="this" checked="checked" /> Select This< br /> < input name="param2" type="radio" value="that" />or That< br /> < input name="param3" type="checkbox" checked="checked" /> Check This< br /> < input name="param4" type="checkbox" checked="checked" /> and This Too ?< br /> < input name="hiddenparam" type="hidden" value="some_value" /> < input type="reset" /> < input type="submit" /> < /form>
Когда пользователь нажимает кнопку Submit, данные из полей формы включается в запрос HTTP. Внутри тега form есть два параметра, которые определяют то, как происходит включение. Параметр action — это URI вашего сценария CGI, то есть куда направляется запрос. Параметр method конкретизирует, как значения кодируются в запросе. Два возможных метода — GET и POST.
Более простой — GET. При использовании GET значения формы кодируются, чтобы быть «URL безопасными» и добавляются в конец URL как список параметров. С POST кодируемые значения посылаются в теле запроса после того, как заголовки посланы.
GET проще, однако длина URL ограничена. Таким образом, использование GET навязывает максимальный ограничение для формы, которая может быть послана. (Около 1,000 символов — это ограничение многих серверов.) Если вы используете форму, чтобы получить длинный текстовый ввод из вашей формы, используйте POST. POST более подходит для запросов, где посылается больше данных.
Одно преимущество GET — то, что вы можете закодировать параметры для скрипта в нормальную ссылку HTML. Это означает, что параметры могут быть отправлены вашей программе без нажатия кнопки пользователем. Кодируемый набор значений выглядит примерно так:
param1=value1¶m2=value+2¶m3=value%263(Http GET запрос добавляет эту строку к URL.) Так что, целый URL, возможно, стал бы чем-нибудь вроде http://www.someserver.com/cgi-bin/test.py?param1=value1¶m2=value+2¶m3=value%26.
? отделяет URI вашего сценария от кодируемых параметров. Символы & отделяют параметры друг от друга. + представляет пространство (которое не нужно посылать как часть URI, конечно), и %26 — это кодируемое значение, которое представляет &. & не нужно посылать как часть значения, иначе CGI думал бы, что был послан новый параметр.
Если вы кодируете ваши собственные значения в URL, используйте функцию urllib.encode() от модуля urllib подобно этому:
value_dict = { ‘param_1’ : ‘value1’, ‘param_2’ : ‘value2’ }
encoded_params = urllib.encode(value_dict)
full_link = script_url + ‘?’ + encoded_params
Получение данных из форм
Формы HTML инкапсулированы в запросы так, что хорошо отображаются в тип данных «словарь» Python. Каждый элемент формы имеет имя и соответственное значение.
Например, если элемент — это радиокнопка, то посланное значение — это значение выбранной кнопки. Например, в предыдущей форме радиокнопка имеет имя param2 и ее значение есть или this, или that. Для переключателя, скажем param3 или param4, посланное значение есть off или on.
Теперь, когда вы знаете основы того, как формы кодируются и отправляются к CGI скриптам, настало время представить вам модуль cgi. Модуль cgi — это ваш механизм для получения данных из формы. Он делает процесс кодирования очень легким.
Чтение данных формы слегка осложняется двумя фактами. Для начала, имена входных элементов формы могут повторяться, так что значения могут быть списками. (Представьте себе форму, которая позволяет вам выбрать все правильные ответы.) Во-вторых, по умолчанию входной элемент, который не имеет никакого значения — как например текстовое поле, которое не заполнено — будет скорее отсутствовать, чем быть просто пустым.
Метод FieldStorage() модуля cgi возвращает объект, который представляет данные формы. Он — почти словарь. Вместо того, чтобы повторять страницу руководства об использовании модуля cgi, давайте помотрим на пару функций общего назначения, которые принимают объект, созданный FieldStorage() и возвращают словари.
Функции
def getform(theform, valuelist, notpresent='', nolist=False): """ This function, given a CGI form as a FieldStorage instance, extracts the data from it, based on valuelist passed in. Any non-present values are set to '' - although this can be changed. (e.g. to return None so you can test for missing keywords - where '' is a valid answer but to have the field missing isn't.) It also takes a keyword argument 'nolist'. If this is True list values only return their first value. """ data = {} for field in valuelist: if not theform.has_key(field): # if the field is not present (or was empty) data[field] = notpresent else: # the field is present if type(theform[field]) != type([]): # is it a list or a single item data[field] = theform[field].value else: if not nolist: # do we want a list ? data[field] = theform.getlist(field) else: data[field] = theform.getfirst(field) # just fetch the first item return data def getall(theform, nolist=False): """ Passed a form (cgi.FieldStorage instance) return *all* the values. This doesn't take into account multipart form data (file uploads). It also takes a keyword argument 'nolist'. If this is True list values only return their first value. """ data = {} for field in theform.keys(): # we can't just iterate over it, but must use the keys() method if type(theform[field]) == type([]): if not nolist: data[field] = theform.getlist(field) else: data[field] = theform.getfirst(field) else: data[field] = theform[field].value return data def isblank(indict): """ Passed an indict of values it checks if any of the values are set. Returns True if the indict is empty, else returns False. I use it on the a form processed with getform to tell if my CGI has been activated without any form values. """ for key in indict.keys(): if indict[key]: return False return True
Почти для всех CGI скриптов, которые получают входные данные от формы, вы будете знать, какие параметры ожидать. (В конце концов, вероятно вы сами и написали форму.) Если вы передаете функции getform() экзампляр FieldStorage() и список всех параметров, которые вы ожидаете получить, она возвращает словарь значений. Любые отсутствующие параметры имеют значение по умолчанию », если только вы не изменяли ключевое слово notpresent. Если вы хотите убедиться, что вы не получаете никаких списковых значений, установите ключевое слово nolist. Если переменная формы была списком, nolist возвращает только первое значение в списке.
Или, если вы хотите получить все значения, посланные формой, используйте функцию getall(). Она также принимает ключевое слово nolist как необязательный аргумент.
isblank() — это специальная функция: она проводит быструю проверку, чтобы определить все ли значения содержатся в словаре, возвращенном getall(), или getform() пуст. Если это так, CGI скрипт был вызван без параметров. В таком случае обычно генерируется страница приветствия и форма. Если словарь не пуст (isblank() возвращает False), форма содержит данные для обработки.
Использование getform()
Давайте обработаем данные из примера формы, описанной выше. Этой части программы нужны предыдущие функции и первые несколько строк программы Hello World.
import cgi
mainpage = '''<html><head><title>Receiving a Form</title></head><body>%s</body></html>''' error = ''' <h1>Error</h1> <h2>No Form Submission Was Received</h2> ''' result = ''' <h1>Receiving a Form Submission</h1> We received the following parameters from the form : <ul> <li>Your name is "%s".</li> <li>You selected "%s".</li> <li>"this" is "%s". </li> <li>"this too" is "%s". </li> <li> A hidden parameter was sent "%s".</li> </ul> ''' possible_parameters = ['param1', 'param2', 'param3', 'param4', 'hidden_param'] if __name__ == '__main__': cgiprint(contentheader) # content header cgiprint() # finish headers with blank line theform = cgi.FieldStorage() formdict = getform(theform, possible_parameters) if isblank(formdict): body = error else: name = formdict['param1'] radio = formdict['param2'] # should be 'this' or 'that' check1 = formdict['param3'] # 'on' or 'off' check2 = formdict['param4'] hidden = formdict['hidden_param'] body = result % (name, radio, check1, check2, hidden) print mainpage % body
Давайте обсудим код. Есть три главных отрезка html: mainpage — это рамка страницы, которой только нужно тело для вставки внутрь нее. error показывается, если сценарий выл вызван без параметров. Однако, если сценарий вызван путем отправки формы, то параметры извлекаются и помещаются в result.
Сценарий печатает обязательные заголовки, а затем создает экземпляр FieldStorage, чтобы представить данные формы. Затем theform передается функции getform() наряду со списком ожидаемых параметров.
Если форма не была послана, то все значения в словаре, возвращенном getform() пусты (то есть »). В данном случае isblank() возвращает True и body установлена, чтобы быть сообщением об ошибке.
Если форма была послана, то isblank() возваращает False и значения из словаря извлекаются и вставляются в result. Переменная name содержит имя введенное в текстовое поле. Значение радиокнопки (в radio) или this, или that, в зависимости от того, какае значение было выбрано. check1 и check2 установлены в off или on в зависимости от того, были ли выбраны соответствующие переключатели. Скрытый параметр возвращается всегда.
Наконец страница напечатана, показывая или ошибку, или результаты. Легко или нет? Использование скрытых значений открывает возможность создания уникальных значений и кодирования их в форму. Это могло бы связать запросы вместе, так что вы можете динамически скроить содержимое для каждого пользователя, так как они проходят через ваше приложение (но это другая история).
Использование getall()
Если бы приложение было больше, возможно с несколькими формами, вы могли бы не знать заранее какие параметры будут введены. В таком случае вы можете использовать getall() вместо getform(). Тогда вы можете проверить существование определенных параметров и выполнить различные действия в зависимости от того, какая форма была отправлена:
formdict = getall(theform) if formdict.has_key('rating'): process_feedback(formdict) # пользователь вводит обратную связь elif formdict.has_key('email'): subscribe(formdict) # пользователь подписывается на рассылку else: optionlist() # показать форму со всеми вариантами
Используя getall(), фактически вы можете переделать наш последний сценарий во что-нибудь немного более общее и полезное:
import cgi
mainpage = '''<html><head><title>Receiving a Form</title></head><body>%s</body></html>''' result = ''' <h1>Receiving a Form Submission</h1> We received the following parameters from the form : <ul>%s</ul> ''' li = " <li>%s = %s</li> " if __name__ == '__main__': cgiprint(contentheader) # заголовок содержимого cgiprint() # завершить заголовки пустой линией theform = cgi.FieldStorage() formdict = getall(theform) params = [] for entry in formdict: params.append(li % (entry, str(formdict[entry]))) print mainpage % (result % ''.join(params))
Этот код получает все введенные параметры используя getall(). Затем он вставляет их в страницу как неопределенный список. Если вы посылаете этому сценарию форму, он показывает вам все полученные параметры на странице, где каждая строка выглядит как «параметр = значение». Поскольку строка кода, которая производит это, использует str() функция для каждого значения, она может справиться со списками.
Список значений
Различные параметры формы могут иметь одинаковые имена. В данном случае, значение, возвращенное в FieldStorage — это список. Вы могли бы использовать это для сбора информации от вашего пользователя. Например, список тем для информационных бюллетеней, которых вы, возможно, рассылаете.
<form action="/cgi-bin/formprocessor.py" method="get"> What is Your Name : <input name="name" type="text" value="Joe Bloggs" /> Email Address : <input name="email" type="text" /> <input name="interests" type="checkbox" value="computers" />Computers <input name="interests" type="checkbox" value="sewing" />Sewing <input name="interests" type="checkbox" value="ballet" />Ballet <input name="interests" type="checkbox" value="scuba" />Scuba Diving <input name="interests" type="checkbox" value="cars" />Cars <input type="reset" /> <input type="submit" /> </form>
Когда эта форма отсылается, она будет содержать именя пользователя, его адрес электронной почты и список всех интересов, которые он отметил. Код получения значений из экземпляра FieldStorage:
import cgi
theform = cgi.FieldStorage()
interests = theform[‘interests’].value
Трудность в том, что если пользователь отмечает только один интерес, тогда интересы — это единое значение вместо списка, который мы ожидаем. Альтернатива — использовать высокоуровневые методы, доступные в FieldStorage.
Метод Getlist() всегда возвращает список, даже если было введено только одно значение. Если выбор вообще не был сделан, он возвращает пустой список.
import cgi
theform = cgi.FieldStorage()
interests = theform.getlist(‘interests’)
Было бы очень легко приспособить функции getform() и getall() к вашим специфическим потребностям при работе со значениями, которые будут списками.
Экспериментируйте
Вам не нужен выделенный сервер, чтобы отлаживать CGI скрипты. Вы можете кодировать и отлаживать веб приложения на вашей настольной машине, и это является хорошей новостью для тех, кто все еще платит за поминутный доступ в Интернета. Использую локальный веб-сервер на вашей собственной машине, вы можете выполнять цикл «кодировать, тестировать, рвать волосы, отлаживать» дома. Попробуйте Xitami_. Это — быстрый и легкий веб-сервер, особенно для платформы Windows.
Нужно проявить внимание при настройке CGI на сервере. Это не трудно, но есть несколько шагов, которые должны быть сделаны.
Если сценарий перемещают на другой сервер, вам вероятно придется поместить его на сервер с помощью FTP. Ваш FTP-клиент должен быть настроен так, чтобы пересылать сценарии Python как текст. Скопируйте в нужный каталог, установите корректные права доступа. Проверьте путь к интрепретатору.
Веб-страница с примерами CGI http://www.voidspace.org.uk/python/cgi.shtml. Скрипты доступны для проверки или загрузке. Они включают генератор анаграмм и различные более маленькие тестовые сценарии. Есть также полный набор инструметов для создания аутентификации пользователей и управления с помощью CGI logintools.
Приичины ошибки 500
Отладка CGI скриптов может быть тяжелым процессом. По умолчанию какая-нибудь проблема с вашим сценарием CGI приводит к анонимной ошибке 500. Фактические детали ошибки вписаны в лог файл веб-сервера, который может быть полезен, если вы имеете доступ к этому файлу.
Однако, более чем половина 500 ошибок может быть легко устранена путем проверки следующих общих источников ошибок. Вы будете удивлены как часто вы будуете допускать некоторые из них.
Был ли ваш сценарий помещен на сервер в ‘текстовом’ режиме? (Настроен ли ваш FTP-клиентдля пересылки файлов .py в текстовом режиме?)
Установили ли вы скрипту корректные права доступа 755? (выполнение для всех)?
Установили ли вы правильный путь к интерпретатору в первой строке?
Напечатали ли вы корресктные заголовки, в том числе заключительную пустую строку?
Кроме того, некоторые серверы требуют, чтобы скрипт находился в каталоге cgi-bin (или его подкаталоге), и некоторые даже требуют, чтобы расширение файла было .cgi вместо .py.
Заключение
Мы обсудили все основы CGI. Информация здесь достаточна для начала работы, или хотя бы как минимум для поиска нужных материалов для самообучения.
Однако осталось еще много тем: кодировки символов, использование шаблонов для вывода подобного кода HTML, получение информации о HTTP-запросе пользователя и т.д.
Проще всего создать динамические страницы на Python при помощи CGI-скриптов. CGI-скрипты — это исполняемые файлы, которые выполняются веб-сервером, когда в URL запрашивается соответствующий скрипт.
Сегодня я расскажу про то, как написать Hello world, как CGI-скрипт.
Настройка локального сервера
В Python уже есть встроенный CGI сервер, поэтому его настройка элементарна.
Для запуска из консоли (для любителей linux-систем). Запускать нужно из той папки, где мы хотим работать:
python3 -m http.server --cgi
Для сидящих на Windows чуть проще будет запуск Python файла (заметьте, что он должен находиться в той же папке, в которой мы планируем работать!):
from http.server import HTTPServer, CGIHTTPRequestHandler server_address = ("", 8000) httpd = HTTPServer(server_address, CGIHTTPRequestHandler) httpd.serve_forever()
Теперь откройте браузер и в адресной строке наберите localhost:8000
Если у вас примерно такая же картина, значит, у вас все заработало!
Теперь в той папке, где мы запустили сервер, создаём папку cgi-bin (у меня она уже создана).
В этой папке создаём скрипт hello.py со следующим содержимым:
#!/usr/bin/env python3 print("Content-type: text/html") print() print("<h1>Hello world!</h1>")
Первая строка говорит о том, что это Python скрипт (CGI-скрипты можно не только на Python писать).
Вторая строка печатает заголовок. Он обозначает, что это будет html файл (бывает ещё css, javascript, pdf и куча других, и браузер различает их по заголовкам).
Третья строка (просто символ новой строки) отделяет заголовки от тела ответа.
Четвёртая печатает Hello world.
Теперь переходим на localhost:8000/cgi-bin/hello.py
И радуемся!
Если у вас не работает, проверьте, установлены ли права на выполнение.
Также в консоли запущенного сервера появляются сообщения об ошибках. Например, убрал скобочку и обновил страницу:
В следующей части мы рассмотрим обработку данных форм и cookies.
Для вставки кода на Python в комментарий заключайте его в теги <pre><code class=»python3″>Ваш код</code></pre>