源碼剖析.Python.深刻源碼剖析Flask程序請求上下文?

上下文的分類

1.Flask上下文分爲application context和request context,即應用上下文和請求上下文,這二者都處於同一請求的局部中python

上下文類的定義

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

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用來保存每一個請求中須要用到的請求內全局變量session

/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實現的app

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

_request_ctx_stack = LocalStack()
_app_ctx_stack = LocalStack()

說明:LocalStack是Flask定義的線程隔離的棧(先進後出)存儲對象,分別用來存儲程序上下文和請求上下文,對於不一樣的線程,它們訪問這兩個對象看到的結果是不同的,徹底隔離,每一個傳給Flask對象的請求,都在不一樣的線程中處理,並且同一時刻每一個線程只處理一個請求,因此對於每一個請求來講,它們徹底不用擔憂本身上下文中的數據被別的請求所修改函數

問題:Flask中的g,session,request,current_app是怎麼作到同一個對象能在全部請求中使用而不會衝突哪?this

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進行操做時,就會調用做爲參數的偏函數,併發操做轉換到偏函數返回的對象上url

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
相關文章
相關標籤/搜索