flask1.x源码详解

目录

1、flask1.x源码解析

        1.1、flask1.x源码之底层通信实现

        1.2、flask1.x源码之初始化启动阶段

1.2.1、实例化Flask对象及app对象

1.2.2、读取配置文件

1.2.3、存储特殊装饰器装饰的函数

1.2.4、完成路由与视图函数的映射

1.2.5、启动flask程序

1.3、flask1.x源码之上下文管理

1.3.1、请求上下文

1.3.2、应用上下文

1.3.3、flask上下文实现的原理

1.3.4、g对象的使用

1.4、flask1.x源码之执行视图函数

1.4.1、请求处理前预处理

1.4.2、执行视图函数

1.4.3、处理视图执行结果并返回最终响应结果

1.5、flask1.x源码之销毁上下文

1.5.1、销毁请求上下文之前的清理工作

1.5.2、销毁应用上下文之前的清理工作

1、flask1.x源码解析

前言:现在flask版本已经到了3.X为什么还要看flask1.X?,会用不就好了吗为什么要看源码?

  • 首先flask3.x是目前最新的版本,之前使用flask写的项目还可能是1.x或2.x版本的,不管是要维护还是迁移都需要知道flask1.x到底是怎么实现的;其次flask1.x是flask框架的最初版本,在其之后的其他版本不管做了什么修改和扩展都是在其基础了进行的,基本的框架是不会变的;后面在看其他版本的源码就可以很轻松快速的掌握,知道新增了什么特性,优化了什么地方。
  • 为什么要看源码呢?看源码不仅可以让我们从怎么使用的角度切换到为什么这么使用,知道底层这样使用是怎么实现的,如果需要对项目添加新的功能但是之前掌握的知识不能实现时,此时需要对flask框架进行二次开发或扩展功能,这时掌握源码的好处体现出来了,其次还可以学习到优秀的设计思想和编码风格,知道开源代码是如何写的,这对自己的编码和框架设计能力有很大的提升以及写出工程性的代码有非常大的帮助。

flask框架的大概工作原理:flask底层网络通信是基于werkzeug模块下的WSGI协议实现的,WSGI 协议规定了一个 Web 服务器与 Python Web 应用程序(flask框架)之间的通信接口。当 Web 服务器接收到一个 HTTP 请求时,它将通过 WSGI 接口将请求传递给 Python Web 应用程序(flask框架处理),应用程序再根据请求路由到对应的视图进行处理,并将处理完成的结果通过 WSGI 接口将响应发送回 Web 服务器,最终 Web 服务器再将响应返回给客户端。

浅谈flask项目部署:实际部署flask项目中会使用专门的WSGI服务器来部署flask以支持实现高并发的场景,虽然flask web程序本身支持多用户并发但是自身并发处理能力有限,所以会借助专门的WSGI服务器来实现,同时也可结合Nginx和容器技术实现更大场景的高并发;在实际项目中可根据项目自身情况采用合适的方式部署项目。

1.1、flask1.x源码之底层通信实现

说明:这里说明flask是如何借助werkzeug模块实现底层的网络通信,首先根据app.run()进入run函数的内部查看源码,可以看到run()函数的本质就是执行下面的代码:

from werkzeug.serving import run_simple
try:
    run_simple(host, port, self, **options)
finally:
    self._got_first_request = False
  • 可以看到是通过werkzeug模块的下的run_simple()函数实现底层的网络通信,run_simple()函数的第三个参数代表当前app对象,是一个可调用对象(这点很重要,也是WSGI与flask交互的入口)
  • 根据上面的说法,我们来实现一个只借助run_simple()与客户端(浏览器)通信,因为第三个参数需要个可调用对象,所以我们需要实现一个函数处理请求和响应并返回给客户端。实现如下:

from werkzeug.serving import run_simple
from werkzeug.wrappers import Response

