Flask上下文管理

Flask上下文管理機制

能夠從三個大的方面來探討flask的兩大上下文管理機制html

方面一:請求進來時python

方面二:視圖函數web

方面三:請求結束前json

請求進來時

觸發執行__call__方法,__call__方法的邏輯很簡單,直接執行wsgi_app方法,將包含全部請求相關數據和一個響應函數傳進去。flask

def application(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/html')])
    return '<h1>Hello, web!</h1>'

上面的application()函數就是符合WSGI標準的一個HTTP處理函數,它接收兩個參數:

    environ:一個包含全部HTTP請求信息的dict對象;

    start_response:一個發送HTTP響應的函數。

在application()函數中,調用:

start_response('200 OK', [('Content-Type', 'text/html')])

符合wsgi協議標準的函數

  備註:__call__是一個符合wsgi標準的函數cookie

執行wsgi_app方法session

def wsgi_app(self, environ, start_response):
        
        ctx = self.request_context(environ)
        error = None
        try:
            try:
                ctx.push()
                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)

  第一步先執行了一個request_context的方法,將environ傳進去,最後返回一個RequestContext類的對象,被ctx的變量接收(ctx=request_context(environ))app

 def request_context(self, environ):
        """Create a :class:`~flask.ctx.RequestContext` representing a
        WSGI environment. Use a ``with`` block to push the context,
        which will make :data:`request` point at this request.

        See :doc:`/reqcontext`.

        Typically you should not call this from your own code. A request
        context is automatically pushed by the :meth:`wsgi_app` when
        handling a request. Use :meth:`test_request_context` to create
        an environment and context instead of this method.

        :param environ: a WSGI environment
        """
        return RequestContext(self, environ)

這個ctx對象在初始化時,賦了兩個很是有用的屬性,一個是request,一個是session框架

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

  這兩個屬性中request是一個Request()對象,這個對象就是咱們在flask中使用的request對象,爲咱們提供了不少便捷的屬性和方法,好比:request.method、request.form、request.args等等,另外一個屬性是session,初始爲None。dom

  緊接着執行ctx.push()方法,這個方法中,在執行請求上下文對象ctx以前先實例化了一個app_context對象,先執行了app_context的push方法,而後才執行_request_ctx_stack對象中的top和_request_ctx_stack.push(self),最後對ctx中的session進行處理。

  因此,flask中的應用上下文發生在請求上下文以前

def push(self):           
       
        top = _request_ctx_stack.top
        if top is not None and top.preserved:
            top.pop(top._preserved_exc)
            
        # 在執行request_context請求上下文的push方法時,先執行了app_context應用上下文的push方法
        app_ctx = _app_ctx_stack.top
        if app_ctx is None or app_ctx.app != self.app:
            app_ctx = self.app.app_context()
            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()
            
        # 而後執行請求上下文對象中LocalStack對象的push方法
        _request_ctx_stack.push(self)

        # 最後處理session
        if self.session is None:
            session_interface = self.app.session_interface
            self.session = session_interface.open_session(
                self.app, self.request
            )

            if self.session is None:
                self.session = session_interface.make_null_session(self.app)

  可是咱們先說請求上下文,在處理完應用上下文的push方法後,緊接着執行了_request_ctx_stack對象的兩個方法。

  而這個_request_ctx_stack是LocalStack這個類的對象。_request_ctx_stack = LocalStack()

   LocalStack有沒有很眼熟,沒錯,flask內部使用的機制就是相似於咱們上文中自定義的LocalStack的機制,實例化過程當中使用了面向對象中的組合概念,self._local = Local(),而後在自身又實現了push、pop、top方法,這三個方法中都是經過反射從Local類的實例化對象中對一個stack屬性進行append、pop、[-1]的操做,因此,Local對象中的stack屬性對應的值必定是一個相似於列表的東西。經過對列表的操做,實現一個相似於棧的存取。

  接着聊聊這個Local類,在實例化時,會對每一個對象生成一個storage的空字典。咱們翻遍整個Local類的源碼,發現內部並無實現一個叫stack的方法或者屬性,可是上面咱們提到了LocalStack對象會對Local對象中的一個叫stack的東西進行一系列操做。找不到不會報錯嗎?

  這就是flask的巧妙之處,經過類的一些魔法方法巧妙的實現了相應的處理。在前引中,提到若是對象中沒有某個屬性,取值時,最終會執行類中的__getattr__方法,而後再作後續的異常處理,flask將全部的對應邏輯都實如今了類的__getattr__方法中,將每個線程存儲到字典中,在請求進來時,將每個對應的請求ctx存在一個列表中,使用時直接調用,而不是經過傳參的形式,更體現出了flask框架的輕量級。

  處理完_request_ctx_stack後,就該處理session了。

  在flask中,處理session時,很是的巧妙,完美的遵循了開閉原則,會先執行session_interface對象的open_session方法,在這個方法中,會先從用戶請求的cookie中獲取sessionid,獲取該用戶以前設置的session值,而後將值賦值到ctx.session中。

  處理完session後,ctx.push方法就執行完了,返回到最開始的app.wsgi_app方法中,執行完push方法後,接着執行full_dispatch_request方法,從這個名字中咱們也能猜到,這個方法只要是負責請求的分發。

