Flask初探六 ( 請求鉤子 / 鉤子函數 / 藍圖 )

請求鉤子

通過裝飾器為一個模塊添加請求鉤子, 對當前模塊的請求進行額外的處理. 比如權限驗證.

項目結構

01-項目結構.png

應用鉤子函數

E:\workspace\pycharm\Demo\application_init_.py

def create_app(config_name):
    app = Flask(__name__)

    # config  Debug = True
    app.config.from_object(config_name)

    from modules import blue_index
    from modules import blue_user
    from modules import blue_admin

    app.register_blueprint(blue_index)
    app.register_blueprint(blue_user)
    app.register_blueprint(blue_admin)

    # 鉤子函數 before_first_request
    @app.before_first_request
    def before_first():
        print("app.before_first")

    # 鉤子函數 before_request
    @app.before_request
    def before():
        print("app.before")

    # 鉤子函數 after_request
    @app.after_request
    def after(response):
        print("app.after")
        return response

    # 鉤子函數 teardown_request
    @app.teardown_request
    def teardown(e):
        print("app.teardown")

    return app
    

E:\workspace\pycharm\Demo\manage.py

from application import create_app
from application.config import *

app = create_app(DevelopmentConfig)


@app.route("/app")
def app_test(): 
    print("app.app_test")
    return "app.app_test"


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


運行結果

app.before_first
app.before
app.app_test
app.after
app.teardown
127.0.0.1 - - [06/Jul/2018 14:56:25] "GET /app HTTP/1.1" 200 -

根據運行結果, 可以得出鉤子函數在一次請求中的執行順序, 如下圖

02-鉤子函數執行順序.png

before_first_request

在對應用程序實例的第一個請求之前注冊要運行的函數, 只會執行一次


    #: A lists of functions that should be called at the beginning of the
    #: first request to this instance.  To register a function here, use
    #: the :meth:`before_first_request` decorator.
    #:
    #: .. versionadded:: 0.8
    self.before_first_request_funcs = []
    
    @setupmethod
    def before_first_request(self, f):
        """Registers a function to be run before the first request to this
        instance of the application.

        .. versionadded:: 0.8
        """
        self.before_first_request_funcs.append(f) 


將要運行的函數存放到before_first_request_funcs 屬性中進行保存

before_request

在每個請求之前注冊一個要運行的函數, 每一次請求都會執行

    
   #: A dictionary with lists of functions that should be called at the
   #: beginning of the request.  The key of the dictionary is the name of
   #: the blueprint this function is active for, `None` for all requests.
   #: This can for example be used to open database connections or
   #: getting hold of the currently logged in user.  To register a
   #: function here, use the :meth:`before_request` decorator.
   self.before_request_funcs = {} 
    
   @setupmethod
    def before_request(self, f):
        """Registers a function to run before each request."""
        self.before_request_funcs.setdefault(None, []).append(f)
        return f


將要運行的函數存放在字典中, None 為鍵的列表中存放的是整個應用的所有請求都要運行的函數.

after_request

在每個請求之后注冊一個要運行的函數, 每次請求都會執行. 需要接收一個 Response 類的對象作為參數 并返回一個新的Response 對象 或者 直接返回接受到的Response 對象


    #: A dictionary with lists of functions that should be called after
    #: each request.  The key of the dictionary is the name of the blueprint
    #: this function is active for, `None` for all requests.  This can for
    #: example be used to open database connections or getting hold of the
    #: currently logged in user.  To register a function here, use the
    #: :meth:`after_request` decorator.
    self.after_request_funcs = {}

    @setupmethod
    def after_request(self, f):
        """Register a function to be run after each request.  Your function
        must take one parameter, a :attr:`response_class` object and return
        a new response object or the same (see :meth:`process_response`).

        As of Flask 0.7 this function might not be executed at the end of the
        request in case an unhandled exception occurred.
        """
        self.after_request_funcs.setdefault(None, []).append(f)
        return f


將要運行的函數存放在字典中, None 為鍵的列表中存放的是整個應用的所有請求都要運行的函數.

teardown_request

