在Flask中处理请求时,应用会生成一个“请求上下文”对象。整个请求的处理过程,都会在这个上下文对象中进行。这保证了请求的处理过程不被干扰。处理请求的具体代码如下:

def wsgi_app(self, environ, start_response):
with self.request_context(environ):
# with语句中生成一个`response`对象
...
return response(environ, start_response)

在Flask 0.9版本之前,应用只有“请求上下文”对象,它包含了和请求处理相关的信息。同时Flask还根据werkzeug.local模块中实现的一种数据结构LocalStack用来存储“请求上下文”对象。这在{% post_link 一个Flask应用运行过程剖析 一个Flask应用运行过程剖析 %}中有所介绍。在0.9版本中,Flask又引入了“应用上下文”的概念。本文主要Flask中的这两个“上下文”对象。

LocalStack

在介绍“请求上下文”和“应用上下文”之前,我们对LocalStack简要做一个回顾。在Werkzeug库——local模块一文中,我们讲解了werkzeug.local模块中实现的三个类LocalLocalStackLocalProxy。关于它们的概念和详细介绍,可以查看上面的文章。这里,我们用一个例子来说明Flask中使用的一种数据结构LocalStack

>>> from werkzeug.local import LocalStack
>>> import threading # 创建一个`LocalStack`对象
>>> local_stack = LocalStack()
# 查看local_stack中存储的信息
>>> local_stack._local.__storage__
{} # 定义一个函数,这个函数可以向`LocalStack`中添加数据
>>> def worker(i):
local_stack.push(i) # 使用3个线程运行函数`worker`
>>> for i in range(3):
t = threading.Thread(target=worker, args=(i,))
t.start() # 再次查看local_stack中存储的信息
>>> local_stack._local.__storage__
{<greenlet.greenlet at 0x4bee5a0>: {'stack': [2]},
<greenlet.greenlet at 0x4bee638>: {'stack': [1]},
<greenlet.greenlet at 0x4bee6d0>: {'stack': [0]}
}

由上面的例子可以看出,存储在LocalStack中的信息以字典的形式存在:键为线程/协程的标识数值,值也是字典形式。每当有一个线程/协程上要将一个对象pushLocalStack栈中,会形成如上一个“键-值”对。这样的一种结构很好地实现了线程/协程的隔离,每个线程/协程都会根据自己线程/协程的标识数值确定存储在栈结构中的值。

LocalStack还实现了pushpoptop等方法。其中top方法永远指向栈顶的元素。栈顶的元素是指当前线程/协程中最后被推入栈中的元素,即local_stack._local.stack[-1](注意,是stack键对应的对象中最后被推入的元素)。

请求上下文

Flask中所有的请求处理都在“请求上下文”中进行,在它设计之初便就有这个概念。由于0.9版本代码比较复杂,这里还是以0.1版本的代码为例进行说明。本质上这两个版本的“请求上下文”的运行原理没有变化,只是新版本增加了一些功能,这点在后面再进行解释。

请求上下文——0.1版本

# Flask v0.1
class _RequestContext(object):
"""The request context contains all request relevant information. It is
created at the beginning of the request and pushed to the
`_request_ctx_stack` and removed at the end of it. It will create the
URL adapter and request object for the WSGI environment provided.
""" def __init__(self, app, environ):
self.app = app
self.url_adapter = app.url_map.bind_to_environ(environ)
self.request = app.request_class(environ)
self.session = app.open_session(self.request)
self.g = _RequestGlobals()
self.flashes = None def __enter__(self):
_request_ctx_stack.push(self) def __exit__(self, exc_type, exc_value, tb):
# do not pop the request stack if we are in debug mode and an
# exception happened. This will allow the debugger to still
# access the request object in the interactive shell.
if tb is None or not self.app.debug:
_request_ctx_stack.pop()

由上面“请求上下文”的实现可知:

  • “请求上下文”是一个上下文对象,实现了__enter____exit__方法。可以使用with语句构造一个上下文环境。

  • 进入上下文环境时,_request_ctx_stack这个栈中会推入一个_RequestContext对象。这个栈结构就是上面讲的LocalStack栈。

  • 推入栈中的_RequestContext对象有一些属性,包含了请求的的所有相关信息。例如apprequestsessiongflashes。还有一个url_adapter,这个对象可以进行URL匹配。

  • with语句构造的上下文环境中可以进行请求处理。当退出上下文环境时,_request_ctx_stack这个栈会销毁刚才存储的上下文对象。

以上的运行逻辑使得请求的处理始终在一个上下文环境中,这保证了请求处理过程不被干扰,而且请求上下文对象保存在LocalStack栈中,也很好地实现了线程/协程的隔离。

