请求上下文

请求上下文在请求期间跟踪请求级别的数据。它不是将请求对象传递给请求期间运行的每个函数,而是访问 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 来模拟完整请求。或者,您可以在 with 块中使用 test_request_context(),在该块中运行的所有内容都将能够访问 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 以多个阶段分派请求,这些阶段会影响请求、响应以及错误的处理方式。上下文在所有这些阶段都处于活动状态。

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

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

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

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

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

如果在终止函数之前引发了异常,Flask 会尝试使用 errorhandler() 函数与之匹配以处理异常并返回响应。如果找不到错误处理程序,或者处理程序本身引发了异常,Flask 将返回一个通用的 500 Internal Server Error 响应。仍然会调用终止函数,并将异常对象传递给它们。

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

终止回调

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

在测试期间,推迟在请求结束后弹出上下文可能很有用,以便可以在测试函数中访问其数据。使用 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)