Под Django , Flask , Bottle и любой другой веб-инфраструктурой Python находится интерфейс шлюза веб-сервера или, для краткости, WSGI. WSGI для Python — это то же, что сервлеты для Java — общая спецификация для веб-серверов, которая позволяет различным веб-серверам и средам приложений взаимодействовать на основе общего API. Однако, как и в большинстве случаев, версия Python значительно проще.
WSGI определен в PEP 3333 , который я рекомендую вам прочитать в качестве справочного материала, если вы хотите получить больше информации после этого краткого вступления.
Эта статья познакомит вас со спецификацией WSGI с точки зрения разработчика приложений и покажет, как работать напрямую с WSGI для создания приложений (если вы того пожелаете).
Ваше первое приложение WSGI
Вот самое простое веб-приложение на Python:
def app(environ, start_fn): start_fn('200 OK', [('Content-Type', 'text/plain')]) return ["Hello World!\n"]
Это оно! Весь файл. Назовите его app.py и запустите его на любом WSGI-совместимом сервере, и вы получите ответ Hello World со статусом 200. Вы можете использовать Gunicorn для этого; просто установите его через pip ( pip install gunicorn
) и запустите его с gunicorn app:app
. Эта команда указывает gunicorn получить вызываемый WSGI из переменной приложения в модуле приложения.
Прямо сейчас вы должны быть очень взволнованы. Всего 3 строки для работающего приложения? Это должна быть какая-то запись (за исключением PHP, потому что mod_php обманывает). Бьюсь об заклад, вы просто рвется узнать больше
Итак, каковы основные части приложения WSGI?
- Приложение WSGI может вызываться на Python, например, функцией, классом или экземпляром класса с помощью метода
__call__
- Вызываемое приложение должно принимать два аргумента:
environ
, которое является Python-диктом, содержащим данные запроса, иstart_fn
, само вызываемое. - Приложение должно вызвать
start_fn
с двумя аргументами: кодом состояния (в виде строки) и списком заголовков, выраженным в виде двух кортежей. - Приложение возвращает итеративное содержимое, содержащее байты в теле ответа в виде удобных, потоковых фрагментов — в данном случае это список строк, содержащих просто
"Hello, World!"
, (Еслиapp
является классом, это можно сделать с__iter__
метода__iter__
.)
В качестве примера, следующие два примера эквивалентны первому:
class app(object): def __init__(self, environ, start_fn): self.environ = environ self.start_fn = start_fn def __iter__(self): self.start_fn('200 OK', [('Content-Type', 'text/plain')]) yield "Hello World!\n"
class Application(object): def __call__(self, environ, start_fn): start_fn('200 OK', [('Content-Type', 'text/plain')]) yield "Hello World!\n" app = Application()
Возможно, вы уже думаете о способах использования этой информации, но, возможно, наиболее актуальным является написание промежуточного программного обеспечения.
Jazzing It Up
Промежуточное ПО — это простой способ расширить функциональность приложений WSGI. Поскольку вам нужно только предоставить вызываемый объект, вы можете заключить его в другие функции, как вам угодно.
Например, скажем, мы хотим изучить содержимое environ
. Мы можем легко создать промежуточное программное обеспечение для этого, как в этом примере:
import pprint def handler(environ, start_fn): start_fn('200 OK', [('Content-Type', 'text/plain')]) return ["Hello World!\n"] def log_environ(handler): def _inner(environ, start_fn): pprint.pprint(environ) return handler(environ, start_fn) return _inner app = log_environ(handler)
Здесь log_environ
— это функция, которая возвращает функцию, которая красиво печатает аргумент environ
прежде чем отложить на исходный обратный вызов.
Преимущество написания промежуточного программного обеспечения таким образом заключается в том, что промежуточное программное обеспечение и обработчик не должны знать или заботиться друг о друге. Например, вы можете легко log_environ
к приложению Flask , поскольку приложения Flask являются приложениями WSGI.
Несколько других полезных идей промежуточного программного обеспечения:
import pprint def handle_error(handler): def _inner(environ, start_fn): try: return handler(environ, start_fn) except Exception as e: print e # Log error start_fn('500 Server Error', [('Content-Type', 'text/plain')]) return ['500 Server Error'] return _inner def wrap_query_params(handler): def _inner(environ, start_fn): qs = environ.get('QUERY_STRING') environ['QUERY_PARAMS'] = urlparse.parse_qs(qs) return handler(environ, start_fn) return _inner
Вы можете использовать reduce
чтобы применить связку промежуточного программного обеспечения сразу, если вы не хотите сделать большую пирамиду в нижней части вашего файла:
# Applied from bottom to top on the way in, then top to bottom on the way out MIDDLEWARES = [wrap_query_params, log_environ, handle_error] app = reduce(lambda h, m: m(h), MIDDLEWARES, handler)
Вы также можете написать промежуточное программное обеспечение, которое изменяет ответ, используя аргумент start_fn
. Вот промежуточное программное обеспечение, которое обращает вывод, если заголовок Content-Type
является text/plain
:
def reverser(handler): # A reverse function rev = lambda it: it[::-1] def _inner(environ, start_fn): do_reverse = [] # Must be a reference type such as a list # Override start_fn to check the content type and set a flag def start_reverser(status, headers): for name, value in headers: if (name.lower() == 'content-type' and value.lower() == 'text/plain'): do_reverse.append(True) break # Remember to call `start_fn` start_fn(status, headers) response = handler(environ, start_reverser) try: if do_reverse: return list(rev(map(rev, response))) return response finally: if hasattr(response, 'close'): response.close() return _inner
Это немного start_fn
благодаря разделению start_fn
и ответа, но все еще отлично работоспособно.
Также обратите внимание, что, чтобы строго соответствовать спецификации WSGI, мы должны проверить наличие в ответе метода close
и вызвать его, если он есть. Устаревшие приложения WSGI также могут возвращать функцию записи
вместо итерируемой при вызове handler
; если вы хотите, чтобы ваше промежуточное ПО поддерживало более старые приложения, вам, возможно, придется разобраться с этим делом.
Как только вы немного поиграете с сырым WSGI, вы начнете понимать, почему в Python буквально десятки веб-фреймворков. WSGI упрощает создание чего-либо, начиная с нуля. Например, вы можете рассмотреть проблему маршрутизации:
routes = { '/': home_handler, '/about': about_handler, } class Application(object): def __init__(self, routes): self.routes = routes def not_found(self, environ, start_fn): start_fn('404 Not Found', [('Content-Type', 'text/plain')]) return ['404 Not Found'] def __call__(self, environ, start_fn): handler = self.routes.get(environ.get('PATH_INFO')) or self.not_found return handler(environ, start_fn)
Работать с WSGI напрямую можно, если вам нравится гибкость сборки библиотек поверх
- Библиотеки шаблонов: просто вставьте любую библиотеку шаблонов, которая вам нравится (например, Jinja2 , Pystashe ) и верните обработанный шаблон из вашего обработчика!
- Помогите вашей маршрутизации с такой библиотекой, как Routes или, возможно, Werkzeug . На самом деле, посмотрите на Werkzeug, если вы хотите использовать очень небольшую абстракцию над WSGI.
- Используйте любые базы данных / библиотеки миграции, как если бы вы использовали Flask или аналогичные.
Конечно, для неспециализированных приложений вы, вероятно, по-прежнему захотите использовать каркас только для правильной обработки крайних случаев и так далее.
Но как насчет серверов?
Существует множество способов обслуживания приложений WSGI. Мы уже говорили о Gunicorn , который является достойным вариантом. uWSGI — еще один отличный вариант. Просто убедитесь, что вы настроили что-то вроде nginx перед ними для обслуживания статических ресурсов, и у вас должна быть надежная отправная точка.
И это все, что нужно сделать!