1. 程式人生 > >flask基礎之請求處理核心機制(五)

flask基礎之請求處理核心機制(五)

sta while listen oca tp服務器 參數調用 corn adapt hasattr

前言

總結一下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基礎之請求處理核心機制(五)