请求上下文

请求上下文在请求期间跟踪请求级别的数据。与将请求对象传递给在请求期间运行的每个函数不同,而是访问 requestsession 代理。

这类似于 应用上下文,应用上下文独立于请求跟踪应用级别的数据。当推送请求上下文时,也会推送相应的应用上下文。

上下文的目的

Flask 应用处理请求时,它会基于从 WSGI 服务器接收的环境创建一个 Request 对象。由于一个工作进程(线程、进程或协程,取决于服务器)一次只处理一个请求,因此在请求期间,请求数据可以被认为是该工作进程的全局数据。Flask 使用术语上下文局部来表示这一点。

Flask 在处理请求时会自动推送请求上下文。在请求期间运行的视图函数、错误处理程序和其他函数将可以访问 request 代理,该代理指向当前请求的请求对象。

上下文的生命周期

当 Flask 应用开始处理请求时,它会推送一个请求上下文,这也会推送一个 应用上下文。当请求结束时,它会弹出请求上下文,然后弹出应用上下文。

上下文对于每个线程(或其他工作进程类型)都是唯一的。request 不能传递给另一个线程,另一个线程有不同的上下文空间,并且不知道父线程指向的请求。

上下文局部变量是使用 Python 的 contextvars 和 Werkzeug 的 LocalProxy 实现的。Python 自动管理上下文变量的生命周期,而本地代理封装了底层接口,使数据更易于使用。

手动推送上下文

如果您尝试在请求上下文之外访问 request 或任何使用它的东西,您将收到此错误消息

RuntimeError: Working outside of request context.

This typically means that you attempted to use functionality that
needed an active HTTP request. Consult the documentation on testing
for information about how to avoid this problem.

这通常只应在测试期望活动请求的代码时发生。一种选择是使用 test client 来模拟完整请求。或者您可以使用 test_request_context()with 代码块中,并且在该代码块中运行的所有内容都将可以访问 request,并使用您的测试数据填充。

def generate_report(year):
    format = request.args.get("format")
    ...

with app.test_request_context(
    "/make_report/2017", query_string={"format": "short"}
):
    generate_report()

如果您在代码中其他与测试无关的地方看到该错误,则很可能表明您应该将该代码移至视图函数中。

有关如何从交互式 Python shell 中使用请求上下文的信息,请参阅 使用 Shell

上下文如何工作

调用 Flask.wsgi_app() 方法来处理每个请求。它在请求期间管理上下文。在内部,请求和应用上下文的工作方式类似于堆栈。当上下文被推送时,依赖于它们的代理可用,并指向来自顶部项目的信息。

当请求开始时,将创建并推送 RequestContext,如果该应用的上下文还不是顶部上下文,则它首先创建并推送 AppContext。当这些上下文被推送时,current_appgrequestsession 代理可用于处理请求的原始线程。

可以推送其他上下文以在请求期间更改代理。虽然这不是常见的模式,但它可以在高级应用程序中使用,例如,执行内部重定向或将不同的应用程序链接在一起。

在调度请求并生成和发送响应后,请求上下文被弹出,然后应用上下文被弹出。在它们被弹出之前,会立即执行 teardown_request()teardown_appcontext() 函数。即使在调度期间发生未处理的异常,这些函数也会执行。

回调和错误

Flask 在多个阶段调度请求,这些阶段可能会影响请求、响应以及错误的处理方式。上下文在所有这些阶段都处于活动状态。

Blueprint 可以为这些事件添加处理程序,这些处理程序特定于蓝图。如果蓝图拥有与请求匹配的路由,则将运行蓝图的处理程序。

  1. 在每个请求之前,都会调用 before_request() 函数。如果其中一个函数返回值,则会跳过其他函数。返回值被视为响应,并且不会调用视图函数。

  2. 如果 before_request() 函数没有返回响应,则调用匹配路由的视图函数并返回响应。

  3. 视图的返回值将转换为实际的响应对象,并传递给 after_request() 函数。每个函数都返回修改后的或新的响应对象。

  4. 在返回响应后,上下文被弹出,这将调用 teardown_request()teardown_appcontext() 函数。即使在上面的任何时候引发了未处理的异常,也会调用这些函数。

如果在 teardown 函数之前引发异常,Flask 会尝试将其与 errorhandler() 函数匹配,以处理异常并返回响应。如果未找到错误处理程序,或者处理程序本身引发异常,则 Flask 返回通用的 500 Internal Server Error 响应。teardown 函数仍然会被调用,并传递异常对象。

如果启用了调试模式,则未处理的异常不会转换为 500 响应,而是会传播到 WSGI 服务器。这允许开发服务器使用回溯呈现交互式调试器。

Teardown 回调

Teardown 回调独立于请求调度,而是由上下文在弹出时调用。即使在调度期间和手动推送的上下文中存在未处理的异常,也会调用这些函数。这意味着不能保证请求调度的任何其他部分首先运行。请务必以不依赖于其他回调且不会失败的方式编写这些函数。

在测试期间,在请求结束后延迟弹出上下文可能很有用,以便可以在测试函数中访问其数据。使用 test_client() 作为 with 代码块以保留上下文,直到 with 代码块退出。

from flask import Flask, request

app = Flask(__name__)

@app.route('/')
def hello():
    print('during view')
    return 'Hello, World!'

@app.teardown_request
def show_teardown(exception):
    print('after with block')

with app.test_request_context():
    print('during with block')

# teardown functions are called after the context with block exits

with app.test_client() as client:
    client.get('/')
    # the contexts are not popped even though the request ended
    print(request.path)

# the contexts are popped and teardown functions are called after
# the client with block exits

信号

发送以下信号

  1. request_started 在调用 before_request() 函数之前发送。

  2. request_finished 在调用 after_request() 函数之后发送。

  3. got_request_exception 在开始处理异常时发送,但在查找或调用 errorhandler() 之前发送。

  4. request_tearing_down 在调用 teardown_request() 函数之后发送。

关于代理的说明

Flask 提供的一些对象是其他对象的代理。对于每个工作线程,代理的访问方式相同,但指向绑定到每个工作线程的唯一对象,如本页所述。

大多数时候您不必关心这一点,但在某些情况下,最好知道此对象实际上是一个代理

  • 代理对象无法伪造其类型作为实际对象类型。如果要执行实例检查,则必须对被代理的对象执行检查。

  • 在某些情况下,需要引用代理对象,例如发送 信号 或将数据传递到后台线程。

如果您需要访问被代理的底层对象,请使用 _get_current_object() 方法

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