WSGI – протокол связи веб-сервера с Python-приложением::Журнал СА 12.2008
www.samag.ru
     
Поиск   
              
 www.samag.ru    Web  0 товаров , сумма 0 руб.
E-mail
Пароль  
 Запомнить меня
Регистрация | Забыли пароль?
Журнал "Системный администратор"
Журнал «БИТ»
Наука и технологии
Подписка
Где купить
Авторам
Рекламодателям
Магазин
Архив номеров
Вакансии
Контакты
   

  Опросы

Какие курсы вы бы выбрали для себя?  

Очные
Онлайновые
Платные
Бесплатные
Я и так все знаю

 Читать далее...

1001 и 1 книга  
20.12.2019г.
Просмотров: 4346
Комментарии: 0
Dr.Web: всё под контролем

 Читать далее...

04.12.2019г.
Просмотров: 5646
Комментарии: 0
Особенности сертификаций по этичному хакингу

 Читать далее...

28.05.2019г.
Просмотров: 6875
Комментарии: 2
Анализ вредоносных программ

 Читать далее...

28.05.2019г.
Просмотров: 7270
Комментарии: 1
Микросервисы и контейнеры Docker

 Читать далее...

28.05.2019г.
Просмотров: 6342
Комментарии: 0
Django 2 в примерах

 Читать далее...

Друзья сайта  

Форум системных администраторов  

sysadmins.ru

 WSGI – протокол связи веб-сервера с Python-приложением

Архив номеров / 2008 / Выпуск №12 (73) / WSGI – протокол связи веб-сервера с Python-приложением

Рубрика: Веб /  Веб-технологии

Дмитрий Васильев

WSGI – протокол связи веб-сервера
с Python-приложением

Если вы разрабатываете веб-приложение, каркас для разработки веб-приложений или даже веб-сервер на языке Python, вам необходимо знание основ протокола WSGI – стандартного способа связи веб-сервера и веб-приложения.

Долгое время пользователи многих веб-приложений, написанных на Python, были ограничены в выборе веб-серверов, которые они могли использовать совместно с приложениями. Разработчики приложений обычно ограничивались поддержкой одного (изредка – нескольких) способа подключения к веб-серверу. Одни приложения могли использовать CGI, или FastCGI, другие могли быть привязаны к модулю Apache mod_python. Некоторые из приложений могли поддерживать только API, специфичное для одного-единственного сервера. Такая ситуация затрудняла распространение веб-приложений, написанных на Python, и в конце 2003 года впервые был предложен протокол WSGI.

Описание протокола