以下是一个简单的例子:

# example - Flask v0.1
>>> from flask import Flask, _request_ctx_stack
>>> import threading
>>> app = Flask(__name__)
# 先观察_request_ctx_stack中包含的信息
>>> _request_ctx_stack._local.__storage__
{} # 创建一个函数,用于向栈中推入请求上下文
# 本例中不使用`with`语句
>>> def worker():
# 使用应用的test_request_context()方法创建请求上下文
request_context = app.test_request_context()
_request_ctx_stack.push(request_context) # 创建3个进程分别执行worker方法
>>> for i in range(3):
t = threading.Thread(target=worker)
t.start() # 再观察_request_ctx_stack中包含的信息
>>> _request_ctx_stack._local.__storage__
{<greenlet.greenlet at 0x5e45df0>: {'stack': [<flask._RequestContext at 0x710c668>]},
<greenlet.greenlet at 0x5e45e88>: {'stack': [<flask._RequestContext at 0x7107f28>]},
<greenlet.greenlet at 0x5e45f20>: {'stack': [<flask._RequestContext at 0x71077f0>]}
}

上面的结果显示:_request_ctx_stack中为每一个线程创建了一个“键-值”对,每一“键-值”对中包含一个请求上下文对象。如果使用with语句,在离开上下文环境时栈中销毁存储的上下文对象信息。

请求上下文——0.9版本

在0.9版本中,Flask引入了“应用上下文”的概念,这对“请求上下文”的实现有一定的改变。这个版本的“请求上下文”也是一个上下文对象。在使用with语句进入上下文环境后,_request_ctx_stack会存储这个上下文对象。不过与0.1版本相比,有以下几点改变:

  • 请求上下文实现了pushpop方法,这使得对于请求上下文的操作更加的灵活;

  • 伴随着请求上下文对象的生成并存储在栈结构中,Flask还会生成一个“应用上下文”对象,而且“应用上下文”对象也会存储在另一个栈结构中去。这是两个版本最大的不同。

我们先看一下0.9版本相关的代码:

# Flask v0.9
def push(self):
"""Binds the request context to the current context."""
top = _request_ctx_stack.top
if top is not None and top.preserved:
top.pop() # Before we push the request context we have to ensure that there
# is an application context.
app_ctx = _app_ctx_stack.top
if app_ctx is None or app_ctx.app != self.app:
app_ctx = self.app.app_context()
app_ctx.push()
self._implicit_app_ctx_stack.append(app_ctx)
else:
self._implicit_app_ctx_stack.append(None) _request_ctx_stack.push(self) self.session = self.app.open_session(self.request)
if self.session is None:
self.session = self.app.make_null_session()

我们注意到,0.9版本的“请求上下文”的pop方法中,当要将一个“请求上下文”推入_request_ctx_stack栈中的时候,会先检查另一个栈_app_ctx_stack的栈顶是否存在“应用上下文”对象或者栈顶的“应用上下文”对象的应用是否是当前应用。如果不存在或者不是当前对象,Flask会自动先生成一个“应用上下文”对象,并将其推入_app_ctx_stack中。

我们再看离开上下文时的相关代码:

# Flask v0.9
def pop(self, exc=None):
"""Pops the request context and unbinds it by doing that. This will
also trigger the execution of functions registered by the
:meth:`~flask.Flask.teardown_request` decorator. .. versionchanged:: 0.9
Added the `exc` argument.
"""
app_ctx = self._implicit_app_ctx_stack.pop() clear_request = False
if not self._implicit_app_ctx_stack:
self.preserved = False
if exc is None:
exc = sys.exc_info()[1]
self.app.do_teardown_request(exc)
clear_request = True rv = _request_ctx_stack.pop()
assert rv is self, 'Popped wrong request context. (%r instead of %r)' \
% (rv, self) # get rid of circular dependencies at the end of the request
# so that we don't require the GC to be active.
if clear_request:
rv.request.environ['werkzeug.request'] = None # Get rid of the app as well if necessary.
if app_ctx is not None:
app_ctx.pop(exc)

上面代码中的细节先不讨论。注意到当要离开以上“请求上下文”环境的时候,Flask会先将“请求上下文”对象从_request_ctx_stack栈中销毁,之后会根据实际的情况确定销毁“应用上下文”对象。

以下还是以一个简单的例子进行说明:

# example - Flask v0.9
# example - Flask v0.9
>>> from flask import Flask, _request_ctx_stack, _app_ctx_stack
>>> app = Flask(__name__) # 先检查两个栈的内容
>>> _request_ctx_stack._local.__storage__
{}
>>> _app_ctx_stack._local.__storage__
{} # 生成一个请求上下文对象
>>> request_context = app.test_request_context()
>>> request_context.push() # 请求上下文推入栈后,再次查看两个栈的内容
>>> _request_ctx_stack._local.__storage__
{<greenlet.greenlet at 0x6eb32a8>: {'stack': [<RequestContext 'http://localhost/' [GET] of __main__>]}}
>>> _app_ctx_stack._local.__storage__
{<greenlet.greenlet at 0x6eb32a8>: {'stack': [<flask.ctx.AppContext at 0x5c96a58>]}} >>> request_context.pop() # 销毁请求上下文时,再次查看两个栈的内容
>>> _request_ctx_stack._local.__storage__
{}
>>> _app_ctx_stack._local.__storage__
{}

应用上下文

上部分中简单介绍了“应用上下文”和“请求上下文”的关系。那什么是“应用上下文”呢?我们先看一下它的类:

class AppContext(object):
"""The application context binds an application object implicitly
to the current thread or greenlet, similar to how the
:class:`RequestContext` binds request information. The application
context is also implicitly created if a request context is created
but the application is not on top of the individual application
context.
""" def __init__(self, app):
self.app = app
self.url_adapter = app.create_url_adapter(None) # Like request context, app contexts can be pushed multiple times
# but there a basic "refcount" is enough to track them.
self._refcnt = 0 def push(self):
"""Binds the app context to the current context."""
self._refcnt += 1
_app_ctx_stack.push(self) def pop(self, exc=None):
"""Pops the app context."""
self._refcnt -= 1
if self._refcnt <= 0:
if exc is None:
exc = sys.exc_info()[1]
self.app.do_teardown_appcontext(exc)
rv = _app_ctx_stack.pop()
assert rv is self, 'Popped wrong app context. (%r instead of %r)' \
% (rv, self) def __enter__(self):
self.push()
return self def __exit__(self, exc_type, exc_value, tb):
self.pop(exc_value)

由以上代码可以看出:“应用上下文”也是一个上下文对象,可以使用with语句构造一个上下文环境,它也实现了pushpop等方法。“应用上下文”的构造函数也和“请求上下文”类似,都有appurl_adapter等属性。“应用上下文”存在的一个主要功能就是确定请求所在的应用。

然而,以上的论述却又让人产生这样的疑问:既然“请求上下文”中也包含app等和当前应用相关的信息,那么只要调用_request_ctx_stack.top.app或者魔法current_app就可以确定请求所在的应用了,那为什么还需要“应用上下文”对象呢?对于单应用单请求来说,使用“请求上下文”确实就可以了。然而,Flask的设计理念之一就是多应用的支持。当在一个应用的请求上下文环境中,需要嵌套处理另一个应用的相关操作时,“请求上下文”显然就不能很好地解决问题了。如何让请求找到“正确”的应用呢?我们可能会想到,可以再增加一个请求上下文环境,并将其推入_request_ctx_stack栈中。由于两个上下文环境的运行是独立的,不会相互干扰,所以通过调用_request_ctx_stack.top.app或者魔法current_app也可以获得当前上下文环境正在处理哪个应用。这种办法在一定程度上可行,但是如果对于第二个应用的处理不涉及到相关请求,那也就无从谈起“请求上下文”。

为了应对这个问题,Flask中将应用相关的信息单独拿出来,形成一个“应用上下文”对象。这个对象可以和“请求上下文”一起使用,也可以单独拿出来使用。不过有一点需要注意的是:在创建“请求上下文”时一定要创建一个“应用上下文”对象。有了“应用上下文”对象,便可以很容易地确定当前处理哪个应用,这就是魔法current_app。在0.1版本中,current_app是对_request_ctx_stack.top.app的引用,而在0.9版本中current_app是对_app_ctx_stack.top.app的引用。

下面以一个多应用的例子进行说明:

# example - Flask v0.9
>>> from flask import Flask, _request_ctx_stack, _app_ctx_stack
# 创建两个Flask应用
>>> app = Flask(__name__)
>>> app2 = Flask(__name__)
# 先查看两个栈中的内容
>>> _request_ctx_stack._local.__storage__
{}
>>> _app_ctx_stack._local.__storage__
{}
# 构建一个app的请求上下文环境,在这个环境中运行app2的相关操作
>>> with app.test_request_context():
print "Enter app's Request Context:"
print _request_ctx_stack._local.__storage__
print _app_ctx_stack._local.__storage__
print
with app2.app_context():
print "Enter app2's App Context:"
print _request_ctx_stack._local.__storage__
print _app_ctx_stack._local.__storage__
print
# do something
print "Exit app2's App Context:"
print _request_ctx_stack._local.__storage__
print _app_ctx_stack._local.__storage__
print
# Result
Enter app's Request Context:
{<greenlet.greenlet object at 0x000000000727A178>: {'stack': [<RequestContext 'http://localhost/' [GET] of __main__>]}}
{<greenlet.greenlet object at 0x000000000727A178>: {'stack': [<flask.ctx.AppContext object at 0x0000000005DD0DD8>]}} Enter app2's App Context:
{<greenlet.greenlet object at 0x000000000727A178>: {'stack': [<RequestContext 'http://localhost/' [GET] of __main__>]}}
{<greenlet.greenlet object at 0x000000000727A178>: {'stack': [<flask.ctx.AppContext object at 0x0000000005DD0DD8>, <flask.ctx.AppContext object at 0x0000000007313198>]}} Exit app2's App Context
{<greenlet.greenlet object at 0x000000000727A178>: {'stack': [<RequestContext 'http://localhost/' [GET] of __main__>]}}
{<greenlet.greenlet object at 0x000000000727A178>: {'stack': [<flask.ctx.AppContext object at 0x0000000005DD0DD8>]}}

在以上的例子中:

  • 我们首先创建了两个Flask应用appapp2

  • 接着我们构建了一个app的请求上下文环境。当进入这个环境中时,这时查看两个栈的内容,发现两个栈中已经有了当前请求的请求上下文对象和应用上下文对象。并且栈顶的元素都是app的请求上下文和应用上下文;

  • 之后,我们再在这个环境中嵌套app2的应用上下文。当进入app2的应用上下文环境时,两个上下文环境便隔离开来,此时再查看两个栈的内容,发现_app_ctx_stack中推入了app2的应用上下文对象,并且栈顶指向它。这时在app2的应用上下文环境中,current_app便会一直指向app2

  • 当离开app2的应用上下文环境,_app_ctx_stack栈便会销毁app2的应用上下文对象。这时查看两个栈的内容,发现两个栈中只有app的请求的请求上下文对象和应用上下文对象。

  • 最后,离开app的请求上下文环境后,两个栈便会销毁app的请求的请求上下文对象和应用上下文对象,栈为空。

与上下文对象有关的“全局变量”

在Flask中,为了更加方便地处理一些变量,特地提出了“全局变量”的概念。这些全局变量有:

# Flask v0.9
_request_ctx_stack = LocalStack()
_app_ctx_stack = LocalStack()
current_app = LocalProxy(_find_app)
request = LocalProxy(partial(_lookup_object, 'request'))
session = LocalProxy(partial(_lookup_object, 'session'))
g = LocalProxy(partial(_lookup_object, 'g')) # 辅助函数
def _lookup_object(name):
top = _request_ctx_stack.top
if top is None:
raise RuntimeError('working outside of request context')
return getattr(top, name) def _find_app():
top = _app_ctx_stack.top
if top is None:
raise RuntimeError('working outside of application context')
return top.app

可以看出,Flask中使用的一些“全局变量”,包括current_apprequestsessiong等都来自于上下文对象。其中current_app一直指向_app_ctx_stack栈顶的“应用上下文”对象,是对当前应用的引用。而requestsessiong等一直指向_request_ctx_stack栈顶的“请求上下文”对象,分别引用请求上下文的requestsessiong。不过,从 Flask 0.10 起,对象 g 存储在应用上下文中而不再是请求上下文中。

另外一个问题,在形成这些“全局变量”的时候,使用了werkzeug.local模块的LocalProxy类。之所以要用该类,主要是为了动态地实现对栈顶元素的引用。如果不使用这个类,在生成上述“全局变量”的时候,它们因为指向栈顶元素,而栈顶元素此时为None,所以这些变量也会被设置为None常量。后续即使有上下文对象被推入栈中,相应的“全局变量”也不会发生改变。为了动态地实现对栈顶元素的引用,这里必须使用werkzeug.local模块的LocalProxy类。

https://segmentfault.com/a/1190000009152655

https://segmentfault.com/a/1190000004223296