注冊一個函數在每個請求的末尾運行,不管是否有異常, 每次請求的最后都會執行.


    #: A dictionary with lists of functions that are called after
    #: each request, even if an exception has occurred. The key of the
    #: dictionary is the name of the blueprint this function is active for,
    #: `None` for all requests. These functions are not allowed to modify
    #: the request, and their return values are ignored. If an exception
    #: occurred while processing the request, it gets passed to each
    #: teardown_request function. To register a function here, use the
    #: :meth:`teardown_request` decorator.
    #:
    #: .. versionadded:: 0.7
    self.teardown_request_funcs = {}

    @setupmethod
    def teardown_request(self, f):
        """Register a function to be run at the end of each request,
        regardless of whether there was an exception or not.  These functions
        are executed when the request context is popped, even if not an
        actual request was performed.
        """
        self.teardown_request_funcs.setdefault(None, []).append(f)
        return f

將要運行的函數存放在字典中, None 為鍵的列表中存放的是整個應用的所有請求都要運行的函數.

app.run


    def run(self, host=None, port=None, debug=None, **options):
        """Runs the application on a local development server.  If the
        :attr:`debug` flag is set the server will automatically reload
        for code changes and show a debugger in case an exception happened.
 
        .. versionchanged:: 0.10
           The default port is now picked from the ``SERVER_NAME`` variable.

        :param host: the hostname to listen on. Set this to ``'0.0.0.0'`` to
                     have the server available externally as well. Defaults to
                     ``'127.0.0.1'``.
        :param port: the port of the webserver. Defaults to ``5000`` or the
                     port defined in the ``SERVER_NAME`` config variable if
                     present.
        :param debug: if given, enable or disable debug mode.
                      See :attr:`debug`.
        :param options: the options to be forwarded to the underlying
                        Werkzeug server.  See
                        :func:`werkzeug.serving.run_simple` for more
                        information.
        """
        from werkzeug.serving import run_simple

        # 主機地址 默認為'127.0.0.1'
        if host is None:
            host = '127.0.0.1'
        
        # 端口 默認為5000
        if port is None:
            server_name = self.config['SERVER_NAME']
            if server_name and ':' in server_name:
                port = int(server_name.rsplit(':', 1)[1])
            else:
                port = 5000

        # debug 默認為false
        if debug is not None:
            self.debug = bool(debug)

        # 設置 use_reloader use_debugger
        options.setdefault('use_reloader', self.debug)
        options.setdefault('use_debugger', self.debug)

        # 參數設置完成調用run_simple 啟動服務
        try:
            run_simple(host, port, self, **options)
        finally:
            # reset the first request information if the development server
            # resetted normally.  This makes it possible to restart the server
            # without reloader and that stuff from an interactive shell.
            self._got_first_request = False


run_simple



def run_simple(hostname, port, application, use_reloader=False,
               use_debugger=False, use_evalex=True,
               extra_files=None, reloader_interval=1,
               reloader_type='auto', threaded=False,
               processes=1, request_handler=None, static_files=None,
               passthrough_errors=False, ssl_context=None):
    """Start a WSGI application. Optional features include a reloader,
    multithreading and fork support.

    ...( 省略 )...
    """

    # 檢查端口
    if not isinstance(port, int):
        raise TypeError('port must be an integer')
    
    # 是否開啟debug
    if use_debugger:
        from werkzeug.debug import DebuggedApplication
        application = DebuggedApplication(application, use_evalex)

    # 設置靜態資源文件
    if static_files:
        from werkzeug.wsgi import SharedDataMiddleware
        application = SharedDataMiddleware(application, static_files)

    # ...(省略)...


