Flask - 請求處理流程和上下文源碼分析

Flask - 請求處理流程和上下文

鎮樓圖:
shell

WSGI

WSGI(全稱Web Server Gateway Interface),是爲 Python 語言定義的Web服務器和Web應用程序之間的一種簡單而通用的接口,它封裝了接受HTTP請求、解析HTTP請求、發送HTTP,響應等等的這些底層的代碼和操做,使開發者能夠高效的編寫Web應用。
一個簡單的使用WSGI的App例子:flask

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正文)。cookie

  • application()函數由WSGI Server直接調用和提供參數。session

  • Python內置了一個WSGIREF的WSGI Server,不過性能不是很好,通常只用在開發環境。能夠選擇其餘的如Gunicorn。數據結構

WSGI Server 和 App交互圖:app

Flask的上下文對象及源碼解析

Flask有兩種Context(上下文),分別是ide

1.RequestContext 請求上下文

  • Request 請求的對象,封裝了Http請求(environ)的內容

  • Session 根據請求中的cookie,從新載入該訪問者相關的會話信息。

2.AppContext 程序上下文

  • g 處理請求時用做臨時存儲的對象。每次請求都會重設這個變量

  • current_app 當前激活程序的程序實例

Flask處理請求流程:

0. 請求入口

if __name__ == '__main__':
    app.run()

.

def run(self, host=None, port=None, debug=None,
        load_dotenv=True, **options):
    # Change this into a no-op if the server is invoked from the
    # command line. Have a look at cli.py for more information.
    if os.environ.get('FLASK_RUN_FROM_CLI') == 'true':
        from .debughelpers import explain_ignored_app_run
        explain_ignored_app_run()
        return
 
    if get_load_dotenv(load_dotenv):
        cli.load_dotenv()
 
        # if set, let env vars override previous values
        if 'FLASK_ENV' in os.environ:
            self.env = get_env()
            self.debug = get_debug_flag()
        elif 'FLASK_DEBUG' in os.environ:
            self.debug = get_debug_flag()
 
    # debug passed to method overrides all other sources
    if debug is not None:
        self.debug = bool(debug)
 
    _host = '127.0.0.1'
    _port = 5000
    server_name = self.config.get('SERVER_NAME')
    sn_host, sn_port = None, None
 
    if server_name:
        sn_host, _, sn_port = server_name.partition(':')
 
    host = host or sn_host or _host
    port = int(port or sn_port or _port)
 
    options.setdefault('use_reloader', self.debug)
    options.setdefault('use_debugger', self.debug)
    options.setdefault('threaded', True)
 
    cli.show_server_banner(self.env, self.debug, self.name, False)
 
    from werkzeug.serving import run_simple
 
    try:
        run_simple(host, port, self, **options)
    finally:
        # reset the first request information if the development server
        # reset normally.  This makes it possible to restart the server
        # without reloader and that stuff from an interactive shell.
        self._got_first_request = False

.

def __call__(self, environ, start_response):
    """The WSGI server calls the Flask application object as the
    WSGI application. This calls :meth:`wsgi_app` which can be
    wrapped to applying middleware."""
    return self.wsgi_app(environ, start_response)

首先每次請求進來以後,對於每次請求進來以後,都會執行Flask類實例的__call__方法, 用來返回實例化的類 return self.wsgi_app(environ, start_response)

而後在上下文中, 要完成的操做是:

  1. 對原生請求進行封裝,生成視圖函數可操做的request對象

  2. 獲取請求中的cookie信息,生成Session對象

  3. 執行預處理函數和視圖函數

  4. 返回響應結果

如下爲上下文源碼,後續對各部分代碼進行分別闡述:

def wsgi_app(self, environ, start_response):
    #  生成 ctx.request , request.session,請求上下文,即請求先關的數據都封裝到了 ctx這個對象中去
    ctx = self.request_context(environ)
    error = None
    try:
        try:
            # 將ctx入棧,可是內部也將應用上下文入棧
            ctx.push()
            # 對請求的url進行視圖函數的匹配,執行視圖函數,返回響應信息(cookie)
            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)

1.請求上下文對象的建立

#  生成 ctx.request , request.session,請求上下文,即請求先關的數據都封裝到了 ctx這個對象中去

ctx = self.request_context(environ)

生成RequestContext類實例,該實例包含了本次請求的request和Session信息

def request_context(self, environ):
    return RequestContext(self, environ)

對生成的類實例進行初始化,而且將傳入的原生請求信息environ封裝值Request類實例中。此時,request爲封裝以後的Request實例,Session爲None

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

    self._after_request_functions = []

    self.match_request()

request_class = Request

2. 將請求上下文和應用上下文入棧

將ctx入棧,可是內部也將應用上下文入棧

ctx.push()

