基本流程概述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
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來操做的。函數
注意:每一次請求的生命週期中導入的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__方法中會幹如下幾件事兒
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'))