DebuggedApplication


    def __init__(self, app, evalex=False, request_key='werkzeug.request',
                 console_path='/console', console_init_func=None,
                 show_hidden_frames=False, lodgeit_url=None,
                 pin_security=True, pin_logging=True):
        if lodgeit_url is not None:
            from warnings import warn
            warn(DeprecationWarning('Werkzeug now pastes into gists.'))
        if not console_init_func:
            console_init_func = None
        self.app = app
        self.evalex = evalex
        self.frames = {}
        self.tracebacks = {}
        self.request_key = request_key
        self.console_path = console_path
        self.console_init_func = console_init_func
        self.show_hidden_frames = show_hidden_frames
        self.secret = gen_salt(20)
        self._failed_pin_auth = 0

        self.pin_logging = pin_logging
        if pin_security:
            # Print out the pin for the debugger on standard out.
            if os.environ.get('WERKZEUG_RUN_MAIN') == 'true' and \
               pin_logging:
                _log('warning', ' * Debugger is active!')
                if self.pin is None:
                    _log('warning', ' * Debugger PIN disabled.  '
                         'DEBUGGER UNSECURED!')
                else:
                    _log('info', ' * Debugger PIN: %s' % self.pin)
        else:
            self.pin = None


 def debug_application(self, environ, start_response):
        """Run the application and conserve the traceback frames."""
        app_iter = None
        try: 
            app_iter = self.app(environ, start_response)
            for item in app_iter:
                yield item
            if hasattr(app_iter, 'close'):
                app_iter.close()
        except Exception:
            if hasattr(app_iter, 'close'):
                app_iter.close()
            traceback = get_current_traceback(
                skip=1, show_hidden_frames=self.show_hidden_frames,
                ignore_system_exceptions=True)
            for frame in traceback.frames:
                self.frames[frame.id] = frame
            self.tracebacks[traceback.id] = traceback

            try:
                start_response('500 INTERNAL SERVER ERROR', [
                    ('Content-Type', 'text/html; charset=utf-8'),
                    # Disable Chrome's XSS protection, the debug
                    # output can cause false-positives.
                    ('X-XSS-Protection', '0'),
                ])
            except Exception:
                # if we end up here there has been output but an error
                # occurred.  in that situation we can do nothing fancy any
                # more, better log something into the error log and fall
                # back gracefully.
                environ['wsgi.errors'].write(
                    'Debugging middleware caught exception in streamed '
                    'response at a point where response headers were already '
                    'sent.\n')
            else:
                is_trusted = bool(self.check_pin_trust(environ))
                yield traceback.render_full(evalex=self.evalex,
                                            evalex_trusted=is_trusted,
                                            secret=self.secret) \
                    .encode('utf-8', 'replace')

            traceback.log(environ['wsgi.errors'])

通過app.run 可以得知 run_simple 方法將Flask 類的實例對象作為實參傳遞給DebuggedApplication 類,初始化了一個DebuggedApplication 類的實例對象對象application. 結合debug_application 的描述可以得知, debug_application方法會被調用 ( WSGIRequestHandler 類 run_wsgi 方法 的內部方法 execute ).

app_iter = self.app(environ, start_response), self是DebuggedApplication 類的實例對象的引用, app 從__init__ 魔法方法得知是Flask 類的實例對象, 所以app(environ, start_response) 會調用Flask 類的魔法方法__call__[1]