def handle_request(environ, start_response):
    # 获取请求参数
    print(f"获取请求URL地址:{environ['REMOTE_ADDR']}:{environ['SERVER_PORT']}{environ['RAW_URI']}")
    # 封装响应对象
    response = Response("hello world")
    print("hello world")
    # 返回响应对象
    return response(environ, start_response)

if __name__ == '__main__':
    run_simple('127.0.0.1', 5000, handle_request)

  

  • 理解了上面的过程我们就很容易知道flask是如何与werkzeug交互处理请求的;回到flask源码我们知道run_simple()函数的第三个参数app对象本身同时它是一个可调用对象,一个类的实例对象是一个可调用对象那么这个类一定实现了__call__()方法;所以我们只要找到Flask类的__call__()方法就知道flask是如何处理请求的。

1.2、flask1.x源码之初始化启动阶段

  • 说明:在flask项目的初始化启动阶段是flask项目的准备阶段,把所有需要预选准备的数据(如:配置文件、视图、特殊函数装饰的内容等)加载到flask中(内存中)等待请求到来然后处理请求;该阶段主要完成以下工作:
    • 实例化Flask对象及app对象
    • 读取配置文件
    • 存储特殊装饰器装饰的函数
    • 完成路由与视图函数的映射
    • 启动flask程序
  • 示例flask代码如下:
from flask import Flask

# 创建app对象
app = Flask(__name__)

# 读取配置文件
app.config.from_object("config")

# 特殊装饰器装饰的函数,将函数before_index添加到对应的before_request_funcs字典中
@app.before_request
def before_index():
    print("在index视图函数之前执行")

# 视图函数,将路由与视图函数建立映射
@app.route("/index")
def index():
    print("index")
    return "index"

if __name__ == '__main__':
    # flask项目启动入口
    # 内部调用werkzeug的run_simple,内部创建socket,监听IP和端口,等待用户请求的到来
    # 一旦有用户请求,执行app.__call__方法【本质就是在run_simple()函数内调用第三个参数】
    app.run(debug=True)
    # 查看源码时可以通过下面的代码可以很方便的跳转到__call__()方法处
    # app.__call__()

1.2.1、实例化Flask对象及app对象

from flask import Flask

# 创建app对象
app = Flask(__name__)
  • 该过程完成app对象的创建和初始化,进入Flask类查看源码如下,可以看到这个过程完成了app对象需要使用到的所有属性的初始化。
def __init__(
    self,
    import_name,
    static_url_path=None,
    static_folder="static",
    static_host=None,
    host_matching=False,
    subdomain_matching=False,
    template_folder="templates",
    instance_path=None,
    instance_relative_config=False,
    root_path=None,
):
    # 初始化父类对象
    _PackageBoundObject.__init__(
        self, import_name, template_folder=template_folder, root_path=root_path
    )
    # 静态URL地址
    self.static_url_path = static_url_path
    # 静态目录
    self.static_folder = static_folder
    # app对象配置
    self.config = self.make_config(instance_relative_config)
    # 视图函数存放
    self.view_functions = {}
    # 在请求之前执行函数存放
    self.before_request_funcs = {}
    ........

1.2.2、读取配置文件

# 读取配置文件
app.config.from_object("config")
  • 进入源码查看如下:
if isinstance(obj, string_types):
    # 导入配置文件模块
    obj = import_string(obj)
    
# 读取配置文件中的所有键值对,并将键值对全部放到config对象(config对象是一个字典)
for key in dir(obj):
    if key.isupper():
        # 把配置文件中所有包含的config对象,赋值给app.config
        self[key] = getattr(obj, key)

1.2.3、存储特殊装饰器装饰的函数

# 特殊装饰器装饰的函数
@app.before_request
def before_index():
    print("在index视图函数之前执行")
  • 进入源码查看如下:
@setupmethod
def before_request(self, f):
    # 可以看到before_request装饰的函数是以字典列表的形式存储在before_request_funcs字典中
    self.before_request_funcs.setdefault(None, []).append(f)
    return f

1.2.4、完成路由与视图函数的映射

