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__

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,408评论 4 371
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,690评论 2 307
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 112,036评论 0 255
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,726评论 0 221
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,123评论 3 296
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 41,037评论 1 225
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,178评论 2 318
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,964评论 0 213
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,703评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,863评论 2 254
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,333评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,658评论 3 263
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,374评论 3 244
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,195评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,988评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,167评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,970评论 2 279

推荐阅读更多精彩内容

  • 22年12月更新:个人网站关停,如果仍旧对旧教程有兴趣参考 Github 的markdown内容[https://...
    tangyefei阅读 35,138评论 22 257
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,111评论 18 139
  • Lua 5.1 参考手册 by Roberto Ierusalimschy, Luiz Henrique de F...
    苏黎九歌阅读 13,522评论 0 38
  • 如何理解wsgi, Werkzeug, flask之间的关系 Flask是一个基于Python开发并且依赖jinj...
    Ghost_3538阅读 678评论 0 6
  • 青春的大雨猝不及防的淋下,谁能又安然躲过?后来,我淋过很多雨,吻过很多花,看过很多地方的天空,遇见很多次爱,却永远...
    言又阅读 318评论 3 3