Flask 類的__call__ 魔法方法

 def __call__(self, environ, start_response):
        """Shortcut for :attr:`wsgi_app`."""
        return self.wsgi_app(environ, start_response)

    
   def wsgi_app(self, environ, start_response):
        """The actual WSGI application.  This is not implemented in
        `__call__` so that middlewares can be applied without losing a
        reference to the class.  So instead of doing this::

            app = MyMiddleware(app)

        It's a better idea to do this instead::

            app.wsgi_app = MyMiddleware(app.wsgi_app)

        Then you still have the original application object around and
        can continue to call methods on it.

        .. versionchanged:: 0.7
           The behavior of the before and after request callbacks was changed
           under error conditions and a new callback was added that will
           always execute at the end of the request, independent on if an
           error occurred or not.  See :ref:`callbacks-and-errors`.

        :param environ: a WSGI environment
        :param start_response: a callable accepting a status code,
                               a list of headers and an optional
                               exception context to start the 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.make_response(self.handle_exception(e))
            return response(environ, start_response)
        finally:
            if self.should_ignore_error(error):
                error = None
            ctx.auto_pop(error)


full_dispatch_request


 def full_dispatch_request(self):
        """Dispatches the request and on top of that performs request
        pre and postprocessing as well as HTTP exception catching and
        error handling.

        .. versionadded:: 0.7
        """
        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)
        response = self.make_response(rv)
        response = self.process_response(response)
        request_finished.send(self, response=response)
        return response

 @property
 def got_first_request(self):
         """This attribute is set to `True` if the application started
         handling the first request.
    
         .. versionadded:: 0.8
         """
         return self._got_first_request

  def try_trigger_before_first_request_functions(self):
        """Called before each request and will ensure that it triggers
        the :attr:`before_first_request_funcs` and only exactly once per
        application instance (which means process usually).

        :internal:
        """
        if self._got_first_request:
            return
        with self._before_request_lock:
            if self._got_first_request:
                return
            self._got_first_request = True
            for func in self.before_first_request_funcs:
                func()

    
    def preprocess_request(self):
        """Called before the actual request dispatching and will
        call every as :meth:`before_request` decorated function.
        If any of these function returns a value it's handled as
        if it was the return value from the view and further
        request handling is stopped.

        This also triggers the :meth:`url_value_processor` functions before
        the actual :meth:`before_request` functions are called.
        """
        bp = _request_ctx_stack.top.request.blueprint

        funcs = self.url_value_preprocessors.get(None, ())
        if bp is not None and bp in self.url_value_preprocessors:
            funcs = chain(funcs, self.url_value_preprocessors[bp])
        for func in funcs:
            func(request.endpoint, request.view_args)

        funcs = self.before_request_funcs.get(None, ())
        if bp is not None and bp in self.before_request_funcs:
            funcs = chain(funcs, self.before_request_funcs[bp])
        for func in funcs:
            rv = func()
            if rv is not None:
                return rv


    def dispatch_request(self):
        """Does the request dispatching.  Matches the URL and returns the
        return value of the view or error handler.  This does not have to
        be a response object.  In order to convert the return value to a
        proper response object, call :func:`make_response`.

        .. versionchanged:: 0.7
           This no longer does the exception handling, this code was
           moved to the new :meth:`full_dispatch_request`.
        """
        req = _request_ctx_stack.top.request
        if req.routing_exception is not None:
            self.raise_routing_exception(req)
        rule = req.url_rule
        # if we provide automatic options for this URL and the
        # request came with the OPTIONS method, reply automatically
        if getattr(rule, 'provide_automatic_options', False) \
           and req.method == 'OPTIONS':
            return self.make_default_options_response()
        # otherwise dispatch to the handler for that endpoint
        return self.view_functions[rule.endpoint](**req.view_args)
 
 
    def process_response(self, response):
        """Can be overridden in order to modify the response object
        before it's sent to the WSGI server.  By default this will
        call all the :meth:`after_request` decorated functions.

        .. versionchanged:: 0.5
           As of Flask 0.5 the functions registered for after request
           execution are called in reverse order of registration.

        :param response: a :attr:`response_class` object.
        :return: a new response object or the same, has to be an
                 instance of :attr:`response_class`.
        """
        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.save_session(ctx.session, response)
        return response


通過 full_dispatch_request 方法可以知道,

  • try_trigger_before_first_request_functions 方法通過判斷 _got_first_request 屬性, 決定是不是要執行before_first_request_funcs 列表( 鉤子函數before_first_request ) 的方法
  • 調用preprocess_request 方法, < self.before_request_funcs.get(None, ()) > 從before_request_funcs 字典( 鉤子函數 before_request ) 得到鍵為None的方法列表
  • 調用dispatch_request 根據路由規則調用指定的視圖函數
  • 調用process_response 方法,
        # 從after_request_funcs 字典( 鉤子函數 after_request )  得到鍵為None的方法列表
        if None in self.after_request_funcs:
            funcs = chain(funcs, reversed(self.after_request_funcs[None]))
        
        # 遍歷,執行,并且將response 對象作為實參進行傳遞
        for handler in funcs:
            response = handler(response)


ctx.auto_pop(error)

    
   # wsgi_app 方法的部分代碼, Flask 類的request_context 方法  
   ctx = self.request_context(environ)
   ctx.auto_pop(error)

   # ctx 是RequestContext 類的實例對象,  Flask 類的request_context 方法  
   def request_context(self, environ): 
        return RequestContext(self, environ)
   
   # RequestContext 類的__init__魔法方法, 
   # app形參接收的是Flask 類的實例對象賦值給RequestContext 類的屬性self.app
   def __init__(self, app, environ, request=None):

   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=None):
        """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()

        clear_request = False
        if not self._implicit_app_ctx_stack:
            self.preserved = False
            self._preserved_exc = None
            if exc is None:
                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
        
        # ...(省略)...

 

結合wsgi_app 方法,可以得知無論是否出錯RequestContext 類的auto_pop 都會被調用. auto_pop 調用了self.pop, self.pop 通過判斷調用了self.app.do_teardown_request(exc) , 既調用了Flask 類的do_teardown_request 方法