# 视图函数
@app.route("/index")
def index():
    print("index")
    return "index"
  • 进入源码查看如下:
def route(self, rule, **options):
    def decorator(f):
        # 获取指定一个特定的endpoint。如果没有显式地指定 endpoint,则 Flask 会默认使用视图函数的名称作为 endpoint。
        endpoint = options.pop("endpoint", None)
        # 1、将url = /index 和 methods = ["GET", "POST"] 和endpoint = "index" 封装到Rule对象
        # 2、将Rule对象添加到 app.url_map中
        # 3、把endpoint和函数的对应关系放到app.view_functions中
        self.add_url_rule(rule, endpoint, f, **options)
        return f
    return decorator

1.2.5、启动flask程序

if __name__ == '__main__':
    # flask项目启动入口
    # 内部调用werkzeug的run_simple,内部创建socket,监听IP和端口,等待用户请求的到来
    # 一旦有用户请求,执行app.__call__方法【本质就是在run_simple()函数内调用第三个参数】
    app.run(debug=True)
  • __call__()源码如下: 从源码可以看出要想知道请求到来时,flask是如何处理请求的,就需要进入self.wsgi_app()函数内部查看。
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)

1.3、flask1.x源码之上下文管理

说明:

  • flask的上下文管理是flask非常核心的概念,只有掌握了上下文管理才能真正理解flask的精髓;什么是上下文?比如我们阅读一篇文章,要分析其中某一段表达的含义,为了不断章取义,这时候我们就需要结合文章的上下文来分析,这里说的上下文和flask的上下文是一个道理,flask上下文就是保存在一次请求过程中所有的数据直到销毁,下面就来看看flask是如何实现上下文管理的。
  • flask上下文,分为请求上下文和应用上下文。
    • 请求上下文(Request Context)
      • 请求上下文是在处理客户端发来的请求期间创建的,用于存储当前请求的相关信息,例如请求的 URL、HTTP 方法、表单数据等。
      • 请求上下文在每个请求处理过程中都是独立的,它只存在于处理当前请求的期间。
      • 在请求处理过程中,Flask 会自动创建请求上下文,并将其绑定到当前线程。这意味着你可以在视图函数中直接访问请求对象,而无需手动传递。
    • 应用上下文(Application Context)
      • 应用上下文是在处理请求之前创建的(这里的应用上下文是指关于Flask应用本身的数据即启动时初始化的数据),用于存储应用程序级别的信息,例如应用配置、数据库连接蓝图和插件等。
      • 应用上下文在整个应用程序的生命周期内是唯一的,它负责管理应用程序级别的全局变量和设置。
      • 应用上下文也会被绑定到当前线程(这里是请求到来时创建的应用上下文,把当前app对象放到当前请求的应用上下文中),使得在处理请求时可以方便地访问应用级别的信息。
  • 首先我们进入到flask处理请求的函数self.wsgi_app()函数内部查看源码如下【注意:这里是flask处理请求的全部流程】:
def wsgi_app(self, environ, start_response):
    """
    1、ctx = RequestContext对象,此处创建请求上下文对象,并且在内部封装了两个对象:
    request = Request(environ)
    session = None
    """
    ctx = self.request_context(environ)
    error = None
    try:
        try:
            """
            2、执行ctx.push()
                app_ctx = 创建AppContext对象(app, g),此处创建当前请求的应用上下文,创建了app和g两个对象
                将app_ctx放入_app_ctx_stack中
                将ctx放入_request_ctx_stack中
                给session赋值
                进行路由匹配
            """
            ctx.push()
            # 执行视图函数,最后把session加密放到cookie中,以及执行特殊装饰器装饰的函数(如果有的话)比如:在视图之前执行的函数,视图执行之后执行的函数
            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)

1.3.1、请求上下文

  • 通过源码我们需要知道请求上文是怎么实现的,即底层是怎么存取的。