WSGI (расшифровывается как Web Server Gateway Interface – интерфейс шлюза веб-сервера) – это простой и универсальный интерфейс взаимодействия между веб-сервером и веб-приложением, впервые описанный в PEP-333 (http://www.python.org/dev/peps/pep-0333). Под простотой в данном случае подразумевается лишь простота подключения приложения, но не простота реализации веб-приложений для авторов. Надо заметить, что основной целью разработки WSGI была разработка простого протокола, который мог бы разделить выбор каркасов для разработки веб-приложений от выбора веб-серверов. Это, в частности, позволяет разработчикам приложений (каркасов) и серверов концентрироваться на своей области специализации и отличает WSGI от более общих протоколов связи приложений с веб-серверами, таких как CGI, или FastCGI. С точки зрения WSGI цельное веб-приложение делится на две части: сервер (или шлюз) и непосредственно приложение (или каркас для построения приложений). Для обращения к приложению серверная часть использует вызываемый объект (это может быть функция, метод, класс или экземпляр класса с методом __call__). WSGI также позволяет создавать приложения-посредники, которые являются приложением для веб-сервера и сервером для веб-приложения. Такие посредники могут использоваться для предварительной обработки запросов к приложению или последующей обработки его ответов. Дальше мы рассмотрим несколько примеров использования WSGI и затем обратимся к деталям протокола.

Сторона приложения

Как уже говорилось, приложение – это вызываемый объект, который принимает два аргумента. Приложения должны допускать возможность многократных вызовов, что является обычной ситуацией практически для всех серверов (исключая вызовы с помощью CGI). Далее представлены два примера приложения. Первое приложение реализовано в виде функции:

def simple_app(environ, start_response):

status = '200 OK'

response_headers = [('Content-type','text/plain')]

start_response(status, response_headers)

return ['Hello world!\n']

Здесь функция использует второй аргумент для передачи статуса и заголовков ответа и затем возвращает тело ответа в виде списка строк. Второе приложение реализовано в виде класса:

class AppClass:

 

def __init__(self, environ, start_response):

self.environ = environ

self.start = start_response

 

def __iter__(self):

status = '200 OK'

response_headers = [('Content-type','text/plain')]

self.start(status, response_headers)

yield "Hello world!\n"

В данном случае объект класса будет представлять из себя итератор, который на первом шаге итерации использует второй аргумент для передачи статуса и заголовков ответа и затем вернет тело ответа.

Надо отметить, что приложение с точки зрения WSGI – это всего лишь точка входа, через которую сервер получает доступ к веб-приложению или каркасу для построения веб-приложений.

Сторона сервера

Сервер (или шлюз) будет вызывать приложение для каждого HTTP-запроса, который ему предназначен. Для примера представлен упрощенный шлюз CGI – WSGI. Пример использует упрощенную обработку ошибок, т.к. по умолчанию ошибки будут выдаваться на sys.stderr и затем записываться в лог веб-сервера. Вызываемый объект приложения в данном случае передается как параметр функции:

import os

import sys

 

def run_with_cgi(application):

 

    environ = dict(os.environ.items())

    environ['wsgi.input'] = sys.stdin

    environ['wsgi.errors'] = sys.stderr

    environ['wsgi.version'] = (1, 0)

    environ['wsgi.multithread'] = False

    environ['wsgi.multiprocess'] = True

    environ['wsgi.run_once'] = True

 

    if environ.get('HTTPS', 'off') in ('on', '1'):

        environ['wsgi.url_scheme'] = 'https'

    else:

        environ['wsgi.url_scheme'] = 'http'

 

    headers_set = []

    headers_sent = []

 

    def write(data):

        if not headers_set:

            raise AssertionError("write() before start_response()")

 

        elif not headers_sent:

            # Перед выводом первых данных вывести

            # сохраненные заголовки

        status, response_headers = headers_sent[:] = headers_set

            sys.stdout.write('Status: %s\r\n' % status)

            for header in response_headers:

                sys.stdout.write('%s: %s\r\n' % header)

            sys.stdout.write('\r\n')

 

        sys.stdout.write(data)

        sys.stdout.flush()

 

    def start_response(status, response_headers, exc_info=None):

        if exc_info:

            try:

                if headers_sent:

                    # Если заголовки были отправлены,

                    # выкинуть исключение

                    raise exc_info[0], exc_info[1], exc_info[2]

            finally:

                exc_info = None

        elif headers_set:

            raise AssertionError("Headers already set!")

 

        headers_set[:] = [status, response_headers]

        return write

 

    result = application(environ, start_response)

    try:

        for data in result:

            # Не отправляем заголовки, пока не видно тела

            if data:

                write(data)

        if not headers_sent:

            # Отправляем заголовки, если тело было пустое

            write('')

    finally:

        if hasattr(result, 'close'):

            result.close()

Посредник: сервер и приложение в одном

Как уже было замечено, некоторые объекты могут играть сразу две роли – быть сервером для какого-либо приложения и приложением для сервера. Вот примеры ситуаций, для которых могут быть полезны WSGI-посредники:

  •   перенаправление запроса на различные приложения, в зависимости от URL после соответствующего изменения environ;
  • возможность запуска нескольких веб-приложений, или каркасов в одном процессе;
  • распределение нагрузки по нескольким сетевым приложениям;
  • обработка ответов приложения, например, с помощью XSL.

Присутствие посредника в большинстве случаев прозрачно и для сервера, и для приложения, более того, посредников можно располагать одного за другим, составляя таким образом «стек посредников».

Детали протокола

Как мы уже видели, приложение должно принимать два аргумента, которые были названы environ и start_response, но могут иметь любые другие имена.

Первый параметр (environ) должен быть объектом словаря (dict) Python и содержит переменные среды, похожие на переменные CGI. Этот объект также должен содержать обязательные для WSGI параметры, которые мы подробнее рассмотрим позже, и может содержать переменные, специфичные для конкретного веб-сервера.

Второй параметр (start_response) – это вызываемый объект, которым приложение предваряет возвращение тела ответа, и принимающий два обязательных параметра и один необязательный. Первый параметр (status) – статус ответа в виде строки, например «200 Ok». Второй параметр (response_headers в примере выше) – список кортежей (tuples) вида (имя_заголовка, значение_заголовка). Третий, необязательный, параметр (exc_info) должен использоваться только при обработке ошибок и должен быть кортежем, который возвращает функция sys.exc_info(). Надо заметить, что start_response не посылает заголовки сразу, а откладывает их до получения первой части тела ответа, чтобы в случае ошибки их можно было заменить на заголовки, сопутствующие ошибке. При этом start_response можно вызывать несколько раз, только если передается третий параметр (exc_info).

После вызова сервером вызываемый объект приложения должен вернуть итерируемый объект, возвращающий ноль, или несколько строк. Как мы видели в предыдущих примерах выше, этого можно достичь несколькими способами, например, вернув список строк, или объект-итератор. При получении очередной части ответа сервер посылает ее клиенту без буферизации, но приложения могут осуществлять буферизацию ответа собственными силами.

В случае если итерируемый объект имеет метод close(), он будет вызван по окончании обработки ответа сервером, даже в случае ошибки. Таким образом метод close() может использоваться для закрытия всех ресурсов приложения, которые могли быть задействованы при создании ответа.

Переменные словаря environ

Словарь environ может содержать следующие CGI переменные (см. таблицу 1).

Таблица 1. Переменные, которые может содержать словарь environ

Имя

Наличие

Описание

REQUEST_METHOD

Обязательный

Метод запроса, например GET или POST

SCRIPT_NAME

Может быть пустым

Начальная порция пути в URL, соответствующая объекту приложения

PATH_INFO

Может быть пустым

Остаток пути в URL, соответствующий цели запроса внутри приложения

QUERY_STRING

Может быть пустым или отсутствовать

Часть URL, которая следует за «?»

CONTENT_TYPE

Может быть пустым или отсутствовать

Содержимое заголовка Content-Type в HTTP-запросе

CONTENT_LENGTH

Может быть пустым или отсутствовать

Содержимое заголовка Content‑Length в HTTP-запросе

SERVER_NAME

Обязательный

Имя сервера

SERVER_PORT

Обязательный

Порт сервера

SERVER_PROTOCOL

Обязательный

Версия протокола, который использует клиент для посылки запроса, например HTTP/1.0 или HTTP/1.1

HTTP_переменные

Необязательны

Переменные, соответствующие заголовкам запроса, переданным клиентом

Также словарь environ должен содержать следующие, обязательные для WSGI, переменные (см. таблицу 2).

Таблица 2. Обязательные для WSGI переменные в словаре environ

Имя

Описание

wsgi.version

Кортеж (1, 0), представляющий версию WSGI – 1.0

wsgi.url_scheme

Строка, представляющая схему из URL, обычно http или https

wsgi.input

Объект, похожий на файл, из которого может быть прочитано тело запроса

wsgi.output

Объект, похожий на файл, в который приложение может выводить сообщения об ошибках

wsgi.multithread

True, если объект приложения может быть одновременно вызван из нескольких потоков

wsgi.multiprocess

True, если соответствующие объекты приложения могут быть одновременно вызваны в нескольких процессах

wsgi.run_once

True, если сервер предполагает (но не гарантирует), что приложение будет вызвано только один раз во время жизни текущего процесса

Плюс environ может содержать специфичные для конкретного сервера переменные и переменные среды. Чтобы увидеть набор переменных, передаваемых приложению, можно использовать, например, следующее простое WSGI-приложение:

def application(environ, start_response):

lines = []

for key, value in environ.items():

lines.append("%s: %r" % (key, value))

start_response("200 OK", [("Content-Type", "text/plain")])

return ["\n".join(lines)]

Обработка ошибок

В общем случае приложение должно обрабатывать внутренние ошибки и выводить соответствующее сообщение клиенту. Конечно, прежде чем выводить сообщение об ошибке, приложение не должно начинать выводить нормальный ответ. Чтобы обойти эту ситуацию, используется третий параметр start_response – exc_info:

try:

# Код обычного приложения

status = "200 OK"

response_headers = [("content-type", "text/plain")]

start_response(status, response_headers)

return ["OK"]

except:

# В реальном коде различные ошибки должны обрабатываться

# различными обработчиками и не должен использоваться пустой except

status = "500 Error"

response_headers = [("content-type", "text/plain")]

start_response(status, response_headers, sys.exc_info())

return ["Error"]

Если приложение еще не начало вывод тела ответа, когда произошло исключение, то вызов start_response в обработчике будет нормальным и клиенту вернется ответ об ошибке. В случае если на момент ошибки уже был начат вывод ответа, start_response выкинет переданное исключение. Это исключение не должно обрабатываться обработчиком, а будет обработано сервером и записано в журнал ошибок.

Заключение

На данный момент подавляющее большинство серверов и каркасов для создания веб-приложений поддерживает WSGI. В качестве примера можно привести большое количество серверов, написанных на Python, в том числе веб-сервер из пакета Twisted (http://twistedmatrix.com/trac), а также адаптеры для CGI и FastCGI, модули для Apache и nginx. В качестве примера каркасов могут быть наиболее известные – Django, TurboGears и Pylons. Более полный список и последнюю информацию по WSGI можно получить на сайте http://www.wsgi.org.

Описание работы WSGI, изложенное в этой статье, должно помочь в лучшем понимании готовых веб-приложений, написанных с использованием этого протокола, а также в написании собственных веб-приложений и серверов.

Приложение

Пакет wsgiref

В стандартной библиотеке Python 2.5 появился новый пакет wsgiref, предоставляющий различные утилиты для упрощения работы с WSGI. Рассмотрим кратко его содержимое:

  • wsgiref.util – содержит функции для работы со словарем environ. Например, функцию для сборки полного URL из переменных environ;
  • wsgiref.headers – содержит класс для упрощения работы с заголовками ответа в виде объекта, похожего на словарь;
  • wsgiref.simple_server – содержит функции и классы для создания простого WSGI-сервера и демонстрационного приложения;
  • wsgiref.validate – содержит функцию-обертку, проверяющую WSGI-приложение и сервер на соответствие WSGI-спецификации;
  • wsgiref.handlers – содержит базовые классы для создания WSGI-серверов.

Комментарии отсутствуют

Добавить комментарий

Комментарии могут оставлять только зарегистрированные пользователи

               Copyright © Системный администратор

Яндекс.Метрика
Tel.: (499) 277-12-41
Fax: (499) 277-12-45
E-mail: sa@samag.ru