源码剖析.Python.深入源码剖析Flask程序请求上下文?

孤者浪人 提交于 2020-02-28 13:38:21

上下文的分类

1.Flask上下文分为application context和request context,即应用上下文和请求上下文,这两者都处于同一请求的局部中

上下文类的定义

/usr/lib/Python27/lib/site-packages/flask/ctx.py

class AppContext(object):
    def __init__(self, app):
        self.app = app
        self.url_adapter = app.create_url_adapter(None)
        self.g = app.app_ctx_globals_class()

        # Like request context, app contexts can be pushed multiple times
        # but there a basic "refcount" is enough to track them.
        self._refcnt = 0

说明:AppContext类即为程序上下文,内部保存几个变量,app为当前程序实例,g用来保存每个请求中需要用到的请求内全局变量

/usr/lib/Python27/lib/site-packages/flask/ctx.py

class RequestContext(object):
    def __init__(self, app, environ, request=None):
        self.app = app
        if request is None:
            request = app.request_class(environ)
        self.request = request
        self.url_adapter = app.create_url_adapter(self.request)
        self.flashes = None
        self.session = None

        # Request contexts can be pushed multiple times and interleaved with
        # other request contexts.  Now only if the last level is popped we
        # get rid of them.  Additionally if an application context is missing
        # one is created implicitly so for each level we add this information
        self._implicit_app_ctx_stack = []

说明:RequestContext即请求上下文,内部保存着几个变量,app为当前程序实例,request为请求对象,session为会话对象

上下文对象的作用域

1.Flask是多线程,线程有个叫ThreadLocal的类,用于实现线程隔离的类,而Werkzeug自己实现了它的线程隔离类,Werkzeug.local.Local,LocalStack就是Local实现的

/usr/lib/Python27/lib/site-packages/flask/globals.py

_request_ctx_stack = LocalStack()
_app_ctx_stack = LocalStack()

说明:LocalStack是Flask定义的线程隔离的栈(先进后出)存储对象,分别用来存储程序上下文和请求上下文,对于不同的线程,它们访问这两个对象看到的结果是不一样的,完全隔离,每个传给Flask对象的请求,都在不同的线程中处理,而且同一时刻每个线程只处理一个请求,所以对于每个请求来说,它们完全不用担心自己上下文中的数据被别的请求所修改

问题:Flask中的g,session,request,current_app是怎么做到同一个对象能在所有请求中使用而不会冲突哪?

current_app = LocalProxy(_find_app)
request = LocalProxy(partial(_lookup_req_object, 'request'))
session = LocalProxy(partial(_lookup_req_object, 'session'))
g = LocalProxy(partial(_lookup_app_object, 'g'))

说明:LocalProxy类的构造函数接受一个callable参数,如上传递一个偏函数,以g为例,当对g进行操作时,就会调用作为参数的偏函数,并发操作转换到偏函数返回的对象上

def _lookup_req_object(name):
    top = _request_ctx_stack.top
    if top is None:
        raise RuntimeError('working outside of request context')
    return getattr(top, name)


def _lookup_app_object(name):
    top = _app_ctx_stack.top
    if top is None:
        raise RuntimeError('working outside of application 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

说明:_app_ctx_stack和request_ctx_stack都是线程隔离的,所以流程是这样,访问g->从当前线程的程序上下文栈顶获取程序上下文->取出其中的g对象->进行操作,所以可以通过一个g对象让所有线程互不干扰的访问自己的g

上下文对象的推送

def push(self):    
    top = _request_ctx_stack.top
    if top is not None and top.preserved:
        top.pop(top._preserved_exc)

    # 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)

说明:构建Flask对象后并不会推送上下文,而在Flask对象调用run()作为WSGI应用启动后,每当有请求进入时,推送请求上下文之前,首先会检查栈顶如已存在应用上下文,如果存在,判断与请求上下文是否属于同一个应用,如果不存在就会推送一个当前应用的上下文,如果是单WSGI应用的程序,此判断毫无意义

def pop(self, exc=None):  
    app_ctx = self._implicit_app_ctx_stack.pop()
         ………………       
      ………………
    rv = _request_ctx_stack.pop()    
    # Get rid of the app as well if necessary.
    if app_ctx is not None:
        app_ctx.pop(exc)

说明:请求结束时,调用auto_pop函数,其中又调用自身pop函数,会把请求上下文和程序上下文都pop掉,所以可以得出结论,在单WSGI应用环境下,每个请求独立于线程上曾经的请求,独立与其它线上的请求

app_ctx = app.app_context()
try:
    # 说明: 尝试从LocalStack获取当前线程对应的程序上下文current_app,发现为空抛出运行时错误
    print current_app.name
except RuntimeError as e:
    # 说明: 程序上下文没有压入LocalStack栈
    print 'found errors: %s' % (e)
    # 说明: 压入程序上下文到栈
    app_ctx.push()
    # 说明: 因为上面压入了栈所以可以访问到
    print current_app.name

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!