def wsgi_app(self, environ, start_response):
    """
    1、ctx = RequestContext对象,此处创建请求上下文对象,并且在内部封装了两个对象:
    request = Request(environ)
    session = None
    """
    ctx = self.request_context(environ)
    try:
    try:
        ctx.push()
        response = self.full_dispatch_request()
    except Exception as e:
        .......
  • 1、首先我们看ctx(请求上下文)的创建,进入到request_context()函数内部可以知道ctx是RequestContext类创建的对象,源码如下:
class RequestContext(object):
    def __init__(self, app, environ, request=None, session=None):
        # 保存当前app对象
        self.app = app
        if request is None:
            request = app.request_class(environ)
        # 创建request对象并将请求的内容放到request中
        self.request = request
        self.url_adapter = None
        try:
            # 创建路由适配器
            self.url_adapter = app.create_url_adapter(self.request)
        except HTTPException as e:
            self.request.routing_exception = e
        self.flashes = None
        # 创建session对象,初始化为空
        self.session = session
  • 2、进入ctx.push()源码查看:
def push(self):
    """Binds the request context to the current context."""
    # 从_request_ctx_stack栈中取出栈顶元素
    top = _request_ctx_stack.top
    # 这一步是检查栈顶元素,是否保存了之前的异常信息,如果有把它释放掉避免内存泄漏,可不关注这步
    if top is not None and top.preserved:
        top.pop(top._preserved_exc)
    # 从_app_ctx_stack栈中取出栈顶元素
    app_ctx = _app_ctx_stack.top
    if app_ctx is None or app_ctx.app != self.app:
        # 创建应用上下文对象app_ctx
        app_ctx = self.app.app_context()
        # 将应用上下文对象放入_app_ctx_stack栈中本质是放到Local类对象中
        app_ctx.push()
        self._implicit_app_ctx_stack.append(app_ctx)
    else:
        self._implicit_app_ctx_stack.append(None)

    # 将ctx(请求上下文)放入_request_ctx_stack中本质也是放到Local类对象中
    _request_ctx_stack.push(self)
    if self.session is None:
        session_interface = self.app.session_interface
        # 将当前上下文的session会话绑定到self.session对象
        self.session = session_interface.open_session(self.app, self.request)
        if self.session is None:
            # 如果self.session为空,则创建一个空的session对象绑定
            self.session = session_interface.make_null_session(self.app)

    if self.url_adapter is not None:
        # 匹配请求,获取请求的url_rule和view_args
        self.match_request()
  • 3、在上面源码中我们可以看到请求上下文和应用上下文分别保存在_request_ctx_stack,_app_ctx_stack中;进入到_app_ctx_stack和_request_ctx_stack所在的globals.py模块,可以看到实现上下文的管理方式,源码如下:

# context locals
# 使用LocalStack()创建_request_ctx_stack,这里创建了一个栈
_request_ctx_stack = LocalStack()
_app_ctx_stack = LocalStack()
# LocalProxy的作用是代理,也就是说我们使用的current_app、request、session、g并不是直接从_app_ctx_stack和_request_ctx_stack获取,
# 而是通过LocalProxy代理的方式帮我们从_app_ctx_stack和_request_ctx_stack中获取
current_app = LocalProxy(_find_app)
request = LocalProxy(partial(_lookup_req_object, "request"))
session = LocalProxy(partial(_lookup_req_object, "session"))
g = LocalProxy(partial(_lookup_app_object, "g"))
  • 4、进入到LocalStack()源码内部查看,底层是如何存储的:
class LocalStack(object):
    def __init__(self):
        # 使用Local类创建一个self._local对象用于存取上下文
        self._local = Local()
    # 存储一个值到栈
    def push(self, obj):
        """Pushes a new item to the stack"""
        rv = getattr(self._local, "stack", None)
        if rv is None:
            self._local.stack = rv = []
        rv.append(obj)
        return rv
    # 从栈中移除一个值
    def pop(self):
        """Removes the topmost item from the stack, will return the
        old value or `None` if the stack was already empty.
        """
        stack = getattr(self._local, "stack", None)
        if stack is None:
            return None
        elif len(stack) == 1:
            release_local(self._local)
            return stack[-1]
        else:
            return stack.pop()
    # 获取栈顶元素
    @property
    def top(self):
        """The topmost item on the stack.  If the stack is empty,
        `None` is returned.
        """
        try:
            return self._local.stack[-1]
        except (AttributeError, IndexError):
            return None        
  • 5、从上面源码可以看到底层存储是通过self._local = Local()实现的,所以再进入Local类看看内部是如何实现的,Local类源码如下:
class Local(object):
    __slots__ = ("__storage__", "__ident_func__")

    def __init__(self):
        # 创建一个__storage__属性默认为空字典
        object.__setattr__(self, "__storage__", {})
        # 创建一个__ident_func__属性默认get_ident(作用是获取当前线程id)
        object.__setattr__(self, "__ident_func__", get_ident)

    def __release_local__(self):
        self.__storage__.pop(self.__ident_func__(), None)

    # 获取值
    def __getattr__(self, name):
        try:
            # 从__storage__字典值中获取对应线程id存储的上下文信息
            return self.__storage__[self.__ident_func__()][name]
        except KeyError:
            raise AttributeError(name)
    # 设置值
    def __setattr__(self, name, value):
        # 获取当前线程的id
        ident = self.__ident_func__()
        # 获取__storage__字典
        storage = self.__storage__
        try:
            # 把对应线程的上下文信息保存到字典中
            storage[ident][name] = value
        except KeyError:
            storage[ident] = {name: value}
  • 通过上面源码解读最后我们可以知道当执行_request_ctx_stack.push()时等价于LocalStack().push(),这样就把需要保存的上下文信息存储到了_request_ctx_stack中,最终的存储格式是字典格式,示例如下:
# 其中123456表示线程id
{
    123456:{"stack":[ctx,]}
}

1.3.2、应用上下文

  • 说明:明白了请求上下文的存储过程,那么应用上下文的存储过程和请求上下文的类似,区别在于创建上下文的对象不同,应用上下文使用AppContext类创建,并且创建了一个app对象和g对象,源码如下:
class AppContext(object):
    def __init__(self, app):
        # 创建app对象用户保存当前app对象
        self.app = app
        self.url_adapter = app.create_url_adapter(None)
        # 创建一个g对象,本质是一个字典对象
        self.g = app.app_ctx_globals_class()
  • 类比请求上下文存储的格式,我们可以知道应用上下文在_app_ctx_stack中的存储格式如下:
# 其中123456表示线程id
{
    123456:{"stack":[app_ctx,]}
}

1.3.3、flask上下文实现的原理

  • 思考:为什么每个请求对应的上下文一定是自己的呢,多个请求的时候为什么上下文不会混乱呢?
  • 上面两个问题其实是一个问题,从上面我们已经知道了上下文的存储,但是多个请求到来时flask又是怎么处理的呢,我们需要知道flask处理请求是以线程方式处理的,也就是一个线程对应一个请求,每个线程都有自己独立的_request_ctx_stack,_app_ctx_stack对象,所以在一个请求中_request_ctx_stack,_app_ctx_stack中自始至终都只有一个对象,分别是ctx(请求上下文对象),app_ctx(应用上下文对象),故每个请求的上下文都是独立且隔离开的所以不会混乱。

1.3.4、g对象的使用

  • 作用:相当于是一次请求周期的全局变量,可以在g中设置值,然后在本次请求周期中读取值。
  • 示例如下:
from flask import Flask, g

app = Flask(__name__)

@app.before_request
def before_test():
    g.user_id = 0001
    print("before_test")

@app.route("/index")
def index():
    print(g.user_id)
    return "hello world"

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

1.4、flask1.x源码之执行视图函数

  • 说明:flask中执行视图函数的源码如下:
response = self.full_dispatch_request()
  • 进入full_dispatch_request()函数内部查看如何执行视图函数,源码如下:
def full_dispatch_request(self):
    # 首次请求到来时触发,全部的请求只触发一次也就是首次请求来时触发那一次
    # 使用before_first_request装饰的函数
    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)
    # 处理上面的执行结果并返回
    return self.finalize_request(rv)

1.4.1、请求处理前预处理

  • 从上面源码可以看出,在视图函数执行之前还会执行预处理步骤,进入self.preprocess_request()函数内部查看是如何进行预处理的,源码如下:
def preprocess_request(self):
    # 获取蓝图对象
    bp = _request_ctx_stack.top.request.blueprint
    # 获取当前app预处理函数列表对象,使用@app.url_value_preprocessor装饰的函数
    funcs = self.url_value_preprocessors.get(None, ())
    # 判断蓝图对象在self.url_value_preprocessors中是否存在
    if bp is not None and bp in self.url_value_preprocessors:
        # 将前app预处理函数列表对象与蓝图预处理函数列表对象连接成一个可迭代对象funcs
        # 这样处理的优势在于无需开辟额外空间,类似于生成器对象只可使用一次
        # 使用@bp.url_value_preprocessor装饰蓝图请求预处理函数
        funcs = chain(funcs, self.url_value_preprocessors[bp])
    for func in funcs:
        # 执行funcs中的预处理函数,先执行app对象中的再执行蓝图中的
        func(request.endpoint, request.view_args)

    # 获取当前app请求前处理函数列表对象,下面的处理过程和上面类似
    # 这里需要注意的是如果请求前处理函数有返回值,会直接返回,不会执行后面流程
    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

示例:

from flask import Flask, Blueprint, request

app = Flask(__name__)
bp = Blueprint('example', __name__)

# 全局的请求前预处理函数
@app.url_value_preprocessor
def app_preprocess_url_values(endpoint, values):
    # 在这里进行 URL 参数的预处理操作
    print("URL 参数预处理")

# 全局的请求前处理函数
@app.before_request
def app_before_request():
    # 在这里执行通用的请求前处理操作
    print("通用请求前处理")

# 蓝图的请求前预处理函数
@bp.url_value_preprocessor
def bp_preprocess_url_values(endpoint, values):
    # 在这里进行蓝图相关的 URL 参数预处理操作
    print("蓝图 URL 参数预处理")

# 蓝图的请求前处理函数
@bp.before_request
def bp_before_request():
    # 在这里执行蓝图相关的请求前处理操作
    print("蓝图请求前处理")

@bp.route('/')
def index():
    return "Hello, World!"

app.register_blueprint(bp)

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

# 输出结果
URL 参数预处理
蓝图 URL 参数预处理
通用请求前处理
蓝图请求前处理

1.4.2、执行视图函数

  • 直接进入self.dispatch_request()函数内部查看是如何执行视图函数的,源码如下:
def dispatch_request(self):
    # 从_request_ctx_stack中获取request对象
    req = _request_ctx_stack.top.request
    if req.routing_exception is not None:
        self.raise_routing_exception(req)
    # 获取url对象
    rule = req.url_rule
    if (
        getattr(rule, "provide_automatic_options", False)
        and req.method == "OPTIONS"
    ):
        return self.make_default_options_response()
    # 从view_functions中找到视图函数并执行,最后返回执行结果
    return self.view_functions[rule.endpoint](**req.view_args)

1.4.3、处理视图执行结果并返回最终响应结果

  • 进入self.finalize_request(rv)函数内部查看如何处理视图结果并返回响应结果,源码如下:
def finalize_request(self, rv, from_error_handler=False):
    # 将试图执行的结果封装为标准响应对象
    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
  • 从上面源码可以看出在返回响应结果之前,还对响应结果进行了处理,进入 self.process_response(response)源码查看,对响应结果进行了那些处理,源码如下:
def process_response(self, response):
    # 获取当前请求上下文对象
    ctx = _request_ctx_stack.top
    # 获取蓝图对象
    bp = ctx.request.blueprint
    # 获取@after_this_request装饰的函数,用于修改响应对象
    funcs = ctx._after_request_functions
    # 判断在蓝图中是否存在@bp.after_request装饰的函数
    if bp is not None and bp in self.after_request_funcs:
        # 注意这里使用了reversed函数把@bp.after_request装饰的函数的执行顺序倒置了
        funcs = chain(funcs, reversed(self.after_request_funcs[bp]))
    # 判断是否存在@app.after_request装饰的函数
    if None in self.after_request_funcs:
        # 注意这里使用了reversed函数把@app.after_request装饰的函数的执行顺序倒置了
        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):
        # 将存在session中的数据加密(从ctx获取)写入到cookie中,等下次请求再来时,就可以从cookie中获取上次写入的值
        self.session_interface.save_session(self, ctx.session, response)
    # 返回响应对象
    return response

示例:

@bp.route('/')
def index():
    @after_this_request
    def add_header(response):
        # 给响应结果的响应头添加一个key-value对
        response.headers['X-Foo'] = 'Parachute'
        return response
    return "Hello, World!"

@app.after_request
def app_after_test1(response):
    print("app_after_test1")
    return response

@app.after_request
def app_after_test2(response):
    print("app_after_test2")
    return response

@bp.after_request
def bp_after_test1(response):
    print("bp_after_test1")
    return response

@bp.after_request
def bp_after_test2(response):
    print("bp_after_test2")
    return response

app.register_blueprint(bp)

if __name__ == '__main__':
    app.run(debug=True)
    
# 执行结果
bp_after_test2
bp_after_test1
app_after_test2
app_after_test1

1.5、flask1.x源码之销毁上下文

  • 说明:销毁上下文的源码如下:
 # 销毁当前请求的请求上下文和应用上下文
 ctx.auto_pop(error)
  • 进入ctx.auto_pop(error)函数内部查看源码是如何实现上下文销毁的:
def auto_pop(self, exc):
    # 判断是否需要保留上下文
    # 检查当前请求环境变量中的 "flask._preserve_context" 是否为真。
    # 如果正在处理一个异常(exc 不为空),并且应用配置中设置了在异常发生时保留上下文(app.preserve_context_on_exception 为真)
    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)
  • 进入 self.pop(exc)函数内部,查看源码如下:
def pop(self, exc=_sentinel):
    # 从_implicit_app_ctx_stack中出栈应用上下文对象
    app_ctx = self._implicit_app_ctx_stack.pop()
    try:
        # 清除请求标识置为False
        clear_request = False
        if not self._implicit_app_ctx_stack:
            # 是否保存上下文标识置为False
            self.preserved = False
            self._preserved_exc = None
            if exc is _sentinel:
                # 获取异常信息
                exc = sys.exc_info()[1]
            # 执行请求结束时的清理工作,例如关闭数据库连接、删除临时文件等。
            self.app.do_teardown_request(exc)

            if hasattr(sys, "exc_clear"):
                sys.exc_clear()
            # 执行关闭请求清理操作(如果close方法存在)
            request_close = getattr(self.request, "close", None)
            if request_close is not None:
                request_close()
            clear_request = True
    finally:
        # 从_request_ctx_stack中出栈(销毁)请求上下文对象
        rv = _request_ctx_stack.pop()

        # get rid of circular dependencies at the end of the request
        # so that we don't require the GC to be active.
        if clear_request:
            rv.request.environ["werkzeug.request"] = None

        # Get rid of the app as well if necessary.
        if app_ctx is not None:
            # 销毁应用上下文对象
            app_ctx.pop(exc)

        assert rv is self, "Popped wrong request context. (%r instead of %r)" % (
            rv,
            self,
        )
  • 从源码中可以看出所谓的销毁上下文对象其实就是把上下对象从所存储的栈中弹出,把对应的栈清空,方便实现垃圾回收。