def push(self):
    # 獲取到的  top  == ctx
    top = _request_ctx_stack.top
    if top is not None and top.preserved:
        top.pop(top._preserved_exc)
 
    # Before we push the request context we have to ensure that there
    # is an application context.
    """
        _request_ctx_stack 和 _app_ctx_stack 都是 Local 類的實例
    """
    # 獲取 應用上下文的棧頂元素,獲得 app_ctx
    app_ctx = _app_ctx_stack.top
    if app_ctx is None or app_ctx.app != self.app:
        # self.app == Fask()
        # 獲得 一個 AppContext類的實例對象,獲得一個 應用上下文對象 app_ctx,此時 app_ctx擁有如下屬性: app_ctx.app = app, app_ctx.g = app.app_ctx_globals_class()
        app_ctx = self.app.app_context()
        # 將 app_ctx 入棧,應用上下文入棧
        app_ctx.push() 
        self._implicit_app_ctx_stack.append(app_ctx)
    else:
        self._implicit_app_ctx_stack.append(None)
 
    if hasattr(sys, 'exc_clear'):
        sys.exc_clear()
 
    # self 指的是 ctx,即將ctx入棧,即 _request_ctx_stack._local.stack = [ctx]。請求上下文入棧
    _request_ctx_stack.push(self)
    # 因爲每次請求都會初始化建立你ctx,所以session都爲None
    if self.session is None:
        # SecureCookieSessionInterface()
        # session_interface = SecureCookieSessionInterface(),即session_interface就是一個SecureCookieSessionInterface類的實例對象
        session_interface = self.app.session_interface
        # 第一次訪問:生成一個 字典(容器) 返回至 self.session
        self.session = session_interface.open_session(
            self.app, self.request
        )
        if self.session is None:
            self.session = session_interface.make_null_session(self.app)

首先,應用上下文入棧,這裏很少作解釋說明,其執行流程與請求上下文相同,請參考下文對與請求上下文的入棧流程分析。

其次,請求上下文入棧。執行 _request_ctx_stack.push(self) ,咱們先看看 _request_ctx_stack 是什麼。由 _request_ctx_stack = LocalStack() 可知 _request_ctx_stackLocalStack 類實例對象,進入 LocalStack 的構造方法中

def __init__(self):
        self._local = Local()

即在類實例化過程當中,爲 _request_ctx_stack 實例對象建立 _local 屬性,該屬性的值是 Local類實例,進入其構造方法中,在該方法中爲每個 Local 類實例建立 __storage____ident_func__ 屬性:

class Local(object):
    __slots__ = ('__storage__', '__ident_func__')
 
    def __init__(self):
        object.__setattr__(self, '__storage__', {})
        object.__setattr__(self, '__ident_func__', get_ident)

至此,完成了對 _request_ctx_stack實例對象建立的流程分析,可是須要注意的是,該實例對象並非在每次請求以後才建立完成的,而是在flask項目啓動以後就會被當即建立,該對象對於每次的請求都會調用該對象的push方法進行請求上下文的入棧,也就是說 _request_ctx_stack 是一個單例對象,該單例對象能夠在任何的地方被調用,其餘的單例對象還有:

"""
    注意:
        在項目啓動以後,global裏的代碼就已經執行完畢,並且也只會執行一次,所以這裏面的變量是針對全部請求所使用的,可是根據不一樣線程id用來存放各自的值
"""
#  生成 請求上下文棧對象,將請求上下文對象 ctx 保存到 _request_ctx_stack._local.stack = [ctx]中
_request_ctx_stack = LocalStack()
# 生成應用上下文棧對象,將應用上下文對象 app_ctx 保存到  _app_ctx_stack._local.stack = [app_ctx]中
_app_ctx_stack = LocalStack()
 
# current_app.__local = app
current_app = LocalProxy(_find_app)
# 獲取ctx.request
request = LocalProxy(partial(_lookup_req_object, 'request'))
# 獲取 ctx.session
session = LocalProxy(partial(_lookup_req_object, 'session'))
# 維護這次請求的一個全局變量,其實就是一個字典
g = LocalProxy(partial(_lookup_app_object, 'g'))

對於以上的單例對象,在項目啓動以後被建立,在項目中止後被銷燬,與請求是否進來無任何關係。如今咱們知道了 _request_ctx_stack 的建立流程,咱們返回以前對請求上下文的入棧操做 _request_ctx_stack.push(self) (self指的是ctx),進入push方法:

def push(self, obj):
    # obj == ctx
    """Pushes a new item to the stack"""
    rv = getattr(self._local, 'stack', None)
    if rv is None:
        self._local.stack = rv = []
    rv.append(obj)
    return rv

在上述流程中,首先使用反射獲取 _request_ctx_stack._local.stack 的值,也就是獲取請求棧的值。項目剛啓動,在第一次請求進來以前,請求棧的爲空,則代碼繼續向下執行將當前請求的ctx追加至請求棧中,而且返回請求棧的值。這裏着重說一下入棧以前的流程和入棧以後的數據結構:執行 self._local.stack = rv = [] ,會調用 Local 類的 setattr 方法

def __setattr__(self, name, value):
        ident = self.__ident_func__()
        storage = self.__storage__
        try:
            storage[ident][name] = valueexcept KeyError:
            storage[ident] = {name: value}