do_teardown_request


    def do_teardown_request(self, exc=None):
        """Called after the actual request dispatching and will
        call every as :meth:`teardown_request` decorated function.  This is
        not actually called by the :class:`Flask` object itself but is always
        triggered when the request context is popped.  That way we have a
        tighter control over certain resources under testing environments.

        .. versionchanged:: 0.9
           Added the `exc` argument.  Previously this was always using the
           current exception information.
        """
        if exc is None:
            exc = sys.exc_info()[1]
        funcs = reversed(self.teardown_request_funcs.get(None, ()))
        bp = _request_ctx_stack.top.request.blueprint
        if bp is not None and bp in self.teardown_request_funcs:
            funcs = chain(funcs, reversed(self.teardown_request_funcs[bp]))
        for func in funcs:
            rv = func(exc)
        request_tearing_down.send(self, exc=exc)


do_teardown_request 方法, 從 < self.teardown_request_funcs > 字典 ( 鉤子函數 teardown_request ) 中得到方法列表( self.teardown_request_funcs.get(None, ()) ), 遍歷,執行,并且傳遞一個Exception 類的實例對象exc

小結

對于應用來說有四個鉤子函數

  • before_first_request
  • before_request
  • after_request
  • teardown_request

鉤子函數扮演了工人的角色, 將通過裝飾器裝飾的函數存儲到指定的容器( 字典或者列表 ), 程序在處理請求的過程中會直接對容器進行操作, 而不會對鉤子函數進行調用.

Blueprint 鉤子函數

E:\workspace\pycharm\Demo\modules_init_.py


from flask import Blueprint

blue_index = Blueprint("index", __name__)
blue_user = Blueprint("user", __name__, url_prefix="/user")
blue_admin = Blueprint("admin", __name__, url_prefix="/admin")

from . import views_index
from . import views_user
from . import views_admin


@blue_index.before_app_first_request
def before_app_first():
    print("before_app_first")


@blue_index.before_app_request
def before_app():
    print("before_app")


@blue_index.before_request
def before():
    print("before")


@blue_index.after_app_request
def after_app(response):
    print("after_app")
    return response


@blue_index.after_request
def after(response):
    print("after")
    return response


@blue_admin.teardown_app_request
def teardown_app(e):
    print("teardown_app")


@blue_admin.teardown_request
def teardown(e):
    print("teardown")


運行結果


before_app_first
before_app
before
after
after_app
teardown_app
teardown


從結果上來看,七個鉤子都被執行了.

根據Blueprint 鉤子函數的運行結果以及前面對應用的鉤子函數的理解,

  • 推測1 : Blueprint 鉤子函數其內部應該也是對某一個容器進行更改,
  • 推測2 : 按照打印順序可以得出另一個推測, 如下圖, 藍圖的請求鉤子是在應用的請求鉤子的基礎上增加了自己的鉤子,共同構成了藍圖的七個請求鉤子.
03-藍圖的請求鉤子.png

before_app_first_request


    def before_app_first_request(self, f):
        """Like :meth:`Flask.before_first_request`.  Such a function is
        executed before the first request to the application.
        """
        # app.before_first_request_funcs
        self.record_once(lambda s: s.app.before_first_request_funcs.append(f))
        return f


before_app_request


    def before_app_request(self, f):
        """Like :meth:`Flask.before_request`.  Such a function is executed
        before each request, even if outside of a blueprint.
        """
        # app.before_request_funcs
        self.record_once(lambda s: s.app.before_request_funcs
            .setdefault(None, []).append(f))
        return f


before_request


    def before_request(self, f):
        """Like :meth:`Flask.before_request` but for a blueprint.  This function
        is only executed before each request that is handled by a function of
        that blueprint.
        """
        # app.before_request_funcs
        self.record_once(lambda s: s.app.before_request_funcs
            .setdefault(self.name, []).append(f))
        return f


after_app_request


    def after_app_request(self, f):
        """Like :meth:`Flask.after_request` but for a blueprint.  Such a function
        is executed after each request, even if outside of the blueprint.
        """
        # app.after_request_funcs
        self.record_once(lambda s: s.app.after_request_funcs
            .setdefault(None, []).append(f))
        return f


after_request


    def after_request(self, f):
        """Like :meth:`Flask.after_request` but for a blueprint.  This function
        is only executed after each request that is handled by a function of
        that blueprint.
        """
        # app.after_request_funcs
        self.record_once(lambda s: s.app.after_request_funcs
            .setdefault(self.name, []).append(f))
        return f


teardown_app_request


 def teardown_app_request(self, f):
        """Like :meth:`Flask.teardown_request` but for a blueprint.  Such a
        function is executed when tearing down each request, even if outside of
        the blueprint.
        """
        # app.teardown_request_funcs
        self.record_once(lambda s: s.app.teardown_request_funcs
            .setdefault(None, []).append(f))
        return f


