Веб-разработчики обнаружили, что Python хорошо подходит для написания веб-серверов и программ, работающих на серверной стороне. Это привело к появлению такого множества фреймворков, написанных на этом языке, что теперь уже становится трудно исследовать их все и сделать выбор, не говоря уже о том, чтобы решить, о каких из них поговорить в книге.
Веб-фреймворк предоставляет функции, с помощью которых вы можете построить сайты, поэтому он может решать большее количество задач, чем простой веб-сервер (HTTP). Вы встретитесь с функциями маршрутизации (URL к функции сервера), шаблонами (HTM с динамическими включениями), отладкой и др.
Я не буду говорить в этой книге обо всех фреймворках — рассмотрю лишь те, которые относительно просты в использовании и подходят для создания настоящих сайтов. Я также покажу вам, как запускать динамические части сайта с помощью Python и других составляющих на традиционном веб-сервере.
Простейший веб-сервер Python
Вы можете запустить простейший веб-сервер, просто введя одну строку кода Python:
$ python — m http.server
С помощью этой строки вы реализуете примитивный Python HTTP server. Если никаких проблем не возникло, вы увидите исходное сообщение о статусе:
Serving HTTP on 0.0.0.0 port 8000…
Запись 0.0.0.0 означает любой адрес TCP, поэтому веб-клиенты могут получать к нему доступ независимо от того, какой адрес имеет сервер. В главе 11 вы можете прочитать о некоторых низкоуровневых деталях TCP и других системах соединения в сеть.
Теперь вы можете запрашивать файлы, чьи пути относительны к вашему текущему каталогу, и они будут вам возвращены. Если вы введете в своем браузере строку http://localhost:8000, то должны увидеть список каталогов, и сервер выведет на экран строки обращения к журналам наподобие следующих:
127.0.0.1 — [20/Feb/2013 22:02:37] "GET / HTTP/1.1" 200 -
localhost и 127.0.0.1 являются для TCP синонимами вашего локального компьютера, поэтому они сработают независимо от того, подключены ли вы к Интернету. Вы можете интерпретировать эти строки следующим образом.
• 127.0.0.1 — это IP-адрес клиента.
• Первый символ — это имя удаленного пользователя, если он присутствует.
• Второй символ — это имя авторизующегося пользователя, если требуется.
• [20/Feb/2013 22:02:37] — это дата и время доступа.
• "GET / HTTP/1.1" — это команда, отправленная веб-серверу:
• метод HTTP (GET);
• запрошенный ресурс (/, верхний уровень);
• версия HTTP (HTTP/1.1).
• Последнее число (200) — это код статуса HTTP, возвращенный веб-сервером.
Щелкните на любом файле. Если ваш браузер может распознать его формат (HTML, PNG, GIF, JPEG и т. д.), он должен отобразить его, и сервер занесет этот запрос в журнал. Например, если в вашем текущем каталоге имеется файл oreilly.png, запрос http://localhost:8000/oreilly.png должен вернуть изображение встревоженной зверушки, показанное на рис. 7.1, а в журнале должна появиться похожая запись:
127.0.0.1 — [20/Feb/2013 22:03:48] "GET /oreilly.png HTTP/1.1" 200 -
Если у вас в этой папке находятся и другие файлы, их названия должны появиться в списке. Можете щелкнуть на одном из файлов, чтобы загрузить его. Если ваш браузер сконфигурирован так, чтобы отображать формат этого файла, вы увидите результат на экране, в противном случае браузер спросит, хотите ли вы загрузить и сохранить файл.
По умолчанию используется порт 8000, но вы можете указать любой другой:
$ python — m http.server 9999
Вы должны увидеть следующее:
Serving HTTP on 0.0.0.0 port 9999…
Этот сервер, написанный только на Python, лучше всего подходит для быстрых тестов. Мы можете выключить его, остановив его процесс с помощью комбинации клавиш Ctrl+C.
Вы не должны использовать этот простой сервер для загруженного производственного сайта. Традиционные веб-серверы вроде Apache и Nginx гораздо быстрее работают со статическими файлами. Кроме того, этот простой сервер не может работать с динамическим содержимым, на что оказываются способны более продвинутые серверы, принимая дополнительные параметры.
Web Server Gateway Interface
Довольно быстро необходимость в простых файлах исчезает, и нам уже нужен сервер, который может запускать программы динамически. В первые годы существования Всемирной паутины общий интерфейс шлюза (Common Gateway Interface, CGI) был разработан для того, чтобы веб-серверы могли запускать внешние программы и возвращать результаты. CGI также обрабатывал получение входных аргументов от клиента, передавая их через сервер сторонним программам. Однако программы запускались заново при каждом обращении клиента. Масштабировать такие системы было трудно, поскольку даже у небольших программ время загрузки довольно велико.
Для того чтобы избежать задержки запуска, люди начали встраивать интерпретатор языка в веб-сервер. Apache запускал код на PHP внутри своего модуля mod_php, Perl — внутри модуля mod_perl и Python — внутри модуля mod_python. Далее код этих динамических языков мог быть выполнен внутри долгоиграющего процесса Apache, а не во внешних программах.
Альтернативный метод заключается в том, чтобы запускать динамический язык внутри отдельной долгоиграющей программы и заставить ее обмениваться данными с веб-сервером. Примерами таких программ являются FastCGI и SCGI.
Веб-разработка с использованием Python совершила рывок с появлением Web Server Gateway Interface (WSGI) — универсального API между веб-приложениями и веб-серверами. Все веб-фреймворки и веб-серверы Python, показанные далее, используют WSGI. Обычно вам не нужно знать, как работает WSGI (для этого многого и не потребуется), но осведомленность об основных принципах его функционирования может действительно помочь разработке.
Фреймворки
Веб-серверы обрабатывают детали работы HTTP и WSGI, но вам нужно использовать веб-фреймворки для того, чтобы написать код Python, который будет поддерживать сайт. Поэтому сейчас мы немного поговорим о фреймворках, а затем вернемся к альтернативным способам обслуживания сайтов, которые их используют.
Для того чтобы написать сайт с помощью Python, существует множество веб-фреймворков (некоторые даже могут сказать, что их слишком много). Веб-фреймворк обрабатывает как минимум запросы клиента и ответы сервера. Он может предоставлять следующие возможности:
• маршруты — интерпретирует URL и находит соответствующие файлы на сервере или серверный код Python;
• шаблоны — объединяет серверные данные в страницы HTML;
• аутентификация и авторизация — обрабатывает имена пользователей, пароли, разрешения;
• сессии — обслуживает временное хранилище данных во время посещения сайта пользователем.
В следующих разделах мы напишем пример, использующий два фреймворка (Bottle и Flask). Далее поговорим об альтернативах, в частности о сайтах, работающих с базами данных. Вы можете найти подходящий фреймворк Python для любого сайта, который только можете себе представить.
Bottle
Bottle состоит из одного файла Python, поэтому его довольно легко опробовать и развернуть. Bottle не является частью стандартной библиотеки Python, поэтому установите его с помощью следующей команды:
$ pip install bottle
Рассмотрим код, который запустит тестовый веб-сервер и вернет текстовую строку, когда ваш браузер обратится по URL http://localhost:9999/. Сохраните этот файл как bottle1.py:
from bottle import route, run
@route('/')
def home():
··return "It isn't fancy, but it's my home page"
run(host='localhost', port=9999)
Bottle использует декоратор route, чтобы связать URL со следующей функцией; в этом примере / (домашняя страница) обрабатывается функцией home(). Запустите этот сценарий сервера с помощью следующей команды:
$ python bottle1.py
Когда вы обратитесь по адресу http://localhost:9999, вы должны увидеть следующее:
It isn't fancy, but it's my home page
Функция run() запускает встроенный тестовый веб-сервер Bottle. Вам не нужно использовать его в программах, написанных с помощью Bottle, но это может оказаться полезным на первых этапах разработки и тестирования.
Теперь вместо создания текста домашней страницы в коде создадим отдельный HTML-файл, который называется index.html и содержит такую строку:
My new and improved home page!!!
Укажите Bottle возвращать содержимое этого файла, когда запрашивается домашняя страница. Сохраните этот сценарий как bottle2.py:
from bottle import route, run, static_file
@route('/')
def main():
····return static_file('index.html', root='.')
run(host='localhost', port=9999)
В вызове static_file() мы хотим получить файл index.html из каталога, указанного в root (в нашем случае в '.', текущем каталоге). Если код предыдущего примера все еще выполняется, то остановите его. Теперь запустите новый сервер:
$ python bottle2.py
Каждый раз, когда вы обращаетесь к странице http:/localhost:9999/, вы должны видеть следующее:
My new and improved home page!!!
Добавим последний пример, который демонстрирует, как передавать аргументы в URL и использовать их. Конечно же, этот файл будет называться bottle3.py:
from bottle import route, run, static_file
@route('/')
def home():
····return static_file('index.html', root='.')
@route('/echo/')
def echo(thing):
····return "Say hello to my little friend: %s!" % thing
run(host='localhost', port=9999)
У нас появилась новая функция echo(), в которую мы хотим передавать строковый аргумент через URL. За это отвечает строка @route('/echo/') в предыдущем примере. Конструкция в маршруте означает, что все, что находится в URL после /echo/, присваивается строковому аргументу thing, который передается функции echo. Чтобы увидеть, что случится, остановите старый сервер, если он все еще работает, и запустите его с новым кодом:
$ python bottle3.py
Далее перейдите в браузере по ссылке http://localhost:9999/echo/Mothra. Вы должны увидеть следующее:
Say hello to my little friend: Mothra!
Оставьте bottle3.py работать еще на пару минут, чтобы мы могли попробовать что-нибудь еще. Вы проверяли, что эти примеры работают, вводя URL в браузер и глядя на отображаемые страницы. Вы также можете использовать клиентские библиотеки вроде requests, чтобы они выполняли работу за вас. Сохраните этот код как bottle_test.py:
import requests
resp = requests.get('http://localhost:9999/echo/Mothra')
if resp.status_code == 200 and \
··resp.text == 'Say hello to my little friend: Mothra!':
····print('It worked! That almost never happens!')
else:
····print('Argh, got this:', resp.text)
Отлично! Теперь запустите этот код:
$ python bottle_test.py
В терминале вы должны увидеть следующее:
It worked! That almost never happens!
Перед вами небольшой пример юнит-теста. В главе 12 вы можете получить более подробную информацию о том, почему тесты — это хорошо и как написать их с помощью Python.
У фреймворка Bottle больше возможностей, чем я вам показал. В частности, когда вызываете функцию run(), можете попробовать добавить следующие аргументы:
• debug=True — создает страницу отладки, если вы получаете ошибку HTTP;
• reloader=True — перезагружает страницу в браузере, если вы измените хотя бы небольшой кусочек кода.
Все это хорошо задокументировано на сайте разработчика http://bottlepy.org/docs/dev/.
Flask
Bottle — это хороший фреймворк для того, чтобы начать работу. Но если вам нужно больше возможностей, попробуйте Flask. Он был создан в 2010 году как первоапрельская шутка, но реакция энтузиастов вдохновила его автора, Армина Ронахера (Armin Ronacher), на то, чтобы сделать его настоящим фреймворком. Он назвал результат Flask («склянка»), обыгрывая название Bottle — «бутылка».
Flask в использовании почти так же прост, как и Bottle, но он поддерживает множество расширений, которые могут оказаться полезными в профессиональной веб-разработке, например аутентификацию с помощью Facebook и интеграцию с базами данных. Этот фреймворк мне нравится больше других веб-фреймворков Python, поскольку в нем сбалансированы простота использования и богатый набор функций.
Пакет Flask включает в себя библиотеку package WSGI werkzeug и библиотеку шаблонов jinja2. Вы можете установить его с помощью терминала:
$ pip install flask
Переделаем наш последний пример с использованием фреймворка Flask. Однако для начала нам нужно внести несколько изменений.
Во Flask папка по умолчанию для статических файлов называется static, и URL для таких файлов тоже начинается со /static. Мы изменяем папку на '.' (текущая папка) и префикс URL на ' ' (пустой), чтобы позволить URL / отображать файл index.html.
В функции run() установка параметра debug=True активизирует также автоматическую перезагрузку, тогда как фреймворк Bottle для отладки и перезагрузки использует отдельные аргументы.
Сохраните этот код в файл flask1.py:
from flask import Flask
app = Flask(__name__, static_folder='.', static_url_path='')
@app.route('/')
def home():
····return app.send_static_file('index.html')
@app.route('/echo/')
def echo(thing):
····return "Say hello to my little friend: %s" % thing
app.run(port=9999, debug=True)
Далее запустите сервер из терминала или окна:
$ python flask1.py
Протестируйте домашнюю страницу, введя в браузер следующий URL:
http://localhost:9999/
Вы должны увидеть следующее (как и в случае с Bottle):
My new and improved home page!!!
Попробуйте обратиться к конечной точке /echo:
http://localhost:9999/echo/Godzilla
Вы должны увидеть следующее:
Say hello to my little friend: Godzilla
Есть еще одно преимущество установки параметра debug равным True при вызове метода run. Если в серверном коде генерируется исключение, Flask возвращает особую отформатированную страницу, содержащую полезные сведения о том, что и где пошло не так. Даже больше: вы можете вводить команды, чтобы увидеть значения переменных в программе сервера.
Не устанавливайте параметр debug = True на производственных веб-серверах. Это предоставит потенциальным злоумышленникам слишком много информации о вашем сервере.
До сих пор примеры с использованием Flask повторяли то, что мы делали с помощью фреймворка Bottle. Что такого может делать Flask, чего не может делать Bottle? Flask содержит jinja2 — более широкую систему шаблонов. Рассмотрим небольшой пример одновременного использования jinja2 и flask.
Создайте папку templates и файл flask2.html внутри нее:
Flask2 Example
Say hello to my little friend: {{ thing }}
Далее мы напишем серверный код, который получает этот шаблон, заполняет значение аргумента thing, который мы передаем, и отрисовывает его как HTML (я опущу функцию home() для экономии места). Сохраните этот файл под именем flask2.py:
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/echo/')
def echo(thing):
····return render_template('flask2.html', thing=thing)
app.run(port=9999, debug=True)
Аргумент thing = thing означает, что для передачи переменной с именем thing в шаблон эта переменная содержит значение строки thing.
Убедитесь, что файл flask1.py перестал работать, и запустите файл flask2.py:
$ python flask2.py
Теперь введите этот URL:
http://localhost:9999/echo/Gamera
Вы должны увидеть следующее:
Say hello to my little friend: Gamera
Модифицируем наш пример и сохраним его в папке templates под именем flask3.html:
Flask3 Example
Say hello to my little friend: {{ thing }}.
Alas, it just destroyed {{ place }}!
Второй аргумент в URL, echo, вы можете передать множеством способов.
Передача аргумента как части пути URLС помощью этого метода вы просто расширяете URL (сохраните этот файл как flask3a.py):
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/echo//')
def echo(thing, place):
····return render_template('flask3.html', thing=thing, place=place)
app.run(port=9999, debug=True)
Как обычно, остановите предыдущий сценарий тестового сервера, если он еще работает, и затем запустите новый:
$ python flask3a.py
URL должен выглядеть так:
http://localhost:9999/echo/Rodan/McKeesport
Вы должны увидеть следующее:
Say hello to my little friend: Rodan. Alas, it just destroyed McKeesport!
Или же вы можете передать аргументы как параметры команды GET (сохраните файл как flask3b.py):
from flask import Flask, render_template, request
app = Flask(__name__)
@app.route('/echo/')
def echo():
····thing = request.args.get('thing')
····place = request.args.get('place')
····return render_template('flask3.html', thing=thing, place=place)
app.run(port=9999, debug=True)
Запустите новый сценарий сервера:
$ python flask3b.py
В этот раз используйте следующий URL:
http://localhost:9999/echo?thing=Gorgo&place=Wilmerding
Вы должны увидеть следующее:
Say hello to my little friend: Gorgo. Alas, it just destroyed Wilmerding!
Когда команда GET используется в URL, любые аргументы должны передаваться в формате &key1=val1&key2=val2&…
Вы также можете использовать оператор словаря **, чтобы передать несколько аргументов в шаблон с помощью одного словаря (назовите файл flask3c.py):
from flask import Flask, render_template, request
app = Flask(__name__)
@app.route('/echo/')
def echo():
····kwargs = {}
····kwargs['thing'] = request.args.get('thing')
····kwargs['place'] = request.args.get('place')
····return render_template('flask3.html', **kwargs)
app.run(port=9999, debug=True)
**kwargs действует как конструкция thing=thing, place=place. Используя этот словарь, можно сэкономить немного времени, если входных аргументов много.
Язык шаблонов jinja2 способен на гораздо большее. Если вы работали на PHP, то увидите много похожих возможностей.
Веб-серверы, не использующие Python
До этого момента мы использовали простые веб-серверы: http.server из стандартной библиотеки или сервера отладки Bottle и Flask. На производстве вам нужно запускать код на более быстрых серверах. Как правило, вы выбираете один из следующих вариантов:
• Apache с модулем mod_wsgi;
• Nginx с сервером приложений uWSGI.
Оба они работают хорошо: Apache, скорее всего, более популярен, а у Nginx имеется репутация стабильного и тратящего меньше памяти сервера.
ApacheЛучшим WSGI-модулем Apache (http://httpd.apache.org/) является mod_wsgi (https://code.google.com/p/modwsgi/). Он может запускать код, написанный на Python, внутри процесса Apache или в отдельном процессе, который обменивается данными с Apache.
Если вы используете Linux или OS X, в вашей системе Apache уже установлен. Для Windows вам придется устанавливать Apache самостоятельно (http://bit.ly/apache-http).
Наконец, установите предпочитаемый веб-фреймворк Python, основанный на WSGI. Попробуем использовать в наших примерах фреймворк Bottle. Практически вся работа включает в себя конфигурирование Apache, что может оказаться довольно затруднительным.
Создайте тестовый файл и сохраните его как /var/www/test/home.wsgi:
import bottle
application = bottle.default_app()
@bottle.route('/')
def home():
····return "apache and wsgi, sitting in a tree"
В этот раз не вызывайте функцию run(), поскольку это запустит встроенный веб-сервер Python. Нам нужно присвоить некоторое значение переменной application, поскольку именно его будет проверять mod_wsgi при объединении веб-сервера и кода Python.
Если Apache и его модуль mod_wsgi работают корректно, нужно лишь соединить их с нашим сценарием Python. Нам нужно добавить в файл одну строку, которая определяет сайт по умолчанию для этого сервера Apache, но поиск этого файла сам по себе является задачей. Он может называться /etc/apache2/httpd.conf, или /etc/apache2/sites-available/default, или даже быть латинским названием чьей-то ручной саламандры.
Предположим, что вы понимаете работу Apache и нашли нужный файл. Добавьте эту строку в раздел , который управляет стандартным сайтом:
····WSGIScriptAlias / /var/www/test/home.wsgi
Этот раздел должен выглядеть так:
····DocumentRoot /var/www
····WSGIScriptAlias / /var/www/test/home.wsgi
····
····Order allow,deny
····Allow from all
····
Запустите Apache или перезапустите его, если работал, чтобы указать ему, что следует использовать новую конфигурацию. Если вы перейдете в браузере по адресу http://localhost/, то должны увидеть эту строку:
apache and wsgi, sitting in a tree
Это запустит mod_wsgi во встроенном режиме как часть самого Apache.
Вы также можете запустить его в режиме демона — как один или несколько процессов, отдельных от Apache. Для того чтобы это сделать, добавьте две новые строки директив в ваш файл конфигурации Apache:
$ WSGIDaemonProcess domain-name user=user-name group=group-name threads=25
WSGIProcessGroup domain-name
В предыдущем примере переменные user-name и group-name представляют собой имена пользователя и группы в операционной системе, а переменная domain-name — имя вашего интернет-домена. Минимальная конфигурация Apache может выглядеть так:
····DocumentRoot /var/www
····WSGIScriptAlias / /var/www/test/home.wsgi
····WSGIDaemonProcess mydomain.com user=myuser group=mygroup threads=25
····WSGIProcessGroup mydomain.com
····
····Order allow,deny
····Allow from all
····
Веб-сервер NginxВеб-сервер Nginx не имеет встроенного модуля Python. Вместо этого он обменивается данными с помощью отдельного сервера WSGI вроде uWSGI. Вместе они представляют собой очень быструю и удобную в конфигурации платформу для веб-разработки с помощью Python.
Вы можете установить Nginx с его официального сайта http://wiki.nginx.org/Install. Вам также нужно установить uWSGI (http://bit.ly/uWSGI). uWSGI — это крупная система, имеющая множество различных настроек. Небольшая страница документации предоставляет вам инструкции, позволяющие объединить Flask, Nginx и uWSGI.
Другие фреймворки
Сайты и базы данных похожи на арахисовое масло и желе — часто можно увидеть, как они работают вместе. Небольшие фреймворки вроде Bottle и Flask не включают в себя функции поддержки баз данных, хотя некоторые надстройки их имеют.
Если вам нужно поставить на поток производство сайтов, работающих с базой данных, а сама база меняется не очень часто, можете попробовать воспользоваться одним из более крупных фреймворков. Рассмотрим самые известные из них.
• django (https://www.djangoproject.com/). Этот фреймворк самый популярный, особенно для крупных сайтов. Его стоит изучить по многим причинам, среди которых регулярно появляющиеся требования опыта работы с django в объявлениях о вакансиях. Он содержит код ORM (об ORM мы говорили в пункте··«The Object-Relational Mapper» подраздела «SQLAlchemy» раздела «Реляционные базы данных» главы 8), позволяющий создавать автоматические веб-страницы для типичных функций баз данных CRUD (создание, замена, обновление, удаление), которые я рассматривал в подразделе «SQL» раздела «Реляционные базы данных» главы 8. Вам не обязательно использовать ORM именно для django, если больше нравится применять что-то другое, например SQLAlchemy или прямые запросы SQL.
• web2py (http://www.web2py.com/). Он работает примерно с тем же, с чем и django, только немного в другом стиле.
• pyramid (http://www.pylonsproject.org/). Этот фреймворк появился из более раннего проекта pylons, с точки зрения функционала он похож на django.
• turbogears (http://turbogears.org/). Этот фреймворк поддерживает ORM, множество баз данных и несколько языков шаблонов.
• wheezy.web (http://pythonhosted.org/wheezy.web/). Этот более молодой фреймворк оптимизирован для повышения производительности. Недавние исследования показали, что он работает быстрее других.
Вы можете сравнить фреймворки с помощью онлайн-таблицы по адресу http://bit.ly/web-frames.
Если вы хотите создать сайт, работающий с реляционной базой данных, вам не обязательно пользоваться одним из этих крупных фреймворков. Можете воспользоваться Bottle, Flask или каким-нибудь другим простым фреймворком и модулями работы с базами данных вроде SQLAlchemy, чтобы сгладить разногласия. Далее вам нужно будет написать обычный код SQL вместо специфического кода ORM, так как большинство разработчиков знают SQL, а не некий определенный синтаксис ORM.
Кроме того, ничто не заставляет вас выбирать именно реляционную базу данных. Если схема ваших данных может значительно изменяться — графы явно различаются в разных рядах, — вам стоит выбрать базу данных, не имеющую схемы, вроде баз данных NoSQL, которые мы рассматривали в разделе «Хранилища данных NoSQL» главы 8. Однажды я работал над сайтом, который изначально хранил данные в базе данных NoSQL, затем переключался на одну реляционную базу данных, затем на другую реляционную базу данных, затем на другую базу данных NoSQL и, наконец, возвращался к одной из реляционных.
Другие веб-серверы PythonДалее перечислены некоторые независимые WSGI-серверы, написанные на Python, которые работают как Apache или Nginx и используют несколько процессов и/или потоков (смотрите раздел «Конкуренция» главы 11) для обработки одновременных запросов:
• uwsgi (http://projects.unbit.it/uwsgi/);
• cherrypy (http://www.cherrypy.org/);
• pylons (http://www.pylonsproject.org/).
Далее перед вами серверы, основанные на событиях, которые пользуются одним процессом, но избегают блокирования любым одиночным запросом:
• tornado (http://www.tornadoweb.org/);
• gevent (http://gevent.org/);
• gunicorn (http://gunicorn.org/).
О событиях я поговорю подробнее в разделе о конкуренции в главе 11.
Веб-сервисы и автоматизация