Контекст запроса Flask

Этот документ описывает поведение Flask 0.7, которое в основном совпадает со старым, но имеет некоторые небольшие отличия.

Рекомендуем сначала прочитать главу Контекст приложения Flask.

Подробнее о локальных объектах контекста

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

from flask import request, url_for

def redirect_url():
    return request.args.get('next') or \
           request.referrer or \
           url_for('index')

Можно заметить, что функция обращается к объекту запроса. Если попытаться запустить её из оболочки Python, будет выброшено исключение:

>>> redirect_url()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'NoneType' object has no attribute 'request'

В этом есть определённый смысл, потому что в данный момент нет запроса, к которому мы пытаемся получить доступ. Итак, нам нужно создать запрос и связать его с текущим контекстом. Создадим RequestContext с помощью метода test_request_context:

>>> ctx = app.test_request_context('/?next=http://example.com/')

Этот контекст можно использовать одним из двух способов - используя выражение with или вызвав методы push() и pop():

>>> ctx.push()

После чего можно работать с объектом запроса:

>>> redirect_url()
u'http://example.com/'

И так до тех пор, пока вы не вызовете pop:

>>> ctx.pop()

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

За более подробной информацией об использовании контекста запроса из интерактивной оболочки Python, обратитесь к главе Работа с командной оболочкой.

Как работает контекст

Если посмотреть изнутри на то, как работает приложение Flask WSGI, можно обнаружить фрагмент кода, который выглядит очень похожим на следующий:

def wsgi_app(self, environ):
    with self.request_context(environ):
        try:
            response = self.full_dispatch_request()
        except Exception, e:
            response = self.make_response(self.handle_exception(e))
        return response(environ, start_response)

Метод request_context() возвращает новый объект RequestContext и использует его в выражении with для связывания контекста. Всё, что будет вызвано из этого потока, начиная с этой точки и до конца выражения with, будет иметь доступ к глобальному объекту запроса (flask.request и т.п.).

Контекст запроса изнутри работает как стек: на самом верху стека находится текущий активный запрос. push() добавляет контекст на верхушку стека, а pop() вынимает его из стека. При изъятии также вызываются функции teardown_request() приложения.

Стоит также отметить, что при добавлении контекста запроса в стек также создаётся контекст приложения, если его ещё не было. (Not completed: Another thing of note is that the request context will automatically also create an application context when it’s pushed and there is no application context for that application so far.)

Функции обратного вызова и ошибки

Что случится, если произойдёт ошибка во время обработки запроса во Flask? Частично это поведение изменилось в версии 0.7, потому что желательно знать, что на самом деле произошло. Новое поведение очень простое:

  1. Перед каждым запросом выполняются функции before_request(). Если одна из этих функций вернула ответ, другие функции не выполняются. Однако, в любом случае, это значение трактуется как значение, возвращённое представлением.
  2. Если функции before_request() не вернули ответ, обработка запроса прекращается и он передаётся в подходящую функцию представления, которая может вернуть ответ.
  3. Значение, возвращённое из функции представления, преобразуется в настоящий объект ответа и обрабатывается функциями after_request(), которые могут заменить его целиком или отредактировать.
  4. В конце запроса выполняются функции teardown_request(). Это происходит независимо от того, было ли выброшено необработанное исключение, были ли вызваны функции before_request(), или произошло всё сразу (например, в тестовом окружении обработка функций обратного вызова before_request() иногда может быть отключена).

Итак, что же происходит в случае ошибки? В рабочем режиме неотловленные исключения приводят к тому, что обработчик выводит сообщение об ошибке 500 на сервере. В режиме разработки, однако, приложение не обрабатывает исключение и передаёт его наверх, серверу WSGI. Таким образом, средства интерактивной отладки могут предоставить информацию для отладки.

Важное изменение в версии 0.7 заключается в том, что внутреннее сообщение сервера об ошибке теперь больше не подвергается пост-обработке с помощью функций обратного вызова after_request() и больше нет гарантии того, что они будут выполнены. Таким образом, внутренняя обработка кода выглядит понятнее и удобнее в настройке.

Предполагается, что вместо них должны использоваться новые функции teardown_request(), специально предназначенные для действий, которые нужно выполнять по окончании запроса при любом его исходе.

Функции обратного вызова teardown_request

Функции обратного вызова teardown_request() - это особые функции обратного вызова, которые выполняются отдельно. Строго говоря, они не зависят от действительной обработки запроса и связаны с жизненным циклом объекта RequestContext. Когда контекст запроса вынимается из стека, вызываются функции teardown_request().

Это важно знать, если жизнь контекста запроса будет удлинена при использовании клиента для тестировании с помощью выражения with или при использовании контекста запроса из командной строки:

with app.test_client() as client:
    resp = client.get('/foo')
    # the teardown functions are still not called at that point
    # even though the response ended and you have the response
    # object in your hand

# only when the code reaches this point the teardown functions
# are called.  Alternatively the same thing happens if another
# request was triggered from the test client

В этом можно убедиться, воспользовавшись командной строкой:

>>> app = Flask(__name__)
>>> @app.teardown_request
... def teardown_request(exception=None):
...     print 'this runs after request'
...
>>> ctx = app.test_request_context()
>>> ctx.push()
>>> ctx.pop()
this runs after request
>>>

Учтите, что функции обратного вызова выполняются всегда, независимо от того, были ли выполнены функции обратного вызова before_request() и произошло ли исключение. Некоторые части системы тестирования могут также создавать временный контекст без вызова обработчиков before_request(). Убедитесь, что ваши обработчики teardown_request() в таких случаях никогда не приводят к ошибкам.

Замечания о посредниках

Некоторые из объектов, предоставляемых Flask, являются посредниками к другим объектам. Причина в том, что эти посредники являются общими для потоков и они скрыто передают объект для обработки соответствующему потоку.

В большинстве случаев об этом не стоит беспокоиться, но существует несколько исключительных случаев, когда хорошо знать, что объект на самом деле является посредником:

  • Объекты-посредники не подделывают наследуемые типы, поэтому если понадобится провести проверки над реальным экземпляром объекта, это можно сделать это над экземпляром, который доступен через посредника (см. ниже _get_current_object).
  • Если ссылка на объект имеет значение (например, для отправки Сигналы).

Если нужно получить доступ к объекту, доступному через посредника, можно воспользоваться методом _get_current_object():

app = current_app._get_current_object()
my_signal.send(app)

Защита контекста при ошибках

Случилась ли ошибка или нет, в конце обработки запроса контекст запроса извлекается из стека и все связанные с ним данные удаляются. Однако, во время разработки это может стать проблемой, потому что может потребоваться извлечь информацию из запроса, если произошло исключение. Во Flask 0.6 и более ранних версиях в режиме отладки, если произошло исключение, контекст запроса не извлекается, так что средство интерактивной отладки всё ещё может предоставить вам необходимую информацию.

Начиная со Flask 0.7 имеется возможность управлять этим поведением при помощи настройки параметра конфигурации PRESERVE_CONTEXT_ON_EXCEPTION. По умолчанию он связан с настройкой DEBUG. Если приложение находится в отладочном режиме, то контекст защищается, а если в рабочем, то - нет.

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

Оригинал этой страницы