teardown_request


 def teardown_request(self, f):
        """Like :meth:`Flask.teardown_request` but for a blueprint.  This
        function is only executed when tearing down requests handled by a
        function of that blueprint.  Teardown request functions are executed
        when the request context is popped, even when no actual request was
        performed.
        """
        # app.teardown_request_funcs
        self.record_once(lambda s: s.app.teardown_request_funcs
            .setdefault(self.name, []).append(f))
        return f


從上面可以看出藍圖的鉤子函數和應用的鉤子函數一樣都是在操作相同容器, 證明我們的推測1 是正確的. 既然都是在修改相同的容器, 結合在分析應用鉤子函數的時候我們知道了wsgi_app 中和鉤子函數有關的兩個方法full_dispatch_request 和 ctx.auto_pop(error) , 所以接下來就分析這兩個方法.

full_dispatch_request


   def full_dispatch_request(self):
        """Dispatches the request and on top of that performs request
        pre and postprocessing as well as HTTP exception catching and
        error handling.

        .. versionadded:: 0.7
        """
        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)
        response = self.make_response(rv)
        response = self.process_response(response)
        request_finished.send(self, response=response)
        return response


從full_dispatch_request 中得到和鉤子函數有關的函數,按照順序分別是try_trigger_before_first_request_functions --> preprocess_request --> dispatch_request --> process_response,

app.before_first_request 和 blue.before_app_first_request

**try_trigger_before_first_request_functions **


 def try_trigger_before_first_request_functions(self):
        """Called before each request and will ensure that it triggers
        the :attr:`before_first_request_funcs` and only exactly once per
        application instance (which means process usually).

        :internal:
        """
        if self._got_first_request:
            return
        with self._before_request_lock:
            if self._got_first_request:
                return
            self._got_first_request = True
            for func in self.before_first_request_funcs:
                func()
 
                

app.before_first_request

  def before_first_request(self, f):
        """Registers a function to be run before the first request to this
        instance of the application.

        .. versionadded:: 0.8
        """
        self.before_first_request_funcs.append(f)


blue.before_app_first_request


  def before_app_first_request(self, f):
        """Like :meth:`Flask.before_first_request`.  Such a function is
        executed before the first request to the application.
        """
        self.record_once(lambda s: s.app.before_first_request_funcs.append(f))
        return f


從上面的三個方法可以看出app.before_first_request 和 blue.before_app_first_request 都是將func 存儲到before_first_request_funcs 列表中,并且通過try_trigger_before_first_request_functions 方法可以得知app 和blue 同等對待,并沒有區別.

app.before_request / blue.before_app_request / blue.before_request

**preprocess_request **


 def preprocess_request(self):
        """Called before the actual request dispatching and will
        call every as :meth:`before_request` decorated function.
        If any of these function returns a value it's handled as
        if it was the return value from the view and further
        request handling is stopped.

        This also triggers the :meth:`url_value_processor` functions before
        the actual :meth:`before_request` functions are called.
        """
        bp = _request_ctx_stack.top.request.blueprint

        funcs = self.url_value_preprocessors.get(None, ())
        if bp is not None and bp in self.url_value_preprocessors:
            funcs = chain(funcs, self.url_value_preprocessors[bp])
        for func in funcs:
            func(request.endpoint, request.view_args)

        funcs = self.before_request_funcs.get(None, ())
        if bp is not None and bp in self.before_request_funcs:
            funcs = chain(funcs, self.before_request_funcs[bp])
        for func in funcs:
            rv = func()
            if rv is not None:
                return rv


app.before_request


  def before_request(self, f):
        """Registers a function to run before each request."""
        self.before_request_funcs.setdefault(None, []).append(f)
        return f


blue.before_app_request


    def before_app_request(self, f):
        """Like :meth:`Flask.before_request`.  Such a function is executed
        before each request, even if outside of a blueprint.
        """
        self.record_once(lambda s: s.app.before_request_funcs
            .setdefault(None, []).append(f))
        return f


blue.before_request


 def before_request(self, f):
        """Like :meth:`Flask.before_request` but for a blueprint.  This function
        is only executed before each request that is handled by a function of
        that blueprint.
        """
        self.record_once(lambda s: s.app.before_request_funcs
            .setdefault(self.name, []).append(f))
        return f