self.__ident_func__()爲獲取當前這次請求的協程id或者線程id, self.__storage__爲一個字典對象,在項目啓動後的第一個請求進來以後會發生 storage[ident][name] = value 的異常錯誤,拋出異常被下面捕獲,所以執行 storage[ident] = {name: value} (以這次協程id或線程id爲key,該key的value爲一個字典,在字典中存儲一個鍵值對"stack":[ctx]),即此數據結構爲:

_request_ctx_stack._local.stack={
        線程id或協程id: {
            'stack': [ctx]
        }
}

同時, self._local.stack = [ctx]。至此,完成請求上下文的入棧操做,應用上下文與請求上下文的入棧流程相同,這裏不在贅述。至此完成了請求入棧的操做,咱們須要知道在上述過程當中使用到的四個類: RequestContext (請求上下文類,實例對象ctx中包含了request,Session兩個屬性)、 Request (對請求的元信息environ進行封裝)、 LocalStack(使用該類實例對象 _request_ctx_stack ,維護請求上下文對象ctx的入棧和出棧操做,至關於請求上下文對象的管理者)、 Local (堆棧類,真正存放請求上下文的類),若是你仍是對着幾個類關係仍是不明白,請看我爲你準備的圖:


返回 wsgi_app 函數,繼續向下執行 response = self.full_dispatch_request() 函數

3.根據請求的URl執行響應的視圖函數,返回執行結果

response = self.full_dispatch_request()

def full_dispatch_request(self):
    #  將 _got_first_request =  True,依次執行定義的 before_first_request 函數
    self.try_trigger_before_first_request_functions()
    try:
        # 觸發 request_started 信號
        request_started.send(self)
        #  執行鉤子函數:before_request,before_first_request
        rv = self.preprocess_request()
        # 若是執行的before_request,before_first_request函數沒有返回值,則繼續執行視圖函數。如有返回值,則不執行視圖函數
        if rv is None:
            # 執行此url對應的別名的視圖函數並執行該函數,返回視圖函數的返回值,獲得相應信息
            rv = self.dispatch_request()
    except Exception as e:
        # 若是發生錯誤,則將異常信息做爲返回值進行返回
        rv = self.handle_user_exception(e)
    # 封裝返回信息並返回,包括 session
    return self.finalize_request(rv)

將請求上下文和應用上下文入棧

在函數的內部首先執行預處理函數再執行視圖函數,返回預處理函數或視圖函數的返回值至瀏覽器。

返回 wsgi_app 函數中,繼續向下執行 ctx.auto_pop(error) 函數,完成對請求上下文和應用上下文的出棧操做:

def auto_pop(self, exc):
    if self.request.environ.get('flask._preserve_context') or \
       (exc is not None and self.app.preserve_context_on_exception):
        self.preserved = True
        self._preserved_exc = exc
    else:
        self.pop(exc)

.

def pop(self, exc=_sentinel):
    """Pops the request context and unbinds it by doing that.  This will
    also trigger the execution of functions registered by the
    :meth:`~flask.Flask.teardown_request` decorator.
 
    .. versionchanged:: 0.9
       Added the `exc` argument.
    """
    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 this interpreter supports clearing the exception information
            # we do that now.  This will only go into effect on Python 2.x,
            # on 3.x it disappears automatically at the end of the exception
            # stack.
            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()
 
        # get rid of circular dependencies at the end of the request
        # so that we don't require the GC to be active.
        if clear_request:
            rv.request.environ['werkzeug.request'] = None
 
        # Get rid of the app as well if necessary.
        if app_ctx is not None:
            # 應用上下文出棧
            app_ctx.pop(exc)
 
        assert rv is self, 'Popped wrong request context.  ' \
            '(%r instead of %r)' % (rv, self)

.

def pop(self):
    """Removes the topmost item from the stack, will return the
    old value or `None` if the stack was already empty.
    """
    stack = getattr(self._local, 'stack', None)
    if stack is None:
        return None
    elif len(stack) == 1:
        release_local(self._local)
        return stack[-1]
    else:
        # 獲取並刪除列表中的第一個元素,同時返回該元素
        return stack.pop()

stack獲取到的是請求棧或應用棧的列表,棧的長度爲1,則進入 elif 控制語句中,首先執行 release_local(self._local)

def release_local(local):
     
    local.__release_local__()

local=self._local ,即執行 Local 類的 __release_local__方法,進入該方法:

def __release_local__(self):
    # 將 self.__storage__ 所維護的字典中刪除當前協程或線程id爲key的元素
    self.__storage__.pop(self.__ident_func__(), None)

從上面的語句中能夠很明顯看出,要執行的操做就是將以當前協程或線程id爲key的元素從字典 self.__storage__ 中刪除,返回至pop函數中的elif控制語句,最終將列表中的最後一個元素返回。注意,最終 _request_ctx_stack._local 的請求棧和應用棧列表中至少會存在一個元素。


感謝博主(http://www.javashuo.com/article/p-qljjpqtc-w.html) 對本文的大力支持!!!

相關文章
相關標籤/搜索