Flask源码之路由

  • 熟悉Django框架的应该都知道,Django为我们提供了非常良好的路由配置环境,只需在urls.py文件里添加url即可。
  • 但在Flask框架并没有给我们提供这样一个文件,我们需要自己来写路由,当然,Flask的路由是超级简单的,只需给视图函数加一个装饰器即可。
  • 接下来我们就启动一个最简单的Flask项目,直接上代码。
from flask import Flask

app = Flask(__name__)

@app.route('/')
def index():
    return 'OK'

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

app.run()方法的背后都执行了什么?

run方法中调用了werkzeug.serving模块的run_simple方法
image.png
run_simple中首先执行这部分代码:

    if use_reloader:
        # If we're not running already in the subprocess that is the
        # reloader we want to open up a socket early to make sure the
        # port is actually available.
        if os.environ.get('WERKZEUG_RUN_MAIN') != 'true':
            if port == 0 and not can_open_by_fd:
                raise ValueError('Cannot bind to a random port with enabled '
                                 'reloader if the Python interpreter does '
                                 'not support socket opening by fd.')

            # Create and destroy a socket so that any exceptions are
            # raised before we spawn a separate Python interpreter and
            # lose this ability.
            address_family = select_ip_version(hostname, port)
            s = socket.socket(address_family, socket.SOCK_STREAM)
            s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            s.bind(get_sockaddr(hostname, port, address_family))
            if hasattr(s, 'set_inheritable'):
                s.set_inheritable(True)

            # If we can open the socket by file descriptor, then we can just
            # reuse this one and our socket will survive the restarts.
            if can_open_by_fd:
                os.environ['WERKZEUG_SERVER_FD'] = str(s.fileno())
                s.listen(LISTEN_QUEUE)
                log_startup(s)
            else:
                s.close()

        # Do not use relative imports, otherwise "python -m werkzeug.serving"
        # breaks.
        from werkzeug._reloader import run_with_reloader
        run_with_reloader(inner, extra_files, reloader_interval,
                          reloader_type)
    else:
        inner()  # 通常会执行这个方法

再来看inner方法,通过make_server方法来创建WSGIServer的实例srv,然后调用server_forever方法:

    def inner():
        try:
            fd = int(os.environ['WERKZEUG_SERVER_FD'])
        except (LookupError, ValueError):
            fd = None
        srv = make_server(hostname, port, application, threaded,
                          processes, request_handler,
                          passthrough_errors, ssl_context,
                          fd=fd)
        if fd is None:
            log_startup(srv.socket)
        srv.serve_forever()

那么当请求进来时,代码流程是什么呢?

  • 首先,当用户在浏览器输入url回车时,会调用app实例的__call__方法:
    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."""
        # 请求每次进来时,先执行__call__方法,项目启动时不会执行
        return self.wsgi_app(environ, start_response)
  • __call__方法中又执行了wsgi_app方法:
    def wsgi_app(self, environ, start_response):
        """The actual WSGI application. This is not implemented in
        :meth:`__call__` so that middlewares can be applied without
        losing a reference to the app object. 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
            Teardown events for the request and app contexts are called
            even if an unhandled error occurs. Other events may not be
            called depending on when an error occurs during dispatch.
            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)
        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)
  • full_dispatch_request方法中又执行了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:
                # 此方法为具体的路由分发逻辑,根据url执行对应的视图函数,rv为视图函数执行结果的返回值
                rv = self.dispatch_request()
        except Exception as e:
            rv = self.handle_user_exception(e)
        # 最后执行finalize_request方法并返回
        return self.finalize_request(rv)
  • 切换到dispatch_request中:
        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`.
        """
        # 返回一个经过数据封装的Request对象
        req = _request_ctx_stack.top.request

        if req.routing_exception is not None:
            self.raise_routing_exception(req)
        # 得到<class 'werkzeug.routing.Rule'>的对象rule
        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
        # 从字典view_functions中获取endpoint对应的视图函数,加括号执行,并把相应的参数传进去
        return self.view_functions[rule.endpoint](**req.view_args)
  • 再来看最后执行的finalize_request方法,将返回数据进行封装:
    def finalize_request(self, rv, from_error_handler=False):
        """Given the return value from a view function this finalizes
        the request by converting it into a response and invoking the
        postprocessing functions.  This is invoked for both normal
        request dispatching as well as error handlers.

        Because this means that it might be called as a result of a
        failure a special safe mode is available which can be enabled
        with the `from_error_handler` flag.  If enabled, failures in
        response processing will be logged and otherwise ignored.

        :internal:
        """
        response = self.make_response(rv)
        try:
            response = self.process_response(response)
            request_finished.send(self, response=response)
        except Exception:
            if not from_error_handler:
                raise
            self.logger.exception('Request finalizing failed with an '
                                  'error while handling an error')
        return response

根据装饰器的特性,执行视图函数之前首先走装饰器

  • 注意:我们在代码中添加的装饰器是有参数的
  • 当访问根路径时,到@app.route('/')时,先执行app.route('/')方法

代码中的装饰器便是一个路由,那么它是怎样一个原理呢?进入Flask路由源码:

image.png

  • route方法返回一个函数体,@app.route('/')等价于@decorator
    此时index函数等价于:
@decorator
def index():
    return 'OK'
  • 这样一写就很清晰了,执行index前需执行decorator
  • 那么问题来了,Flask为什么不直接这样写呢,还要多写一个route方法?想必大家应该都看出来了,这里用到了python的闭包,目的就是把参数传递给decorator

接下来查看decorator方法

  • 第一步是得到endpoint参数的值,这就不多说了
  • 第二步执行了add_url_rule方法,并把路由,视图函数等当作参数传递进去
  • 最后返回视图函数

进入add_url_rule

  • 第一步:获取endpoint和methods
    image.png
    当我们没有设置endpoint时,会执行_endpoint_from_view_func方法,其内部返回值是:
    image.png
  • 继续往下走:
    image.png
到这里,装饰器逻辑完成后,就可以执行上面dispatch_request里的self.view_functions[rule.endpoint](**req.view_args)

猜你喜欢

转载自blog.csdn.net/weixin_43977375/article/details/90200812
今日推荐