Что такое WSGI
Расскажем, что такое WSGI и почему этот стандарт до сих пор остается важной частью Python-экосистемы.

Что такое WSGI
WSGI (Web Server Gateway Interface) — это стандарт взаимодействия между веб-сервером и Python-приложением. Он описывает, как сервер должен передавать HTTP-запросы в приложение, а приложение — возвращать ответы обратно. Благодаря WSGI сервер и приложение остаются независимыми: достаточно одного универсального интерфейса, чтобы они «поняли» друг друга.
В рамках этого стандарта Python-приложение должно быть вызываемым объектом (callable), чаще всего — функцией, которая принимает два аргумента: environ (словарь с данными запроса) и start_response (функция для начала формирования ответа). Когда сервер получает HTTP-запрос, он создает нужные объекты и передает их приложению.
История появления: от CGI к WSGI
До появления WSGI основным способом интеграции веб-приложений с сервером был CGI (Common Gateway Interface) — устаревшее решение, при котором для каждого HTTP-запроса создавался новый процесс: например, запускался Python-интерпретатор. Такой подход был крайне неэффективен: он тратил много ресурсов, плохо масштабировался и не позволял реализовывать сложные сценарии обработки запросов.
В начале 2000-х, с ростом популярности Python-фреймворков, потребовался унифицированный способ подключения приложений к серверам без накладных расходов.
Так в 2003 году появился WSGI. Сначала как PEP 333 – официальный документ, описывающий стандарт взаимодействия между сервером и приложением в веб-среде. Позднее, в 2010 году, для совместимости с Python 3, стандарт был обновлен в PEP 3333.
Преимущества и назначение WSGI
Благодаря WSGI разработчики получили единый понятный способ интеграции приложений с серверами, независимо от используемых библиотек. У него есть несколько ключевых достоинств.
- Совместимость. Любое WSGI-приложение может запускаться на любом WSGI-совместимом сервере (uWSGI, Gunicorn и другие).
- Масштабируемость. Параллельная обработка запросов и балансировка нагрузки.
- Гибкость. Возможность добавления middleware (аутентификация, логирование и так далее).
- Стандартизация. Единый протокол по PEP 3333, снижающий риски несовместимости.
- Простота. Минимальное WSGI-приложение можно написать в несколько строк кода.
- Расширяемость. Поддержка разнообразных фреймворков (Django, Flask и другие) и возможности встраивания в крупные архитектуры.
Благодаря этим преимуществам WSGI с 2003 года стал официальным стандартом взаимодействия между Python-приложениями и веб-серверами. Он до сих пор используется в разработке синхронных веб-приложений.
Компоненты WSGI
WSGI описывает способ взаимодействия между веб-сервером и веб-приложением. Между ними также может находиться дополнительный слой — middleware-компоненты, которые выполняют вспомогательные функции.
Разберем каждую из этих частей подробнее.
Веб-сервер
В контексте WSGI веб-сервер исполняет роль шлюза между внешним миром (браузерами и другими HTTP-клиентами) и Python-приложением. Он принимает HTTP-запросы, формирует специальный словарь окружения с параметрами запроса и вызывает Python-приложение, передавая ему управление.
После выполнения запроса сервер получает от приложения ответ и отправляет его обратно клиенту.
Веб-приложение
Это основная бизнес-логика, написанная на языке Python. В терминах WSGI веб-приложение — это Python-объект, реализующий определенный интерфейс. Чаще всего это функция, принимающая два аргумента:
- environ — словарь с параметрами запроса и переменными окружения;
- start_response — функция обратного вызова, которую приложение должно вызвать перед отправкой тела ответа. С ее помощью передается код статуса (например, 200 OK) и заголовки HTTP-ответа.
Пример простейшего WSGI-приложения:
def application(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/plain')])
return [b'Hello from Trex!']
Middleware-компоненты
Middleware — это WSGI-совместимые компоненты, которые принимают запросы от сервера, обрабатывают их и передают дальше в приложение или другим middleware-компонентам. Аналогично, они могут обрабатывать и ответ перед отправкой его клиенту.
Такие компоненты позволяют расширять функциональность приложения без изменения его кода. Middleware часто применяются для решения следующих задач:
- модификация словаря environ (например, добавление параметров или контекста),
- логирование запросов и ответов,
- управление сессиями и cookie,
- обработка ошибок и исключений,
- кеширование, переадресация и контроль доступа.
Использование middleware облегчает тестирование, повторное использование и адаптацию одного и того же приложения к разным средам.
Как работает WSGI
WSGI позволяет Python-приложениям и веб-серверам «договориться» между собой: сервер получает HTTP-запрос от клиента, а приложение возвращает ответ — все строго по правилам. Этот стандарт описывает, кто за что отвечает и как именно передаются данные, что избавляет разработчиков от необходимости погружаться в детали сетевого взаимодействия.
Обработка запроса проходит в несколько этапов.
1. Отправка запроса клиентом
Пользователь открывает сайт в браузере или отправляет запрос через API. Веб-сервер принимает это соединение (чаще всего по портам 80 или 443) и начинает обработку.
2. Разбор запроса и подготовка окружения
WSGI-сервер разбирает запрос: определяет HTTP-метод, путь, заголовки, тело и другие параметры. Из этих данных он собирает словарь environ, который потом передаст приложению. Также создается функция start_response (status, headers), чтобы приложение позже обратилось к серверу и сообщило, какой статус ответа отправить клиенту и какие заголовки нужны.
3. Вызов приложения
Далее сервер вызывает само WSGI-приложение, передавая ему environ и start_response. В ответ приложение должно вернуть итерируемый объект — например, список байтов или генератор.
Генератор — это специальная функция, которая возвращает данные по частям, используя ключевое слово yield. Он не хранит все содержимое в памяти сразу, а отдает его «на лету», по мере запроса. Это удобно, если нужно отдавать большой ответ (например, большой файл или поток данных), не загружая всю информацию в оперативную память.
4. Формирование и отправка ответа клиенту
Сначала сервер использует start_response, чтобы отправить статус и заголовки. Затем поэлементно читает итерируемый объект и передает содержимое клиенту.
5. Управление соединением и обработка ошибок
Помимо основной передачи данных, сервер следит за таймаутами, завершает соединение, обрабатывает сетевые ошибки и логирует важные события. Многие WSGI-серверы также поддерживают многопоточность или многопроцессность, чтобы обслуживать несколько клиентов одновременно.
WSGI четко разделяет зоны ответственности: приложение занимается бизнес-логикой, а сервер — всем остальным. Это делает архитектуру гибкой, масштабируемой и независимой от конкретного веб-сервера.
Требования в WSGI-приложению
На первый взгляд, написать WSGI-приложение кажется простой задачей. Но чтобы оно действительно корректно взаимодействовало с сервером, нужно строго соблюдать ряд требований из спецификации PEP 3333.
Перечислим несколько моментов, на которые стоит обратить внимание.
- Вызываемость. Приложение должно быть вызываемым объектом (callable): чаще всего это функция, но может быть и класс с методом __call__. Это уже рассмотрено в разделе о компонентах.
- Передача ответа. После вызова start_response(), приложение должно вернуть итерируемый объект, содержащий байтовые строки. Это может быть список, генератор или любой другой iterable. Главное — не str, а bytes. Иначе сервер не сможет правильно отправить ответ клиенту.
- start_response нужно вызывать один раз. Важно делать это до того, как начнется возврат тела ответа, так как именно через этот вызов сервер получает HTTP-статус и заголовки. Нарушение порядка вызовов может привести к сбоям.
- Приложение не должно заниматься сетевыми операциями. Оно не должно напрямую отправлять данные клиенту, управлять соединениями, логировать низкоуровневые ошибки и так далее. Все это — зона ответственности сервера. Приложение должно просто вернуть данные, ничего не зная о том, кто и как их доставит до клиента.
- Ошибки нужно либо обрабатывать, либо честно выбрасывать. Если в коде приложения произошла ошибка, WSGI-сервер поймает исключение и сможет обработать его на своей стороне. Но приложение при этом не должно скрывать ошибки или оставлять соединение в неопределенном состоянии.
- Кодировка. Все строки, которые вы отдаете в теле ответа, уже должны быть в виде bytes. Преобразование str → bytes — обязанность приложения.
Кратко: приложение должно быть «хорошим гражданином» в WSGI-мире — принимать входные данные, вызывать start_response, вернуть байты и не лезть туда, где правит сервер.
Словарь environ и его переменные
Словарь environ — это ключевой механизм передачи данных от WSGI-сервера к Python-приложению. Он содержит все сведения о текущем HTTP-запросе и окружении, в котором он обрабатывается. Этот словарь полностью совместим с обычным dict в Python и заполняется по соглашениям CGI, расширенным спецификой WSGI.
Разберем, из чего он состоит и как с ним работать.
Основные переменные CGI
Эти ключи описывают структуру HTTP-запроса и параметры соединения. Они являются обязательной частью environ и используются для получения базовой информации:
Ключ | Значение |
REQUEST_METHOD | HTTP-метод запроса: GET, POST, PUT, DELETE и т.д. |
SCRIPT_NAME | Начальная часть пути URL, соответствующая приложению (или пустая строка). |
PATH_INFO | Остаток пути URL после SCRIPT_NAME, указывает на запрашиваемый ресурс. |
QUERY_STRING | Строка запроса после ?, без декодирования. |
CONTENT_TYPE | MIME-тип тела запроса, например: application/json, text/plain |
CONTENT_LENGTH | Длина тела запроса в байтах (если указана). |
SERVER_NAME | Имя сервера, принявшего запрос. |
SERVER_PORT | Порт, на котором получен запрос. |
SERVER_PROTOCOL | Версия HTTP, например: HTTP/1.1 |
REMOTE_ADDR | IP-адрес клиента. |
REMOTE_PORT | Порт клиента. |
Эти переменные позволяют приложению понять, кто, что и каким образом запросил.
Заголовки HTTP-запроса
Все заголовки запроса автоматически преобразуются в элементы environ, но в измененной форме:
- префикс HTTP_,
- все символы — заглавные (upper),
- дефисы заменяются на подчеркивания.
Примеры:
Заголовок | Ключ в environ |
Host | HTTP_HOST |
User-Agent | HTTP_USER_AGENT |
Authorization | HTTP_AUTHORIZATION |
Accept-Encoding | HTTP_ACCEPT_ENCODING |
Исключения: Content-Type и Content-Length не получают префикс HTTP_, они присутствуют как отдельные ключи (см. выше).
Специфические переменные WSGI
Дополнительные параметры, которые описывают техническое окружение, в котором выполняется приложение:
Ключ | Описание |
wsgi.version | Версия протокола WSGI. |
wsgi.url_scheme | Схема запроса: http или https. |
wsgi.input | Поток ввода (file-like объект), содержащий тело запроса. |
wsgi.errors | Поток ошибок (обычно связан с sys.stderr). |
wsgi.multithread | Признак того, что приложение может выполняться в нескольких потоках. |
wsgi.multiprocess | Признак того, что приложение может выполняться в нескольких процессах. |
wsgi.run_once | Признак того, что приложение будет вызвано только один раз и сразу завершится (например, при запуске в CGI-среде). |
Эти переменные позволяют приложению адаптироваться к среде выполнения — учитывать потоковую / процессную модель сервера, использовать правильную схему и понимать, как читать тело запроса.
Работа с wsgi.input
Когда клиент отправляет данные в теле запроса (например, форму POST или JSON), сервер не помещает их напрямую в environ. Вместо этого тело доступно через поток wsgi.input. Чтобы считать его содержимое:
- получите длину тела запроса из переменной CONTENT_LENGTH;
- считайте ровно это количество байт из wsgi.input.
Пример:
length = int(environ.get("CONTENT_LENGTH", 0))
body = environ["wsgi.input"].read(length)
Если считать больше, чем указано, приложение может зависнуть, ожидая несуществующие данные. Если меньше — часть тела потеряется.
Обратите внимание:
- wsgi.input — это не строка, а поток (file-like object). Он ведет себя как файл: его можно читать, но не перематывать. Это значит, что повторное чтение тела запроса без сохранения не получится — нужно буферизовать, если потребуется доступ к телу позже.
- Поток содержит байты, и часто нужно декодировать их (body.decode(‘utf-8’)).
Обработка ошибок
Ошибки в WSGI-приложении могут возникнуть на любом этапе — от разбора запроса до генерации ответа. Чтобы поведение приложения оставалось предсказуемым и корректным, важно правильно отлавливать исключения, формировать валидный HTTP-ответ и логировать проблему через системные потоки ошибок.
WSGI накладывает определенные требования на обработку ошибок: вы должны сообщать серверу о сбоях через вызов start_response() и, при необходимости, передавать подробности исключения. Нарушение этих правил может привести к нарушению протокола, рассинхронизации с клиентом или зависанию соединения.
Разберем три типовых случая, когда могут возникнуть ошибки, и что делать в каждом из них.
Ошибка до вызова start_response
Это самый простой и безопасный случай. Исключение возникает в начале обработки запроса — например, при чтении данных из environ, вычислениях или в бизнес-логике.
Что нужно сделать:
- Записать сообщение об ошибке в environ[«wsgi.errors»] — это поток, предназначенный для вывода логов ошибок.
- Вызвать start_response() с кодом ошибки, например, ‘500 Internal Server Error’.
- Обязательно передать третий аргумент exc_info=sys.exc_info() — это сигнал для WSGI-сервера, что произошла ошибка, и он должен отнестись к ответу соответствующим образом.
- Вернуть читаемое тело ответа, объясняющее ошибку клиенту.
Пример:
import sys
def application(environ, start_response):
try
result = 1 / 0 # Деление на ноль – вызов исключения
start_response('200 OK', [('Content-Type', 'text/plain')])
return [b'Hello, world!']
except Exception:
print('Ошибка в приложении', file=environ['wsgi.errors'])
start_response(
'500 Internal Server Error',
[('Content-Type', 'text/plain')],
sys.exc_info()
)
return [b'Internal Server Error']
Ошибка после вызова start_response
Иногда ошибка случается после того, как заголовки уже были отправлены — например, в процессе формирования тела ответа. В этом случае мы обязаны повторно вызвать start_response(), но уже с аргументом exc_info, чтобы сервер корректно понял, что произошел сбой.
Пример:
import sys
def application(environ, start_response):
try:
start_response('200 OK', [('Content-Type', 'text/plain')]) # Первый вызов start_response()
body = b'Result: ' + str(1 / 0).encode() # Деление на ноль - вызов исключения
return [body]
except Exception:
print('Ошибка после start_response()', file=environ['wsgi.errors'])
start_response( # Повторный вызов start_response с exc_info
'500 Internal Server Error',
[('Content-Type', 'text/plain')],
sys.exc_info()
)
return [b'Internal Server Error']
Ошибка во время генерации тела ответа
Иногда тело ответа возвращается не сразу, а по частям — через генератор. Это называется ленивая итерация (lazy iteration).
Такой подход экономит память, особенно при больших объемах данных. Однако если исключение произойдет внутри генератора, оно случится уже после вызова start_response(), и может нарушить соединение с клиентом.
Чтобы избежать этого, оберните генератор в защитную обертку, которая перехватит ошибки и залогирует их:
# Генератор, формирующий тело ответа
def generate_response():
yield b'Chunk 1\n'
raise ValueError("Ошибка внутри генератора")
yield b'Chunk 2\n'
# Обертка, которая безопасно перехватывает исключения
def safe_iter(iterable, error_stream):
try:
for chunk in iterable:
yield chunk
except Exception as e:
print(f'Ошибка в теле ответа: {e}', file=error_stream)
Использование в приложении:
def application(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/plain')])
return safe_iter(generate_response(), environ['wsgi.errors'])
Централизованная обработка ошибок через middleware
Повторять try/except в каждом обработчике неудобно и чревато ошибками. Вместо этого лучше использовать промежуточный слой (middleware), который будет перехватывать все необработанные исключения в приложении.
Пример простого middleware для обработки ошибок:
class ErrorMiddleware:
def __init__(self, app):
self.app = app
def __call__(self, environ, start_response):
try:
return self.app(environ, start_response)
except Exception:
import sys
print('Исключение в приложении', file=environ['wsgi.errors'])
start_response(
'500 Internal Server Error',
[('Content-Type', 'text/plain')],
sys.exc_info()
)
return [b'Internal Server Error']
Использование:
app = ErrorMiddleware(application)
Как выбрать WSGI-сервер
Выбор WSGI-сервера зависит от ваших задач: нужна ли простота запуска, высокая производительность или тонкая настройка.
Если вам нужен простой, надежный и масштабируемый сервер для продакшена, практически стандартом является Gunicorn. Он легко запускается, хорошо работает с Django и Flask, поддерживает как синхронные, так и асинхронные воркеры. Подходит для 90% проектов. Однако он не работает под Windows — там его стоит заменить на Waitress.
Waitress — это решение, которое хорошо работает на Windows и не требует компиляции. Данная реализация немного проще по возможностям, чем Gunicorn, но идеально подходит для команд разработки кроссплатформенных решений.
Для сложной инфраструктуры, где требуется тонкая настройка, управление процессами и поддержка разных протоколов, используется uWSGI. Это мощный и гибкий сервер с довольно сложной конфигурацией. Выбирается в проектах, где важен контроль над производительностью и поведением.
Если в приоритете максимальная производительность с минимальными накладными расходами, стоит обратить внимание на Meinheld или Bjoern. Первый использует веб-сокеты, реализует событийную модель и часто используется в связке с Gunicorn как воркер. Bjoern написан на языке C и позволяет получить максимальную скорость работы. Хорошо подходит для микросервисов и встроенных решений, но требует опыта и ручной настройки.
Для небольших автономных приложений можно использовать CherryPy — это и сервер, и фреймворк. Он работает «из коробки», удобен для прототипов и внутренних инструментов, но редко применяется в больших продакшн-проектах.
Полезные ресурсы
Формальное описание WSGI-протокола: сигнатура приложения, переменные окружения, правила формирования ответа и работы со статусом.
Оба фреймворка используют WSGI в качестве интерфейса между приложением и сервером.
uWSGI умеет не только запускать Python-приложения, но и отдавать статику, управлять кешем, логами, процессами и многим другим.
Один из самых надежных и распространенных WSGI-серверов. В документации — примеры запуска, настройки воркеров, логирования и многого другого.
Где запустить приложение
Когда WSGI-приложение готово, возникает главный вопрос: где его запускать? Один из надежных вариантов — облачная платформа Selectel.
Selectel предоставляет все необходимое для развертывания и масштабирования Python-приложений: виртуальные серверы, управляемые Kubernetes-кластеры, балансировщики нагрузки и объектное хранилище. Вы можете быстро развернуть Gunicorn или uWSGI, подключить хранилище, настроить масштабирование и получить стабильную, гибкую инфраструктуру под реальные нагрузки.
Selectel помогает не просто запустить WSGI-приложение, но сделать его частью полноценной, продуманной инфраструктуры. Удобная панель управления для работы со всеми услугами и понятный API позволяют сосредоточиться на логике приложения, не отвлекаясь на рутину.
Заключение
WSGI стал стандартом для запуска Python-приложений в вебе, задав единый и понятный способ общения между кодом и сервером. Благодаря ему фреймворки и серверы легко сочетаются, а приложения работают стабильно — вне зависимости от инфраструктуры.