Flask主要依賴於Werkzeug
和Jinja
這兩個庫,是很簡潔的Python Web框架。
Werkzeug
是一個WSGI的工具包,是Flask
的核心庫。Jinja
則是一個模板渲染的庫,主要負責渲染返回給客戶端的html文件。
各個文件的功能簡介
文件名 | 功能簡介 |
---|---|
__init__.py | 主要是添加了一些常用的模塊或者對象,方便導入 |
_compat.py | 主要是做 Python3 與 Python2 之間的兼容處理,還有pypy 的兼容處理 |
app.py | 實現與定義WSGI對象的主要模塊(WSGI規範可以參考PEP333和PEP3333) |
blueprints.py | 是Blueprints的實現與定義模塊。Blueprints的作用是項目的模塊劃分,類似於Django的APP |
cli.py | 主要是Flask的命令行功能實現和命令行解析 |
config.py | 實現與配置相關等功能 |
ctx.py | 實現需要上下文管理相關的類與方法 |
debughelpers.py | 定義一些錯誤提示或者檢查的方法或類,增強開發者的體驗 |
globals.py | 定義所有全局對象或方法的文件 |
helpers.py | 各種幫助類工具類和方法的文件 |
logging.py | 日誌模塊的實現文件 |
sessions.py | 定義和實現cookie 和session 的文件 |
signals.py | 定義和處理信號的文件,主要以blinker 庫實現 |
templating.py | 實現了與Jinja 交互的接口文件 |
testing.py | 實現測試需要的幫助類或者函數。一般不用於生產環境 |
views.py | 定義和實現了視圖的類 |
wrappers.py | 實現WSGI規範中的Wrapper,Request和Response |
主要類的簡單解析
主要類的關係
-
主要類的關係
-
Session類的關係
-
Wrapper類的關係
Context 機制
Werkzeug.local
模塊
參考之http://python.jobbole.com/87738/
Pyhton標準庫中thread.local
,實現了線程局部變量,使得每個線程可以有私有變量,具有線程隔離性,可以通過線程安全的方式獲取或者改變線程中的變量。
而Werkzeug.local
的功能與thread.local
類似,不過Werkzeug.local
在支持線程的基礎上,加上了greenlet
的協程支持。
在Flask的上下文管理中,主要依賴了Werkzeug.local
來實現的。
Werkzeug.local
主要有4個類:
Local
LocalStack
LocalProxy
LocalManager
Local
# werkzeug.local
...
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__', {})
object.__setattr__(self, '__ident_func__', get_ident)
def __iter__(self):
return iter(self.__storage__.items())
def __call__(self, proxy):
"""Create a proxy for a name."""
return LocalProxy(self, proxy)
def __release_local__(self):
self.__storage__.pop(self.__ident_func__(), None)
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)
__storage__
: 字典,用來保存不同線程/協程中的線程變量。字典中的Key爲線程/協程的ID。__ident_func__
: 這個是保存獲取當前線程/協程的ID函數。如果是協程,則是greenlet.getcurrent
。如果是線程,則是thread. get_ident
。
在Local中調用local.xxx
時,會調用__getattr__
函數,在__storage__
中用當前線程/協程ID作爲Key然後再查找變量,代碼: self.__storage__[self.__ident_func__()][name]
。
LocalStack
LocalStack
則是使用Local
實現一個棧來存儲線程/協程的變量。
LocalStack
還實現了push、pop、top等方法或屬性。調用這些屬性或者方法時,該類會根據當前線程/協程的ID,在Local實例中對相應的數值進行操作。
AppContext
應用上下文
AppContext
在請求、Flask的CLI命令及其他活動期間,保存整個應用程序級別的數據。類似於RequestContext
是保存請求期間的數據一樣。
# ctx.py
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)
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
def push(self):
"""Binds the app context to the current context."""
self._refcnt += 1
if hasattr(sys, 'exc_clear'):
sys.exc_clear()
_app_ctx_stack.push(self)
appcontext_pushed.send(self.app)
def pop(self, exc=_sentinel):
"""Pops the app context."""
try:
self._refcnt -= 1
if self._refcnt <= 0:
if exc is _sentinel:
exc = sys.exc_info()[1]
self.app.do_teardown_appcontext(exc)
finally:
rv = _app_ctx_stack.pop()
assert rv is self, 'Popped wrong app context. (%r instead of %r)' \
% (rv, self)
appcontext_popped.send(self.app)
def __enter__(self):
self.push()
return self
def __exit__(self, exc_type, exc_value, tb):
self.pop(exc_value)
if BROKEN_PYPY_CTXMGR_EXIT and exc_type is not None:
reraise(exc_type, exc_value, tb)
# globals.py
from functools import partial
from werkzeug.local import LocalStack, LocalProxy
...
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
_app_ctx_stack = LocalStack()
...
current_app = LocalProxy(_find_app)
g = LocalProxy(partial(_lookup_app_object, 'g'))
從代碼中可以看到,_app_ctx_stack
是一個werkzeug.local.LocalStack
的棧,current_app
和g
是通過代理,每次獲取棧頂元素AppContext
的app
和g
屬性。
AppContext
的實現目的,主要目的是:
- 使得一個進程中,可以多個Flask App共存,比如
Werkzeug
內置的Middleware
可以將兩個Flask App組合成一個WSGI應用,這種情況下,每個App都有自己獨立的AppContext
。 - 可以使得一些App的全局屬性,在離線的CLI命令或者單元測試等非Web環境中中進行訪問,而不需要構造出請求,只需要將
AppContext
push
到棧頂。
AppContext
中有屬性_refcnt
,這個值是記錄該上下文是否已經沒被使用,默認值爲0
,每次push()
則增1,pop()
則減1,直至_refcnt
減至0
,纔會觸發teardown_appcontext
的函數。
RequestContext
請求上下文
RequestContext
在整個請求處理期間,保存整個請求級別的數據。
# ctx.py
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.
Do not attempt to use this class directly, instead use
:meth:`~flask.Flask.test_request_context` and
:meth:`~flask.Flask.request_context` to create this object.
When the request context is popped, it will evaluate all the
functions registered on the application for teardown execution
(:meth:`~flask.Flask.teardown_request`).
The request context is automatically popped at the end of the request
for you. In debug mode the request context is kept around if
exceptions happen so that interactive debuggers have a chance to
introspect the data. With 0.4 this can also be forced for requests
that did not fail and outside of ``DEBUG`` mode. By setting
``'flask._preserve_context'`` to ``True`` on the WSGI environment the
context will not pop itself at the end of the request. This is used by
the :meth:`~flask.Flask.test_client` for example to implement the
deferred cleanup functionality.
You might find this helpful for unittests where you need the
information from the context local around for a little longer. Make
sure to properly :meth:`~werkzeug.LocalStack.pop` the stack yourself in
that situation, otherwise your unittests will leak memory.
"""
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 = []
# indicator if the context was preserved. Next time another context
# is pushed the preserved context is popped.
self.preserved = False
# remembers the exception for pop if there is one in case the context
# preservation kicks in.
self._preserved_exc = None
# Functions that should be executed after the request on the response
# object. These will be called before the regular "after_request"
# functions.
self._after_request_functions = []
self.match_request()
# globals.py
from functools import partial
from werkzeug.local import LocalStack, LocalProxy
...
def _lookup_req_object(name):
top = _request_ctx_stack.top
if top is None:
raise RuntimeError(_request_ctx_err_msg)
return getattr(top, name)
...
# context locals
_request_ctx_stack = LocalStack()
...
request = LocalProxy(partial(_lookup_req_object, 'request'))
session = LocalProxy(partial(_lookup_req_object, 'session'))
...
從代碼中可以看到,_request_ctx_stack
是一個werkzeug.local.LocalStack
的棧,request
和session
是通過代理,每次獲取棧頂元素RequestContext
的request
和session
屬性。
RequestContext
的實現目的,主要目的是:
- 保證了每個請求之間的信息完全隔離,避免衝突。
- 同時,可以簡單的使用
from flask import request
獲取當前的請求。
一個線程只執行一個請求,所以RequestContext
對於每個線程是唯一的。
當Flask應用開始處理請求時,會將當前RequestContext``push
到棧頂,也會同時將AppContext``push
到它的棧頂。當請求結束時,會將RequestContext``pop
出,然後將AppContext
也從其棧頂pop
出。
流程
- 在經過步驟A後,
globals.py
的_request_ctx_stack
和_app_ctx_stack
的top分別爲當前的req_ctx
和app_ctx
,經過LocalProxy
的代理後,在Flask中所有的from flask import session, request, g
,都是來自棧頂元素的req_ctx
或者app_ctx
- 所以在步驟B中,對請求進行處理的都是當前線程的request。
- 在步驟C中,會將
_request_ctx_stack
和_app_ctx_stack
的棧頂元素彈出。
在RequestContext
中的_implicit_app_ctx_stack
是爲了防止在處理請求中有如下操作,使得app_ctx
和req_ctx
出棧錯誤:
# 在請求處理中
from
with req_ctx:
...
# 如果沒有`_implicit_app_ctx_stack`做判斷,會出錯。AppContext中的`_refcnt`作用也類似
請求接收到返回響應流程
在流程的圖裏,當req_ctx.push()
後,就是開始調用self.full_dispatch_request()
進行請求的處理了。