學習一樣東西不能只停留在表面,我們要探索其中的細節,學習作者的編程思想,這樣才能更進一步。
關于WSGI
WSGI(全稱Web Server Gateway Interface
),是為 Python 語言定義的Web服務器
和Web應用程序
之間的一種簡單而通用的接口
,它封裝了接受HTTP請求
、解析HTTP請求
、發送HTTP
,響應
等等的這些底層的代碼和操作,使開發者可以高效的編寫Web應用。
一個簡單的使用WSGI的App例子:
def application(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/html')])
return [b'<h1>Hello, I Am WSGI!</h1>']
-
environ
: 一個包含全部HTTP請求信息的字典,由WSGI Server
解包HTTP請求生成。 -
start_response
: 一個WSGI Server
提供的函數,調用可以發送響應的狀態碼和HTTP
報文頭, 函數在返回前必須調用一次start_response()
。 -
application()
應當返回一個可以迭代的對象(HTTP正文)。 -
application()
函數由WSGI Server
直接調用和提供參數。 - Python內置了一個
WSGIREF
的WSGI Server
,不過性能不是很好,一般只用在開發環境。可以選擇其他的如Gunicorn
。
Flask的上下文對象
Flask有兩種Context
(上下文),分別是
-
RequestContext
請求上下文 -
Request
請求的對象,封裝了Http請求(environ
)的內容 -
Session
根據請求中的cookie,重新載入該訪問者相關的會話信息。 -
AppContext
程序上下文 -
g
處理請求時用作臨時存儲的對象。每次請求都會重設這個變量 -
current_app
當前激活程序的程序實例
生命周期:
-
current_app
的生命周期最長,只要當前程序實例還在運行,都不會失效。 -
Request
和g
的生命周期為一次請求期間,當請求處理完成后,生命周期也就完結了 -
Session
就是傳統意義上的session了。只要它還未失效(用戶未關閉瀏覽器、沒有超過設定的失效時間),那么不同的請求會共用同樣的session。
Flask處理流程
- 第一步:創建上下文
Flask根據WSGI Server封裝的請求等的信息(environ
)新建RequestContext對象
和AppContext對象
# 聲明對象
# LocalStack LocalProxy 都由Werkzeug提供
# 我們不深究他的細節,那又是另外一個故事了,我們只需知道他的作用就行了
# LocalStack 是棧結構,可以將對象推入、彈出
# 也可以快速拿到棧頂對象。當然,所有的修改都只在本線程可見。
_request_ctx_stack = LocalStack()
_app_ctx_stack = LocalStack()
# 如果調用一個LocalStack實例, 能返回一個 LocalProxy 對象
# 這個對象始終指向 這個LocalStack實例的棧頂元素。
# 如果棧頂元素不存在,訪問這個 LocalProxy 的時候會拋出 RuntimeError異常
# LocalProxy對象你只需暫時理解為棧里面的元素即可了
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'))
# RequestContext
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
#AppContext
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()
self._refcnt = 0
這里需要注意的是,RequestContext
在初始化的時候,當前Flask的實例作為參數被傳進來。雖然每次的請求處理都會創建一個RequestContext對象,但是每一次傳入的app參數卻是同一個。通過這個機制,可以使得:
由同一個Flask實例所創建的
RequestContext
,其成員變量app都是同一個Flask實例對象 。實現了多個RequestContext
對應同一個current_app
的目的。
- 第二步:入棧
將RequestContext
對象push進_request_ctx_stack
里面。在這次請求期間,訪問request對象
,session對象
將指向這個棧的棧頂元素
class RequestContext(object):
def push(self):
....
_app_ctx_stack.push(self)
appcontext_pushed.send(self.app)
AppContext對象push進_app_ctx_stack
里面。在這次請求期間,訪問g
對象將指向這個棧的棧頂元素
class AppContext(object):
def push(self):
....
_request_ctx_stack.push(self)
第三步:請求分發
response = self.full_dispatch_request()
Flask將調用full_dispatch_request
函數進行請求的分發,之所以不用給參數,是因為我們可以通過request
對象獲得這次請求的信息。full_dispatch_request
將根據請求的url找到對應的藍本里面的視圖函數,并生成一個response
對象。注意的是,在請求之外的時間,訪問request對象是無效的,因為request對象依賴請求期間的_request_ctx_stack
棧。第四步:上下文對象出棧
這次HTTP的響應已經生成了,就不需要兩個上下文對象了。分別將兩個上下文對象出棧,為下一次的HTTP請求做出準備。第五步:響應WSGI
調用Response對象,向WSGI Server返回其結果作為HTTP正文。Response對象是一個 可調用對象,當調用發生時,將首先執行WSGI服務器傳入的start_response()函數 發送狀態碼和HTTP報文頭。
最后附上Flask處理請求的wsgi_app
函數
# environ: WSGI Server封裝的HTTP請求信息
# start_response: WSGI Server提供的函數,調用可以發送狀態碼和HTTP報文頭
def wsgi_app(self, environ, start_response):
# 根據environ創建上下文
ctx = self.request_context(environ)
# 把當前的request context,app context綁定到當前的context
ctx.push()
error = None
try:
try:
#根據請求的URL,分發請求,經過視圖函數處理后返回響應對象
response = self.full_dispatch_request()
except Exception as e:
error = e
response = self.make_response(self.handle_exception(e))
return response(environ, start_response)
finally:
if self.should_ignore_error(error):
error = None
# 最后出棧
ctx.auto_pop(error)
參考資料
匯智網:Flask框架-上下文對象 :Flask核心機制