Flask中的请求上下文和应用上下文的更多相关文章

  1. Flask基础(12)-->请求上下文和应用上下文

    请求上下文和应用上下文 请求上下文:可以简单理解为客户端与服务器之间数据交互请求的容器 请求上下文对象有:request.Session request:封装了HTTP请求的内容,针对的是http的请 ...

  2. flask中的上下文_请求上下文和应用上下文

    前引 在了解flask上下文管理机制之前,先来一波必知必会的知识点. 面向对象双下方法 首先,先来聊一聊面向对象中的一些特殊的双下划线方法,比如__call__.__getattr__系列.__get ...

  3. flask 源码专题(三):请求上下文和应用上下文入栈与出栈

    1.请求上下文和应用上下文入栈 # 将ctx入栈,但是内部也将应用上下文入栈 ctx.push() def push(self): # 获取到的 top == ctx top = _request_c ...

  4. Flask中的ThreadLocal本地线程,上下文管理

    先说一下和flask没有关系的: 我们都知道线程是由进程创建出来的,CPU实际执行的也是线程,那么线程其实是没有自己独有的内存空间的,所有的线程共享进程的资源和空间,共享就会有冲突,对于多线程对同一块 ...

  5. (三)flask中的请求钩子函数

    请求勾子 在客户端和服务器交互的过程中,有些准备工作或扫尾工作需要处理,比如: 在请求开始时,建立数据库连接: 在请求开始时,根据需求进行权限校验: 在请求结束时,指定数据的交互格式: 为了让每个视图 ...

  6. Flask框架中特有的变量/函数及上下文

    模板中特有的变量和函数 你可以在自己的模板中访问一些 Flask 默认内置的函数和对象 config 你可以从模板中直接访问Flask当前的config对象: {{config.SQLALCHEMY_ ...

  7. 六十八:flask上下文之app上下文和request上下文

    app上下文: 先看现象 current_app源码 手动入栈 app_context()源码 with语句入栈 request上下文 不在app上下文中 即使手动入栈也会报错,不在请求上下文中 ur ...

  8. Flask中current_app和g对象

      Flask零基础到项目实战(七)请求方法.g对象和钩子函数 一.get方法 二.post方法 post请求在模板中要注意几点: input标签中,要写name来标识这个value的key,方便后台 ...

  9. Flask中本地栈的使用

    4种上下文变量 承接上一篇内容.当一个请求到来时,除了request被封装成全局变量之外,还有三个变量也是同样被封装成全局变量,那就是current_app.g.session.上面4个变量之所以能够 ...

随机推荐

  1. 打印n对括号的全部有效组合(左右括号正确匹配)Java实现

    一.问题导出 今天在做算法题目的时候遇到了一道左右括号匹配的问题,问题如下: 二.问题思考 (1)这种要列出全部可能性的题目很容易想到由小到大去发现规律,用递归或者暴力搜索. 首先1的情况,一个括号只 ...

  2. Python3.5环境安装及使用 Speech问题解决(转)

    修改speech.py line59 修改import thread,改成import threading line157 修改print prompt,改成print(prompt) 对最后的函数_ ...

  3. NUC970 Linux CAN 驱动问题及解决办法之二

    开发平台介绍: NUC970 + 内置CAN控制器(双通道CAN1\CAN2) + 官方Linux_Kernel(少量修改) 名词: 终端,使用NUC970的硬件 异常表现: 1.当CAN收发器(VP ...

  4. Java中各种对象(PO,BO,VO,DTO,POJO,DAO,Entity,JavaBean,JavaBeans)的区分

    PO:持久对象 (persistent object),po(persistent object)就是在Object/Relation Mapping框架中的Entity,po的每个属性基本上都对应数 ...

  5. 三种方式构建C#单例模式

    /// <summary> /// 双检锁实现单例 /// </summary> public sealed class SingletonDoubleCheck { //s_ ...

  6. websocket链接报错 ERR_CONNECTION_TIME_OUT

    每次打开页面都会报这个错误,链接超时,之前一直是可以的,查看日志之后发现链接数据库失败,修改启动配置文件,修改数据库配置信息,我准备数据库配置信息写死了,然后启动.解决!!!!

  7. python3:iterable, iterator, generator,抽象基类, itertools的使用。

    目录: iterable对象 iterator对象, 数据类型Iterator类 数据类型Generator类. 生成器表达式 collections.abc:容器的抽象基类.用于判断具体类. ite ...

  8. python之global用法

    如果需要在函数内部改变函数外部的变量,就可以通过在函数内部声明变量为global变量.这样当程序运行至global变量便会替换外部的同名变量. 例1: # -*- coding:utf-8 -*- n ...

  9. python_函数作用域

    py文件:全局作用域 函数:局部作用域 一个函数是一个作用域 def func(): x = 9 print(x) func() print(x) 作用域中查找数据规则:优先在自己的作用域找数据,自己 ...

  10. docker化hbase并使用外部zookeeper

    近日公司需要单节点hbase,并不使用内置zookeeper,我们的zookeeper是单节点的docker化,hbase也要做单节点的docker化,于是以下为自写的Dockerfile hbase ...