雖然app.before_request / blue.before_app_request / blue.before_request都是將方法存儲到before_request_funcs 字典中, 但是blue.before_request 是將方法存儲到以self.name( 藍圖名 ) 為鍵的列表中.

preprocess_request 方法, 先得到None 為鍵的函數列表( funcs = self.before_request_funcs.get(None, ()) ), 然后判斷藍圖 ( if bp is not None and bp in self.before_request_funcs ) , 遍歷并調用方法.

test1


from itertools import chain

c = chain([1, 2, 3], [4, 5, 6])
for i in c:
    print(i)

# 運行結果
# 1
# 2
# 3
# 4
# 5
# 6


結合前面的運行結果可以得出,雖然chain 看不到源碼,但是結合test1 的運行結果可以得知chain 的作用是將兩個列表進行了鏈接, 因為以None 為鍵的函數列表在前,所以會先被調用執行, 以藍圖名為鍵的函數列表被鏈接到后面,所以后被調用. 所以運行結果表現為先打印before_app 后打印before .

preprocess_request 執行完成會調用視圖函數, 不對視圖函數部分做研究

blue.after_request / blue.after_app_request / app.after_request

process_response


   def process_response(self, response):
        """Can be overridden in order to modify the response object
        before it's sent to the WSGI server.  By default this will
        call all the :meth:`after_request` decorated functions.

        .. versionchanged:: 0.5
           As of Flask 0.5 the functions registered for after request
           execution are called in reverse order of registration.

        :param response: a :attr:`response_class` object.
        :return: a new response object or the same, has to be an
                 instance of :attr:`response_class`.
        """
        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.save_session(ctx.session, response)
        return response

    

blue.after_request


    def after_request(self, f):
        """Like :meth:`Flask.after_request` but for a blueprint.  This function
        is only executed after each request that is handled by a function of
        that blueprint.
        """
        self.record_once(lambda s: s.app.after_request_funcs
            .setdefault(self.name, []).append(f))
        return f


blue.after_app_request


    def after_app_request(self, f):
        """Like :meth:`Flask.after_request` but for a blueprint.  Such a function
        is executed after each request, even if outside of the blueprint.
        """
        self.record_once(lambda s: s.app.after_request_funcs
            .setdefault(None, []).append(f))
        return f


app.after_request


  def after_request(self, f):
        """Register a function to be run after each request.  Your function
        must take one parameter, a :attr:`response_class` object and return
        a new response object or the same (see :meth:`process_response`).

        As of Flask 0.7 this function might not be executed at the end of the
        request in case an unhandled exception occurred.
        """
        self.after_request_funcs.setdefault(None, []).append(f)
        return f


blue.after_request / blue.after_app_request / app.after_request 都是將方法存儲到after_request_funcs 字典中, 但是blue.after_request是將方法存儲到以self.name( 藍圖名 ) 為鍵的列表中.

process_response 方法, 先判斷藍圖名然后使用chain 鏈接以藍圖名為鍵的函數列表, 然后判斷鏈接以None 為鍵的函數列表. 所以會先打印after_request 再打印after_app_request

app.teardown_request / blue.teardown_app_request / blue.teardown_request

ctx.auto_pop(error) 和鉤子函數有關的主要部分是Flask 類的do_teardown_request 方法, 所以著重探究do_teardown_request 方法

do_teardown_request


   def do_teardown_request(self, exc=None):
        """Called after the actual request dispatching and will
        call every as :meth:`teardown_request` decorated function.  This is
        not actually called by the :class:`Flask` object itself but is always
        triggered when the request context is popped.  That way we have a
        tighter control over certain resources under testing environments.

        .. versionchanged:: 0.9
           Added the `exc` argument.  Previously this was always using the
           current exception information.
        """
        if exc is None:
            exc = sys.exc_info()[1]
        funcs = reversed(self.teardown_request_funcs.get(None, ()))
        bp = _request_ctx_stack.top.request.blueprint
        if bp is not None and bp in self.teardown_request_funcs:
            funcs = chain(funcs, reversed(self.teardown_request_funcs[bp]))
        for func in funcs:
            rv = func(exc)
        request_tearing_down.send(self, exc=exc)



blue.teardown_app_request


    def teardown_app_request(self, f):
        """Like :meth:`Flask.teardown_request` but for a blueprint.  Such a
        function is executed when tearing down each request, even if outside of
        the blueprint.
        """
        self.record_once(lambda s: s.app.teardown_request_funcs
            .setdefault(None, []).append(f))
        return f