1.5.1、销毁请求上下文之前的清理工作

  • 从上面 self.pop(exc)函数内部可以看出,在销毁请求上下文之前做了一些清理工作,具体实现的函数是:
self.app.do_teardown_request(exc)
  • 进入do_teardown_request()函数内部查看源码如下:
def do_teardown_request(self, exc=_sentinel):
    if exc is _sentinel:
        # 获取异常信息
        exc = sys.exc_info()[1]
    # 获取@app.teardown_request装饰的函数列表并把列表对象逆置
    funcs = reversed(self.teardown_request_funcs.get(None, ()))
    # 获取蓝图对象
    bp = _request_ctx_stack.top.request.blueprint
    # 如果蓝图对象存在于self.teardown_request_funcs,则获取@bp.teardown_request装饰的函数列表对象并且逆置
    if bp is not None and bp in self.teardown_request_funcs:
        # 将上面app请求清理函数列表对象与蓝图请求清理函数列表对象连接成一个可迭代对象funcs
        funcs = chain(funcs, reversed(self.teardown_request_funcs[bp]))
    for func in funcs:
        # 执行func函数
        func(exc)
    # 发送请求清理完成信号
    request_tearing_down.send(self, exc=exc)

示例:

from flask import Flask, Blueprint, request, after_this_request

app = Flask(__name__)
bp = Blueprint('example', __name__)

@bp.route('/')
def index():
    return "Hello, World!"

@app.teardown_request
def app_teardown_test1(exc):
    print("app_teardown_test1")

@app.teardown_request
def app_teardown_test2(exc):
    print("app_teardown_test2")

@bp.teardown_request
def bp_teardown_test1(exc):
    print("bp_teardown_test1")

@bp.teardown_request
def bp_teardown_test2(exc):
    print("bp_teardown_test2")

app.register_blueprint(bp)

if __name__ == '__main__':
    app.run(debug=True)
    
# 执行结果
app_teardown_test2
app_teardown_test1
bp_teardown_test2
bp_teardown_test1

1.5.2、销毁应用上下文之前的清理工作

  • 进入到销毁应用上下文执行的函数app_ctx.pop(exc),如下:
def pop(self, exc=_sentinel):
    """Pops the app context."""
    try:
        # 应用上下文计数减一
        self._refcnt -= 1
        if self._refcnt <= 0:
            if exc is _sentinel:
                # 获取异常信息
                exc = sys.exc_info()[1]
            # 销毁应用上下文对象之前的清理工作
            self.app.do_teardown_appcontext(exc)
    finally:
        # 销毁应用上下文(出栈)
        rv = _app_ctx_stack.pop()
    assert rv is self, "Popped wrong app context.  (%r instead of %r)" % (rv, self)
    # 销毁应用上下文后发送信号
    appcontext_popped.send(self.app)
  • 进入self.app.do_teardown_appcontext()函数内部查看销毁应用上下文对象之前做了哪些清理工作,源码如下:
def do_teardown_appcontext(self, exc=_sentinel):
    if exc is _sentinel:
        # 获取异常信息
        exc = sys.exc_info()[1]
    # 获取@app.teardown_appcontext装饰的函数列表对象并逆置
    for func in reversed(self.teardown_appcontext_funcs):
        func(exc)
    # 清理工作完成之后发送信号
    appcontext_tearing_down.send(self, exc=exc)

示例:

from flask import Flask, Blueprint, request, after_this_request

app = Flask(__name__)
bp = Blueprint('example', __name__)

@bp.route('/')
def index():
    return "Hello, World!"
    
@app.teardown_appcontext
def app_teardown_appcontext(exc):
    print("app_teardown_appcontext")

@app.teardown_appcontext
def app_teardown_appcontext2(exc):
    print("app_teardown_appcontext2")

app.register_blueprint(bp)

if __name__ == '__main__':
    app.run(debug=True)
    
# 执行结果
app_teardown_appcontext2
app_teardown_appcontext
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值