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

简介: 客户端发起一个请求,Flask 都干了什么呢?url 如何与视图进行绑定的?

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 方法展开描述。

# 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 源码,我们可以看到先执行了如下代码:


ctx = self.request_context(environ)

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

# 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 请求钩子

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

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

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

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

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

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


a.接受一个参数:用来接收错误信息

2.4 路由

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


response = self.full_dispatch_request()

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

# 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

@app.route('/')
def hello_world():
    return 'Hello World!'

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

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

# 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 为何方神圣。

# 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
相关文章
|
4月前
|
前端开发 数据库 Python
使用 Python 的 Web 框架(如 Django 或 Flask)来建立后端接口,用于处理用户的请求,从数据库中查找答案并返回给前端界面
【1月更文挑战第13天】使用 Python 的 Web 框架(如 Django 或 Flask)来建立后端接口,用于处理用户的请求,从数据库中查找答案并返回给前端界面
97 7
|
19天前
|
网络架构 Python
在Flask中,如何定义路由并处理HTTP请求的不同方法(GET、POST等)?
【4月更文挑战第25天】在Flask中,使用`@app.route()`装饰器定义路由,如`/hello`,处理GET请求返回'Hello, World!'。通过添加`methods`参数,可处理不同HTTP方法,如POST请求。单一函数可处理多种方法,通过检查`request.method`区分。动态路由使用 `<variable_name>` 传递URL变量到视图函数。这些基础构成处理HTTP请求的Flask应用。
23 1
|
24天前
|
存储 JSON 数据安全/隐私保护
Flask Python:如何获取不同请求方式的参数
Flask Python:如何获取不同请求方式的参数
|
26天前
|
JSON 数据格式 Python
如何在Flask框架中定义路由和处理请求?
【4月更文挑战第18天】在Flask框架中,创建应用实例、定义路由和处理请求涉及5个步骤:1) 导入Flask并实例化应用;2) 使用`app.route()`装饰器定义路由,指定URL和HTTP方法;3) 编写视图函数处理请求逻辑;4) 视图函数返回响应内容,Flask会自动转换格式;5) 用`app.run()`启动服务器。
28 3
|
5月前
|
网络协议 测试技术 Python
PythonWeb开发基础(三)类Flask框架请求封装
类Flask框架请求封装 Web服务器 本质是个TCP服务器,监听在特定端口上 支持HTTP协议,能够将HTTP请求报文进行解析,能够把响应数据进行HTTP协议的报文封装并返回浏览器端。
49 0
|
5月前
|
JSON 数据格式 Python
flask 接收get请求, 以及返回 json格式
flask 接收get请求, 以及返回 json格式
56 0
|
7月前
|
中间件 测试技术 数据库
软件测试|测试平台开发-Flask 入门:Flask HTTP请求详解
软件测试|测试平台开发-Flask 入门:Flask HTTP请求详解
44 0
|
11月前
|
数据库 Python
python的flask的请求钩子的使用方法和使用场景
python的flask的请求钩子的使用方法和使用场景
105 0
|
11月前
|
开发框架 Python
Python的flask框架使用方法和内容解析(参数获取,请求头获取,请求方法等等)
Python的flask框架使用方法和内容解析(参数获取,请求头获取,请求方法等等)
202 0
|
Python Go 前端开发
源码解析flask的路由系统
当我们新建一个flask项目时,pycharm通常已经为项目定义了一个基本路由 @app.route('/') def hello_world(): return 'Hello World!' 此时在浏览器中输入地址http://127.
903 0