Flask1.0.2系列(十四) 请求上下文

英文原文地址:http://flask.pocoo.org/docs/1.0/reqcontext/

若有翻译错误或者不尽人意之处,请指出,谢谢~


        在一个请求期间,请求上下文保持追踪了请求等级的数据。相较于在一个请求期间为每个方法传递一个请求对象的做法,使用requestsession两个代理更加简便。

        请求上下文与应用程序上下文很类似,都在在一个独立的请求内保持追踪应用程序等级的数据。当一个请求上下文被推送的时候,也会推送一个相应的应用程序上下文。


1. 上下文的目的

        当Flask应用程序处理一个请求时,它会基于WSGI服务接收到的环境来创建了一个Request对象。因为一个工作者(线程,进程,或者依赖于服务的协程)处理在同一时刻只处理一个请求,而在这个请求期间,请求数据会被工作者认为是全局的。Flask请求数据使用了一个术语——局部上下文(context local)。

        当处理一个请求时,Flask会自动推送(pushes)一个请求上下文。视图方法,错误处理程序,以及其他在一个请求期间运行的方法,都能访问这个request代理,这个代理指向了当前请求的请求对象。


2. 上下文的生命周期

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

        对于每个线程(或者其他类型的工作者)来说,上下文都是唯一的。request不能被传递到其他线程,其他线程会有一个不同的上下文堆,并且它们并不知道福线程指向的请求是什么。

        局部上下文是在Werkzeug中实现的。你可以在局部上下文一文中获取更多关于它内部是如何工作的信息。


3. 手动推送一个上下文

        如果你在一个请求上下文之外尝试访问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中怎么使用请求上下文,后面会讲解。


4. 上下文如何工作的

        调用Flask.wsgi_app()方法来处理每个请求。它在请求期间管理了上下文。在内部,请求和应用程序上下文就像堆栈一样工作,_request_ctx_stack_app_ctx_stack。当上下文被推送到堆栈上,依赖于上下文的代理是可用的,并且指向堆栈顶部的上下文信息。

        当请求开始时,一个RequestContext被创建并且被推送,如果应用程序的一个上下文还不是最顶部的上下文,那么它将先创建和推送一个AppContext。在这些上下文被推送时,current_appgrequest以及session代理,对于处理这个请求的原始线程来说都是可用的。

        因为上下文是堆栈,所以在一个请求期间,其他上下文可能会被被推送,以改变代理。虽然这不是一个常用模式,但是它可以被用在高级应用程序中,举个栗子,实现内部重定向或者将不同的应用程序链接到一起。

        在请求被发出且生成的一个响应也被发出后,请求上下文被取出,随后应用程序上下文也被取出。在它们被取出之前,teardown_request()teardown_appcontext()方法会被执行。即使在发送期间出现一个未处理的异常,这两个方法依然会执行。


5. 回调和错误

        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服务器。这允许开发服务使用traceback来在交互式调试器中呈现异常。


6. Teardown回调

        teardown回调是独立于请求分发的,并且是在上下文被取出时,由上下文调用的。即使再分发期间有一个未处理的异常以及手动推送上下文时,teardown回调都会被调用。这意味着,我们不能保证请求分发的任何其他部分会先执行。确保不要依赖于其他回调来编写这些函数,并且这些函数执行不能失败。

        在测试期间,推迟取出上下文到请求结束之后是很有用的,因此它们的数据能在测试方法中进行访问。在with块中使用test_client()来保持上下文,一直到块结束。

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():
    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 exists


7. 信号

        如果signals_available为true,下面的信号会被发送:

        1. request_started在“before_request()方法被调用”之前发送。

        2. request_finished在“after_request()方法被调用”之后发送。

        3. got_request_exception在一个“异常开始被处理,但是在一个errorhandler()被查找或者被调用”的时候调用。

        4. request_tearing_down在“teardown_request()方法被调用”之后发送。


8. 上下文保留错误

        在一个请求的最后,请求上下文被取出,并且所有关联它的数据都会被销毁。如果在开发期间出现一个错误,为了调试目的,将数据延迟销毁是很有用的。

        当开发服务器运行在开发模式(FLASK_ENV环境变量被设置为‘development’)时,错误和数据会被保存,并且显示在交互式调试器上。

        这个行为可以使用PRESERVE_CONTEXT_ON_EXCEPTION配置项来控制。正如描述的那样,在开发环境中这个配置项的默认值为True。

        不要在生产中激活PRESERVE_CONTEXT_ON_EXCEPTION配置项,因为它会导致你的应用程序在异常情况下泄漏内存。


9. 关于代理

        Flask提供的某些对象是其他对象的代理。在每个工作者线程中访问代理的方式相同,但代理指向的唯一对象,是在这也描述中与幕后每个工作者绑定的。

        大多时候,你不用关心这个,但是这里有些异常,你最好知道这个对象是一个实际的代理:

        ● 代理对象不能伪造它们的类型作为实际的对象类型。如果你希望执行实例检查,你必须在被代理的对象上(而不是代理上)做这事。

        ● 如果特定对象引用是很重要的,举个栗子,发送信号或者传递数据到一个后台线程,你需要传递被代理的对象(而不是代理)。

        如果你需要访问被代理的底层对象,使用_get_current_object()函数:

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

猜你喜欢

转载自blog.csdn.net/regandu/article/details/80239543
今日推荐