请求上下文 — Flask 文档

来自菜鸟教程
Flask/docs/1.1.x/reqcontext
跳转至:导航、​搜索

请求上下文

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

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

上下文的目的

Flask 应用程序处理一个请求时,它会根据它从 WSGI 服务器接收到的环境创建一个 Request 对象。 因为一个 worker(线程、进程或协程取决于服务器)一次只处理一个请求,所以在该请求期间,请求数据可以被认为是该 worker 的全局数据。 Flask 为此使用术语 context local

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


上下文的生命周期

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

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

上下文局部变量在 Werkzeug 中实现。 请参阅 werkzeug:local 以获取有关其内部工作原理的更多信息。


手动推送上下文

如果您尝试在请求上下文之外访问 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', data={'format': 'short'}):
    generate_report()

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

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


上下文如何工作

调用 Flask.wsgi_app() 方法来处理每个请求。 它在请求期间管理上下文。 在内部,请求和应用程序上下文作为堆栈工作,_request_ctx_stack_app_ctx_stack。 当上下文被压入堆栈时,依赖于它们的代理可用并指向堆栈顶部上下文的信息。

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

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

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


回调和错误

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

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

  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

信号

如果 signals_available 为真,则发送以下信号:

  1. request_started 在调用 before_request() 函数之前发送。
  2. request_finished 在调用 after_request() 函数后发送。
  3. got_request_exception 在开始处理异常时发送,但在查找或调用 errorhandler() 之前。
  4. request_tearing_down 在调用 teardown_request() 函数后发送。


错误时的上下文保护

在请求结束时,请求上下文被弹出并销毁与其关联的所有数据。 如果在开发过程中发生错误,延迟销毁数据以进行调试是很有用的。

当开发服务器在开发模式下运行时(FLASK_ENV 环境变量设置为 'development'),错误和数据将被保留并显示在交互式调试器中。

这种行为可以通过 PRESERVE_CONTEXT_ON_EXCEPTION 配置来控制。 如上所述,在开发环境中默认为True

不要在生产中启用 PRESERVE_CONTEXT_ON_EXCEPTION,因为它会导致您的应用程序在异常时泄漏内存。


代理注意事项

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

大多数时候你不必关心这个,但有一些例外,知道这个对象实际上是一个代理:

  • 代理对象不能将它们的类型伪装成实际的对象类型。 如果要执行实例检查,则必须在被代理的对象上执行此操作。
  • 在某些情况下需要对代理对象的引用,例如发送 Signals 或将数据传递到后台线程。

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

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