def full_dispatch_request(self):
      
            self.try_trigger_before_first_request_functions()
            try:
                request_started.send(self)
                rv = self.preprocess_request()
                if rv is None:
                    rv = self.dispatch_request()
            except Exception as e:
                rv = self.handle_user_exception(e)
            return self.finalize_request(rv)     

  在full_dispatch_request方法中先執行preprocess_request方法,這個方法,會先執行全部被before_request裝飾器裝飾的函數,而後就經過路由的分發執行視圖函數了(dispatch_request)

執行視圖函數時

  在執行視圖函數以前,先執行了before_request,在執行咱們的視圖函數。

  視圖函數主要處理業務邏輯。在視圖函數中能夠調用request對象,進行取值,也能夠調用session對象對session的存取。

  在整個request的請求生命週期中,獲取請求的數據直接調用request便可,對session進行操做直接調用session便可。request和session都是LocalProxy對象,藉助偏函數的概念將對應的值傳入_lookup_req_object函數。先從_request_ctx_stack(LocalStack)對象中獲取ctx(請求上下文對象),再經過反射分別獲取request和session屬性。整個過程當中LocalStack扮演了一個全局倉庫的角色,請求進來將數據存取,須要時即去即用。因此,flask實現了在整個請求的生命週期中哪兒須要就直接調用的特點。

request = LocalProxy(partial(_lookup_req_object, 'request'))
session = LocalProxy(partial(_lookup_req_object, 'session'))

請求結束前

  視圖函數執行完後,dispatch_request執行結束,執行full_dispatch_request方法的返回值finalize_request方法。這個方法中,一樣的,在返回響應以前,先執行全部被after_request裝飾器裝飾的函數。

---->finalize_request ------> process_response
 def process_response(self, response):
        
        ctx = _request_ctx_stack.top
        bp = ctx.request.blueprint
        funcs = ctx._after_request_functions
        if bp is not None and bp in self.after_request_funcs:
            funcs = chain(funcs, reversed(self.after_request_funcs[bp]))
        if None in self.after_request_funcs:
            funcs = chain(funcs, reversed(self.after_request_funcs[None]))
        for handler in funcs:
            response = handler(response)
        if not self.session_interface.is_null_session(ctx.session):
            self.session_interface.save_session(self, ctx.session, response)
        return response

  執行process_response過程當中,執行完after_request後,而後,執行session的save_session方法。將內存中保存在ctx.session的值取到後,json.dumps()序列化後,寫入響應的cookie中(set_cookie),最後返回響應。

    def save_session(self, app, session, response):
        domain = self.get_cookie_domain(app)
        path = self.get_cookie_path(app)

        # If the session is modified to be empty, remove the cookie.
        # If the session is empty, return without setting the cookie.
        if not session:
            if session.modified:
                response.delete_cookie(
                    app.session_cookie_name,
                    domain=domain,
                    path=path
                )

            return

        # Add a "Vary: Cookie" header if the session was accessed at all.
        if session.accessed:
            response.vary.add('Cookie')

        if not self.should_set_cookie(app, session):
            return

        httponly = self.get_cookie_httponly(app)
        secure = self.get_cookie_secure(app)
        samesite = self.get_cookie_samesite(app)
        expires = self.get_expiration_time(app, session)
        val = self.get_signing_serializer(app).dumps(dict(session))
        # set_cookie將session寫入響應的cookie中
        response.set_cookie(
            app.session_cookie_name,
            val,
            expires=expires,
            httponly=httponly,
            domain=domain,
            path=path,
            secure=secure,
            samesite=samesite
        )

  返回響應後,自動的調用ctx.auto_pop(error),將Local中存儲的ctx對象pop掉,整個請求結束。

請求上下文的執行流程:

 上下文的執行流程

相關文章
相關標籤/搜索