blue.teardown_request


    def teardown_request(self, f):
        """Like :meth:`Flask.teardown_request` but for a blueprint.  This
        function is only executed when tearing down requests handled by a
        function of that blueprint.  Teardown request functions are executed
        when the request context is popped, even when no actual request was
        performed.
        """
        self.record_once(lambda s: s.app.teardown_request_funcs
            .setdefault(self.name, []).append(f))
        return f


app.teardown_request


 def teardown_request(self, f):
        """Register a function to be run at the end of each request,
        regardless of whether there was an exception or not.  These functions
        are executed when the request context is popped, even if not an
        actual request was performed.

        Example::

            ctx = app.test_request_context()
            ctx.push()
            ...
            ctx.pop()

        When ``ctx.pop()`` is executed in the above example, the teardown
        functions are called just before the request context moves from the
        stack of active contexts.  This becomes relevant if you are using
        such constructs in tests.

        Generally teardown functions must take every necessary step to avoid
        that they will fail.  If they do execute code that might fail they
        will have to surround the execution of these code by try/except
        statements and log occurring errors.

        When a teardown function was called because of a exception it will
        be passed an error object.

        .. admonition:: Debug Note

           In debug mode Flask will not tear down a request on an exception
           immediately.  Instead if will keep it alive so that the interactive
           debugger can still access it.  This behavior can be controlled
           by the ``PRESERVE_CONTEXT_ON_EXCEPTION`` configuration variable.
        """
        self.teardown_request_funcs.setdefault(None, []).append(f)
        return f



app.teardown_request/ blue.teardown_app_request/ blue.teardown_request 都是將方法存儲到after_request_funcs 字典中, 但是blue.teardown_request 是將方法存儲到以self.name( 藍圖名 ) 為鍵的列表中.

do_teardown_request 方法, 先得到以None 為鍵的函數列表, 然后通過chain 鏈接以藍圖名 為鍵的函數列表. 所以會先打印after_app_request 再打印after_request

經過以上探究可以證明推測2 是正確的. 這個順序可以理解為請求兩部分應用部分 和藍圖部分. 當請求需要進入藍圖時, 先要通過應用部分的認證, 才能訪問藍圖部分, 訪問完藍圖之后, 要先從藍圖出來, 再經過應用返回響應. 如果出錯, 先檢查應用是否有錯, 如果應用出錯, 則返回, 否則返回藍圖的錯誤.

這個過程有點像你去拜訪朋友, 要現在門衛登記, 然后經過允許才能進入小區, 到達朋友家門前. 敲門, 朋友給你開門才正式進入朋友家. 拜訪結束你要從朋友家出來 ( 是走出來 ) , 要先出朋友的家門, 到達門衛, 然后出小區,回家. 如果你發現丟了東西, 要先問問門衛有沒撿到失物, 如果有你就不用上樓找朋友了. 如果沒有你就需要上樓問一問朋友了.

總結

  • 鉤子函數分為應用鉤子函數 以及藍圖鉤子函數

    • 應用鉤子函數 4個
      • app.before_first_request
      • app.before_request
      • app.after_request
      • app.teardown_request
    • 藍圖鉤子函數 7個
      • blue.before_app_first_request
      • blue.before_app_request
      • blue.before_request
      • blue.after_request
      • blue.after_app_request
      • blue.teardown_app_request
      • blue.teardown_request
  • 在不發生錯誤的前提下, blue.before_app_first_request / blue.before_app_request / blue.before_request / blue.after_request / blue.after_app_request 都可以返回響應


到此結? DragonFangQy 2018.7.8


  1. __call__ ?

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 22年12月更新:個人網站關停,如果仍舊對舊教程有興趣參考 Github 的markdown內容[https://...
    tangyefei閱讀 35,215評論 22 257
  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,869評論 18 139
  • Lua 5.1 參考手冊 by Roberto Ierusalimschy, Luiz Henrique de F...
    蘇黎九歌閱讀 13,882評論 0 38
  • 如何理解wsgi, Werkzeug, flask之間的關系 Flask是一個基于Python開發并且依賴jinj...
    Ghost_3538閱讀 722評論 0 6
  • 青春的大雨猝不及防的淋下,誰能又安然躲過?后來,我淋過很多雨,吻過很多花,看過很多地方的天空,遇見很多次愛,卻永遠...
    言又閱讀 363評論 3 3