Что такое WSGI - про сервер, приложение и протокол - Академия Selectel

Что такое WSGI

Юлия Белоконь
Юлия Белоконь Автор Selectel
31 мая 2025

Расскажем, что такое 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_METHODHTTP-метод запроса: GET, POST, PUT, DELETE и т.д.
SCRIPT_NAMEНачальная часть пути URL, соответствующая приложению (или пустая строка).
PATH_INFOОстаток пути URL после SCRIPT_NAME, указывает на запрашиваемый ресурс.
QUERY_STRINGСтрока запроса после ?, без декодирования.
CONTENT_TYPEMIME-тип тела запроса, например: application/json, text/plain
CONTENT_LENGTHДлина тела запроса в байтах (если указана).
SERVER_NAMEИмя сервера, принявшего запрос.
SERVER_PORTПорт, на котором получен запрос.
SERVER_PROTOCOLВерсия HTTP, например: HTTP/1.1
REMOTE_ADDRIP-адрес клиента.
REMOTE_PORTПорт клиента.

Эти переменные позволяют приложению понять, кто, что и каким образом запросил.

Заголовки HTTP-запроса

Все заголовки запроса автоматически преобразуются в элементы environ, но в измененной форме:

  • префикс HTTP_,
  • все символы — заглавные (upper),
  • дефисы заменяются на подчеркивания.

Примеры:

ЗаголовокКлюч в environ
HostHTTP_HOST
User-AgentHTTP_USER_AGENT
AuthorizationHTTP_AUTHORIZATION
Accept-EncodingHTTP_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. Чтобы считать его содержимое:

  1. получите длину тела запроса из переменной CONTENT_LENGTH;
  2. считайте ровно это количество байт из 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, вычислениях или в бизнес-логике.

Что нужно сделать:

  1. Записать сообщение об ошибке в environ[«wsgi.errors»] — это поток, предназначенный для вывода логов ошибок.
  2. Вызвать start_response() с кодом ошибки, например, ‘500 Internal Server Error’.
  3. Обязательно передать третий аргумент exc_info=sys.exc_info() — это сигнал для WSGI-сервера, что произошла ошибка, и он должен отнестись к ответу соответствующим образом.
  4. Вернуть читаемое тело ответа, объясняющее ошибку клиенту.

Пример:


    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-протокола: сигнатура приложения, переменные окружения, правила формирования ответа и работы со статусом.

  • Документация фреймворков Flask и Django

Оба фреймворка используют WSGI в качестве интерфейса между приложением и сервером.

uWSGI умеет не только запускать Python-приложения, но и отдавать статику, управлять кешем, логами, процессами и многим другим.

Один из самых надежных и распространенных WSGI-серверов. В документации — примеры запуска, настройки воркеров, логирования и многого другого.

Где запустить приложение

Когда WSGI-приложение готово, возникает главный вопрос: где его запускать? Один из надежных вариантов — облачная платформа Selectel.

Selectel предоставляет все необходимое для развертывания и масштабирования Python-приложений: виртуальные серверы, управляемые Kubernetes-кластеры, балансировщики нагрузки и объектное хранилище. Вы можете быстро развернуть Gunicorn или uWSGI, подключить хранилище, настроить масштабирование и получить стабильную, гибкую инфраструктуру под реальные нагрузки.

Selectel помогает не просто запустить WSGI-приложение, но сделать его частью полноценной, продуманной инфраструктуры. Удобная панель управления для работы со всеми услугами и понятный API позволяют сосредоточиться на логике приложения, не отвлекаясь на рутину. 

Заключение

WSGI стал стандартом для запуска Python-приложений в вебе, задав единый и понятный способ общения между кодом и сервером. Благодаря ему фреймворки и серверы легко сочетаются, а приложения работают стабильно — вне зависимости от инфраструктуры.