flask 上下文管理 &源碼剖析

基本流程概述django

 

複製代碼
- 與django相比是兩種不一樣的實現方式。
    - django/tornado是經過傳參數形式實現
    - 而flask是經過上下文管理, 兩種均可以實現,只不實現的方式不同罷了。
    - 上下文管理:
        - 說上下文管理前要先提一下threadinglocal,它爲每個線程開闢一塊獨立的空間,可是Flask不是用它作得,它本身實現類一個local類
        - 其中建立了一個字典來保存數據,這個字典的key是用線程的惟一標識,若是有協程用greelet拿的一個惟一標識,能夠是線程的也能夠支持協程,後面是存取的數據{greenlet作惟一標識:存數據} 這樣能夠保證數據隔離
        - 當請求進來的時候:
            - 將請求相關全部數據(request,session)封裝到了RequestContext中。
            - 再將RequestContext對象經過LocalStack類添加到Local中(調用push方法)
        - 使用時,調用request
            - 調用此類方法 request.method、print(request)、request+xxx 會執行LocalProxy中對應的魔法方法
            - 魔法方法中調用_get_current_object函數
            - 經過LocalStack中的top方法,去Local中獲取值,取的是列表的最後一個值。
        - 請求終止時
            - 仍是經過LocalStack的pop方法 將Local中將值在列表中pop掉。
複製代碼

 

複製代碼
內置session流程

當請求剛進來的時候,會把請求相關的和session封裝到RequestContext的對象中去,RequestCcontext對像再經過它裏面對push方法把對象放到Flask特有的,相似與theadingLocal那麼一個Local對象中去,
push中會調用session裏面open_session方法,經過這個方法幫助咱們獲取用戶原有的session信息,有就獲取,沒有就返回一個空的相似字典的數據結構,賦值給對象中的session 當使用的時候觸發LocalProxy對像裏對魔法方法,再調用get_current_obj,經過偏函數用Localstark中方法去Local獲取到數據 使用完後,調用session對像的save_session方法,將數據加密寫到用戶的cookie中,這個操做在after_request以後
複製代碼

 

 

 

 request 與 sessionflask

flask中要想調用當前請求的request對象,須要使用from flask import reuqest導入。當咱們導入request全局對象的時候,本質上是從Local中本次請求線程對應的RequestContext內封裝的request_context_obj.request對象。
除了request對象,session、g 以及current_app都是這個原理

 

 

LocalStack類與Local類cookie

 

Local類session

 

  是flask模仿threading.Local實現的一個本地線程,內部的self.__storage__封裝了一個字典,用來存放每個請求對應線程的私有數據數據,保證了每個請求之間的數據隔離。數據結構

  他的結構是這樣app

  •  其中RequestContext對象中封裝了當前請求的request對象和session對象
self.__storage__ = {
    greenlet獲取的惟一標識:{"stack": [RequestContext]},
    greenlet獲取的惟一標識:{"stack": [RequestContext]},
    greenlet獲取的惟一標識:{"stack": [RequestContext]},
}

 

__storage__是一個字典,可是在Local中還定義了__setattr__、__getattr__等方法,意味着咱們能夠向操做Local對象屬性的方式操做__storage__字典
複製代碼
try:
    from greenlet import getcurrent as get_ident        # 協程的惟一標識
except ImportError:
    try:
        from thread import get_ident            # 線程的惟一標識
    except ImportError:
        from _thread import get_ident


class Local(object):
    __slots__ = ('__storage__', '__ident_func__')

    def __init__(self):
        object.__setattr__(self, '__storage__', {})            # 這裏不能直接使用self.__storage__ = {},由於會觸發setattr
        object.__setattr__(self, '__ident_func__', get_ident)


    def __release_local__(self):
        self.__storage__.pop(self.__ident_func__(), None)    # 刪除__storage__中存放的線程相關數據(requestcontext)

    def __getattr__(self, name):
        try:
            return self.__storage__[self.__ident_func__()][name]
        except KeyError:
            raise AttributeError(name)

    def __setattr__(self, name, value):
        ident = self.__ident_func__()
        storage = self.__storage__
        try:
            storage[ident][name] = value
        except KeyError:
            storage[ident] = {name: value}

    def __delattr__(self, name):
        try:
            del self.__storage__[self.__ident_func__()][name]
        except KeyError:
            raise AttributeError(name)
複製代碼

 

 

LocalStack 類ide

 

Local類的做用只是用來存放數據,而LocalStack則提供了pop、top、push方法來操做Local中的數據。即Local中的數據是經過LocalStack來操做的。函數

  • push方法將一個RequestContext放到local.__storage__字典中stack對應的列表中
  • pop方法將RequestContext從列表中取出並從列表中刪除
  • top方法用來獲取RequestContext對象,並不會刪除該對象

注意:每一次請求的生命週期中導入的request、session等其餘flask全局對象,指向的都是同一個引用,也就是說在其餘地方操做session都是操做的同一個session對象tornado

複製代碼
class LocalStack(object):

    def __init__(self):
        self._local = Local()

    def push(self, obj):
        rv = getattr(self._local, 'stack', None)
        if rv is None:
            self._local.stack = rv = []
        rv.append(obj)
        return rv

    def pop(self):
        stack = getattr(self._local, 'stack', None)
        if stack is None:
            return None
        elif len(stack) == 1:
            release_local(self._local)
            return stack[-1]
        else:
            return stack.pop()

    @property
    def top(self):
        """The topmost item on the stack.  If the stack is empty,
        `None` is returned.
        """
        try:
            return self._local.stack[-1]
        except (AttributeError, IndexError):
            return None
