扒源码 - 一个请求在flask中经历了什么

2020-04-22 10:42:02 浏览数 (1)

客户端发起一个请求,Flask 都干了什么呢?url 如何与视图进行绑定的?更多精彩文章请关注公众号『Pythonnote』或者『全栈技术精选』

1.过程简述

1) 创建请求上下文

2) 创建应用上下文

3) 把上下文压入栈

4) 执行请求钩子 before_first_request 的相关操作

5) 执行请求钩子 before_request 的相关操作

6) 路由

7) 执行请求钩子 after_request 的相关操作

8) 执行请求钩子 teardown_request 的相关操作

9) 把上下文弹出栈

10) 返回响应结果

2.过程详述

2.1 wsgi 接口

总所周知,客户端每次发起请求,服务器都会调用框架实现的 wsgi 接口进行通讯。在 Flask 中,每个请求都会先调用 Flask.__call__ 方法,而此方法又调用了 Flask.wsgi_app ,它便是 Flask 中的 wsgi 接口了。接下来我们结合源码进行说明。更多精彩文章请关注公众号『Pythonnote』或者『全栈技术精选』

小闫使用的版本为 flask1.1.2,以下所有源码都源自于此版本,并以 wsgi_app 方法展开描述。

代码语言:javascript复制
# flask/app.py

class Flask(_PackageBoundObject):

    ... 省略其他方法

    def wsgi_app(self, environ, start_response):
        """The actual WSGI application. 
        ... 此处省略一些说明
        :param environ: A WSGI environment. 环境字典,包含全部 HTTP 请求信息
        :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:  # noqa: B001
                error = sys.exc_info()[1]
                raise
            # 返回响应结果
            return response(environ, start_response)
        finally:
            if self.should_ignore_error(error):
                error = None
            # 上下文出栈
            ctx.auto_pop(error)

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

2.2 创建上下文

结合上面提供的 wsgi_app 源码,我们可以看到先执行了如下代码:

代码语言:javascript复制
ctx = self.request_context(environ)

此处便是创建上下文操作。框架会先去创建请求上下文,并去判断是否有应用上下文,以及应用上下文与当前应用是否一致,然后决定是否去创建一个应用上下文。如下便是依次进行调用的方法:

代码语言:javascript复制
# flask/app.py

class Flask(_PackageBoundObject):
    def request_context(self, environ):
        return RequestContext(self, environ)

# flask/ctx.py

class RequestContext(object):
    def push(self):
        top = _request_ctx_stack.top
        if top is not None and top.preserved:
            top.pop(top._preserved_exc)

        # 从栈中弹出一个应用上下文
        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()

        _request_ctx_stack.push(self)

        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)

        if self.url_adapter is not None:
            self.match_request()

2.3 请求钩子

如果在代码中定义了四种请求钩子,那么它们会按照如下顺序执行。

1.before_first_request:在处理第一个请求前执行

2.before_request:在每次请求前执行,在该装饰函数中,一旦return,视图函数不再执行

3.after_request:如果没有抛出错误,在每次请求后执行

代码语言:javascript复制
a.接受一个参数:视图函数作出的响应
b.在此函数中可以对响应值,在返回之前做最后一步处理,再返回

4.teardown_request:在每次请求后执行更多精彩文章请关注公众号『Pythonnote』或者『全栈技术精选』

代码语言:javascript复制
a.接受一个参数:用来接收错误信息

2.4 路由

wsgi_app 方法中如下代码便会进行请求分发:

代码语言:javascript复制
response = self.full_dispatch_request()

下面将所涉及到的方法源码依次列出:

代码语言:javascript复制
# flask/app.py

class Flask(_PackageBoundObject):
    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.
        """
        # 首次处理请求前的操作,通过 @before_first_request 定义,也就是我们的请求钩子
        self.try_trigger_before_first_request_functions()
        try:
            request_started.send(self)
            # 每次处理请求前进行的操作, 通过 @before_request 来定义
            rv = self.preprocess_request()
            if rv is None:
                # 调用 dispatch_request 函数匹配 url,执行请求调度
                rv = self.dispatch_request()
        except Exception as e:
            rv = self.handle_user_exception(e)
        # 调用 finalize_request 方法将视图函数的返回值转换成一个真正的响应对象
        return self.finalize_request(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`.
        """
        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 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:
            # 每次正常处理请求后进行的操作, 通过 @after_request 来定义
            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

2.5 路由表的创建

下面是一个最简单的视图,其作用便是在访问根目录时,返回 Hello World

代码语言:javascript复制
@app.route('/')
def hello_world():
    return 'Hello World!'

你有没有想过视图函数与 url 是如何绑定的呢?它是通过方法 add_url_rule 创建的路由规则,下面我们来看一下源码:

1) 先查看一下 @app.route 装饰器干了哪些工作:

代码语言:javascript复制
# flask/app.py

class Flask(_PackageBoundObject):
    def route(self, rule, **options):
        def decorator(f):
            # 获取 endpoint,可以看作为每个 view_func 的 ID
            endpoint = options.pop("endpoint", None)
            # 调用 add_url_rule 方法添加路由信息
            self.add_url_rule(rule, endpoint, f, **options)
            return f

        return decorator

2) 然后就来看看 add_url_rule 为何方神圣。更多精彩文章请关注公众号『Pythonnote』或者『全栈技术精选』

代码语言:javascript复制
# flask/app.py

class Flask(_PackageBoundObject):

    # 定义view_functions
    self.view_functions = {}
    # 定义url_map
    self.url_map = Map()

    @setupmethod
    def add_url_rule(
        self,
        rule,
        endpoint=None,
        view_func=None,
        provide_automatic_options=None,
        **options
    ):
        if endpoint is None:
            endpoint = _endpoint_from_view_func(view_func)
        options["endpoint"] = endpoint
        methods = options.pop("methods", None)

        # if the methods are not given and the view_func object knows its
        # methods we can use that instead.  If neither exists, we go with
        # a tuple of only ``GET`` as default.
        if methods is None:
            methods = getattr(view_func, "methods", None) or ("GET",)
        if isinstance(methods, string_types):
            raise TypeError(
                "Allowed methods have to be iterables of strings, "
                'for example: @app.route(..., methods=["POST"])'
            )
        methods = set(item.upper() for item in methods)

        # Methods that should always be added
        required_methods = set(getattr(view_func, "required_methods", ()))

        # starting with Flask 0.8 the view_func object can disable and
        # force-enable the automatic options handling.
        if provide_automatic_options is None:
            provide_automatic_options = getattr(
                view_func, "provide_automatic_options", None
            )

        if provide_automatic_options is None:
            if "OPTIONS" not in methods:
                provide_automatic_options = True
                required_methods.add("OPTIONS")
            else:
                provide_automatic_options = False

        # Add the required methods now.
        methods |= required_methods

        # 创建 rule
        rule = self.url_rule_class(rule, methods=methods, **options)
        rule.provide_automatic_options = provide_automatic_options

        # 把 rule 添加到 url_map
        self.url_map.add(rule)
        if view_func is not None:
            old_func = self.view_functions.get(endpoint)
            if old_func is not None and old_func != view_func:
                raise AssertionError(
                    "View function mapping is overwriting an "
                    "existing endpoint function: %s" % endpoint
                )
             # 把 view_func 添加到 view_functions 字典
            self.view_functions[endpoint] = view_func

0 人点赞