前言
總結一下flask框架的請求處理流程。
系列文章
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方法按照接收請求,建立上下文,入棧,請求分發,出棧的步驟處理完業務邏輯返回響應資料;