複製代碼

 

 

源碼流程分析加密

 

1. 請求進來後會首先執行app的__call__方法,在該方法內部調用的其實就是app.wsgi_app()方法

2. ctx = self.request_context(environ)方法本質上就是實例化一個RequestContext對象,並將請求的全部相關信息(WSGI environment)封裝到RequestContext對象中,在實例化RequestContext對象的時候,其__init__方法中會幹如下幾件事兒

  •  2.1 初始化RequestContext,初始化app, request, session等屬性
  •  2.2 調用requestcontext_obj.match_request(),該函數內部會匹配url_rule
複製代碼
class Flask:
    def wsgi_app(self, environ, start_response):

        ctx = self.request_context(environ)
        ctx.push()

        # 中間省略部份內容

    def request_context(self, environ):
        return RequestContext(self, environ)


class RequestContext:
    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                    # 在self.push()中被賦值

        self.match_request()

    def match_request(self):
        try:
            url_rule, self.request.view_args = \
                self.url_adapter.match(return_rule=True)
            self.request.url_rule = url_rule
        except HTTPException as e:
            self.request.routing_exception = e
複製代碼

 

 

2.3  request = app.request_class(environ)會將請求的全部相關信息分裝成一個Request對象,並綁定到RequestContext對象的request屬性上

class Flask:
    request_class = Request            # app.request_class其實就是Request

 

 

3.  ctx.push(),這個方法會將RequestContext對象經過LocalStack類push到Local.__storage__字典的列表中,

  獲取當前請求對應的session數據並綁定RequestContext對象的session屬性上

複製代碼
# globals.py

_request_ctx_stack = LocalStack()
_app_ctx_stack = LocalStack()
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'
複製代碼
複製代碼
class RequestContext:
    def push(self):
        # 省略部分

        _request_ctx_stack.push(self)        # 把requestcontext保存到列表中

        self.session = self.app.open_session(self.request)    # 獲取session數據並綁定到requestcontext對象的session屬性中
        if self.session is None:
            self.session = self.app.make_null_session()
複製代碼

 

 

4.  當咱們使用from flask import request, sesion的時候,就會觸發LocalProxy類中相應對魔法方法,調用 _get_current_object() 函數,函數中調用 _lookup_req_object,經過LocalStack到Local到__storage__字典中

  對應的列表中的 RequestContext對象的request屬性和session屬性 對應的Request對象和SecureCookieSession對象取出

複製代碼
request = LocalProxy(partial(_lookup_req_object, 'request'))    # 獲取requestcontext_obj.request
session = LocalProxy(partial(_lookup_req_object, 'session'))    # 獲取requestcontext_obj.session


def _lookup_req_object(name):
    top = _request_ctx_stack.top
    if top is None:
        raise RuntimeError(_request_ctx_err_msg)
    return getattr(top, name)        # 利用反射獲取RequestContext對象的屬性值
複製代碼

 

 

5. 請求結束時,調用RequestContext中調pop方法,經過Localstack將Local中__storage__字典內的數據pop掉

複製代碼
class LocalStack():  

    """"省略"""
    def pop(self):
        """Removes the topmost item from the stack, will return the
        old value or `None` if the stack was already empty.
        """
        stack = getattr(self._local, 'stack', None)
        if stack is None:
            return None
        elif len(stack) == 1:
            release_local(self._local)
            return stack[-1]
        else:
            return stack.pop()
複製代碼
複製代碼
class Local(object):
    __slots__ = ('__storage__', '__ident_func__')

    def __init__(self):
        object.__setattr__(self, '__storage__', {})
        object.__setattr__(self, '__ident_func__', get_ident)

    
    def __delattr__(self, name):
        try:
            del self.__storage__[self.__ident_func__()][name]
        except KeyError:
            raise AttributeError(name)
複製代碼

 

應用上下文

 

應用上下文的原理和流程和管理上下文基本一致,他們分別建立了本身的一個Local類,在本身的Local類中爲每個線程建立類獨立的存儲數據的空間

上下文全局變量current_app, g, request, session等都是對象,咱們能夠在這些對象上經過屬性綁定一些數據,在須要的時候再取出這些數據進行操做

複製代碼
from flask import Flask,request,g,current_app,

app = Flask(__name__)

@app.before_request
def before():
    g.permission_code_list = ['list','add']
    current_app.x = 123
    request.name = "zhou"



@app.route('/',methods=['GET',"POST"])
def index():
    print(g.permission_code_list)
    print(current_app.x)
    print(request.name)
    # ['list', 'add']
    # 123
    # zhou
    return "index"


if __name__ == '__main__':
    app.run()
複製代碼

 

源碼

複製代碼
def _lookup_req_object(name):
    top = _request_ctx_stack.top
    if top is None:
        raise RuntimeError(_request_ctx_err_msg)
    return getattr(top, name)


def _lookup_app_object(name):
    top = _app_ctx_stack.top
    if top is None:
        raise RuntimeError(_app_ctx_err_msg)
    return getattr(top, name)


def _find_app():
    top = _app_ctx_stack.top
    if top is None:
        raise RuntimeError(_app_ctx_err_msg)
    return top.app


# context locals
_request_ctx_stack = LocalStack() #請求上下文
_app_ctx_stack = LocalStack()  #應用上下文
current_app = LocalProxy(_find_app)
g = LocalProxy(partial(_lookup_app_object, 'g'))
request = LocalProxy(partial(_lookup_req_object, 'request'))
session = LocalProxy(partial(_lookup_req_object, 'session'))
相關文章
相關標籤/搜索