flask基礎之請求處理核心機制(五)
前言
總結一下flask框架的請求處理流程。
系列文章
flask基礎之安裝和使用入門(一)
flask基礎之jijia2模板使用基礎(二)
flask基礎之jijia2模板語言進階(三)
flask基礎之app初始化(四)
WSGI協議
一般來說http服務器和框架需要進行解耦,http專門負責接受HTTP請求、解析HTTP請求、發送HTTP,響應請求等;而web框架負責處理請求的邏輯,和數據庫的交互等等,那麽它們之間需要約定一套接口使得http服務器能夠調用web框架的處理邏輯,這個協議就是WSGI協議。
WSGI協議要求http服務器接收到http請求後經過處理得到兩個參數,一個是請求數據封裝的字典environ,另一個是需要框架回調的方法start_response。
在flask框架中,服務器對每個請求調用一次app的wsgi_app方法返回結果,而wsgi_app方法的執行過程就是請求的處理流程。
class Flask(object): def wsgi_app(self, environ, start_response): ctx = self.request_context(environ) ctx.push() error = None try: try: response = self.full_dispatch_request() except Exception as e: error = e response = self.handle_exception(e) except: error = sys.exc_info()[1] raise return response(environ, start_response) finally: if self.should_ignore_error(error): error = None ctx.auto_pop(error)
第一步:服務器啟動
服務器啟動後,假設服務器是基於線程的,此時app對象被創建,加載了相關的初始化參數,這時代理對象如current_app、g、session、request等會被創建,但是它們目前並沒有代理任何的對象,如果此時使用它們會報錯,需要在第一次接收到請求後才會真正地代理上下文。那麽服務器啟動究竟幹了什麽事呢?
詳細請參考:flask之app初始化
第二步:接收請求,創建上下文,入棧
服務器收到一個http請求後,使用app上下文和請求數據創建一個線程,調用app的request_context(self, environ)方法,將解包後封裝的http請求數據當做environ參數傳入,返回一個RequestContext實例對象,每一個請求都有一個RequestContext實例對象,同時他們都擁有各自的app上下文,也就是說在本線程中的app應用是服務器初始化app的一個引,因此我們可以動態修改app的屬性。
將RequestContext對象push進_request_ctx_stack裏面,_request_ctx_stack是一個棧對象,此時代理對象request指向棧頂的RequestContext對象的request屬性,該request是一個Request對象,而session此時指向棧頂的RequestContext對象的session屬性。
判斷_app_ctx_stack棧頂是否存在應用上下文對象AppContext,不存在就創建,同時將AppContext推送到_app_ctx_stack棧對象中,此時current_app指向棧頂AppContext對象的app屬性,而g變量指向棧頂AppContext對象的g屬性,本質上是一個_AppCtxGlobals對象,數據結構是一個字典。
- 應用上下文和請求上下文存放的棧對象
_request_ctx_stack = LocalStack()
_app_ctx_stack = LocalStack()
- 動態修改app的屬性
from flask import Flask
app = Flask(__name__)
@app.route(‘/test1‘)
def test1():
"""
動態添加一個視圖函數
"""
@app.route(‘/test2‘)
def test2():
return ‘test2‘
return ‘OK‘
- 應用上下文和請求上下文源碼分析
class Flask(object):
def app_context(self):
return AppContext(self)
def request_context(self, environ):
return RequestContext(self, environ)
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()
def push(self):
self._refcnt += 1
if hasattr(sys, ‘exc_clear‘):
sys.exc_clear()
_app_ctx_stack.push(self) # 將自己推送到棧中
appcontext_pushed.send(self.app)
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
def push(self):
pass
第三步:請求分派
分發請求並執行處理邏輯的函數為full_dispatch_request,其返回一個Response對象。處理的過程為:
先執行app對象before_first_request_funcs列表中的所有方法,這是針對app的第一次請求需要的預處理方法,執行該列表中的所有方法是一個原子操作,被加了線程鎖,如果不是第一次請求就跳過;
然後執行app對象的url_value_preprocessors字典中對應藍圖的列表中的所有方法,對所有的URL進行預處理;
執行app對象的before_request_funcs列表中的所有方法,其會按照加載的順序鏈執行,並且如果中間有任何一個方法返回的結果不是None,那麽執行中斷,直接返回結果,不再執行視圖函數。這是針對app所有的請求都會執行的方法,當然也可以通過藍圖來進行管理;
通過request對象的url_rule(Rule)找到app中的url_map中對應的視圖函數執行,返回一個元組的結果rv,就是我們平時寫視圖函數時返回的元組;
調用make_response函數,以返回的結果rv作為參數構建一個Response對象;
執行app對象中的after_request_funcs列表的所有方法,以構建的Response對象作為參數,每個方法必須都返回Response類型的對象,最後調用session保存本次的狀態信息;
第四步:出棧
先執行app對象的teardown_request_funcs列表中的所有的方法,其方法和after_request_funcs中的一樣,只不過是在出棧前才觸發,這意味著即使處理邏輯的部分出錯,這裏方法也會執行,然後從_request_ctx_stack中彈出RequestContext請求上下文,然後執行app對象中的teardown_appcontext_funcs列表的所有方法,最後從_app_ctx_stack中彈出AppContext應用上下文。
class AppContext:
def pop(self, exc=_sentinel):
app_ctx = self._implicit_app_ctx_stack.pop()
try:
clear_request = False
if not self._implicit_app_ctx_stack:
self.preserved = False
self._preserved_exc = None
if exc is _sentinel:
exc = sys.exc_info()[1]
self.app.do_teardown_request(exc) # 調用請求鉤子
if hasattr(sys, ‘exc_clear‘):
sys.exc_clear()
request_close = getattr(self.request, ‘close‘, None)
if request_close is not None:
request_close()
clear_request = True
finally:
rv = _request_ctx_stack.pop() # 彈出請求上下文
if clear_request:
rv.request.environ[‘werkzeug.request‘] = None
if app_ctx is not None:
app_ctx.pop(exc) # 彈出應用上下文
flask請求處理最簡代碼模型
假設服務器使用的是多進程模式。
from multiprocessing import Process, Pool
class Flask(object):
def __call__(self, environ, start_response):
"""定義app對請求的處理過程"""
pass
def listen_port():
"""假設這是端口監聽並解析http請求的方法"""
pass
def run_web():
"""假設這是程序主循環"""
app = Flask() # 創建一個app,這是app初始化做的
pool = Pool(10)
while True:
# 獲取一個http請求的數據
environ, start_response = listen_port()
# 調用app處理請求
pool.apply_async(app, args=(environ, start_response))
if __name__ == ‘__main__‘:
run_web()
總結
無論是gunicorn服務器還是uwsgi服務器,其啟動後加載了app對象;
當收到http請求後,按照http協議解析數據,將數據打包成一個字典,將其和響應函數一起作為參數調用app對象的wsgi_app方法;
wsgi_app方法按照接收請求,創建上下文,入棧,請求分發,出棧的步驟處理完業務邏輯返回響應數據;
參考
http://docs.jinkan.org/docs/flask/reqcontext.html
https://dormousehole.readthedocs.io/en/latest/
flask基礎之請求處理核心機制(五)