Flask 源码剖析 (一):Flask 启动流程

前言

Flask 是 Python 著名的 web 框架,其特点是轻量简单易扩展。

Flask 源码量挺多的,本文从比较高的维度整体看一下 Flask 关键结构的实现原理,文中不会细究太多细节,不多废话,开搞。

考虑篇幅长度,分多篇文章来讨论,本文系列文章以 Flask 1.0.2 为基准。

前置背景知识

Flask 依赖于 werkzeug 与 jinja 这两个核心库,werkzeug 是 HTTP 与 WSGI 相关的工具集,而 jinja 主要用于渲染前端模板文件。

Flask 框架满足 WSGI 协议,其功能简单而言就是将 HTTP 数据转为 environ 包含请求中所有的信息以及 start_response 回调函数传递给 web 框架对象,形象如图:

Flask 源码剖析 (一):Flask 启动流程_第1张图片

如果依旧不清晰,可以参考此前的旧文「实现满足 WSGI 协议的 Web 服务」。

Flask 应用启动流程

从 Flask 最基本的使用开始,代码如下。

from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello():
    return "hello 懒编程"
if __name__ == '__main__':
    app.run()

在代码中,通过 Flask(__name__)实例化了 Flask 类,对应的__init__() 方法如下

# flask/app.py
classs Flask(_PackageBoundObject):
    def __init__(...):
        # 对每次请求,创建一个处理通道。
        self.config = self.make_config()
        self.view_functions = {}
        self.error_handler_spec = {}
        self.before_request_funcs = {}
        self.before_first_request_funcs = []
        self.after_request_funcs = {}
        self.teardown_request_funcs = {}
        self.teardown_appcontext_funcs = []
        self.url_value_preprocessors = {}
        self.url_default_functions = {}
        self.url_map = Map()
        self.blueprints = {}
        self._blueprint_order = []
        self.extensions = {}

__init__() 方法中有大量的注释,注释中解释了这些变量的用途,但仅从变量名就知道,它们用于存储每次请求对应的信息,相当于一个处理通道。

Flask 实例化后,接着利用 @app.route('/')装饰器的方式将 hello () 方法映射成了路由,相关代码如下:

# flask/app.py/Flask
def route(self, rule, **options):
    def decorator(f):    
        endpoint = options.pop('endpoint', None)
        self.add_url_rule(rule, endpoint, f, **options)
        return f
    return decorator

可以发现 route () 方法就是一个简单的装饰器,具体处理逻辑在 addurlrule () 方法中。

# flask/app.py/Flask
@setupmethod
def add_url_rule(self, rule, endpoint=None, view_func=None,
                     provide_automatic_options=None, **options):
    # ... 省略其他代码细节
    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)
       # 每个方法的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)
       # 将rule对应的endpoint与view_func通过view_functions字典对应上
       self.view_functions[endpoint] = view_func

从 addurlrule () 方法可以看出, @app.route('/')的主要作用就是将路由保存到 urlmap 中,将装饰的方法保存到 viewfunctions 中。需要注意的是,每个方法的 endpoint 必须不同,否则会抛出 AssertionError。

最后调用了 app.run () 方法运行 Flask 应用,对应代码如下。

# flask/app.py/Flask
def run(self, host=None, port=None, debug=None,
            load_dotenv=True, **options):
    # ... 省略
    from werkzeug.serving import run_simple
    try:
        # 利用
        run_simple(host, port, self, **options)
    finally:
        self._got_first_request = False

run () 方法进一步调用 werkzeug.serving 下的 run_simple () 方法启动 web 服务,其中 self 就是 Flask () 的 application。

逐层深入,runsimple() -> makeserver() -> BaseWSGIServer() -> WSGIRequestHandler

WSGIRequestHandler 类从名称就可以知,它主要用于处理满足 WSGI 协议的请求,该类中的 execute () 方法部分代码如下。

# werkzeug/serving.py/WSGIRequestHandler
def execute(app):
    application_iter = app(environ, start_response)
    # 省略其他代码

简单而言,app.run () 会启动一个满足 WSGI 协议的 web 服务,它会监听指定的端口,将 HTTP 请求解析为 WSGI 格式的数据,然后将 environ, start_response 传递给 Flask () 实例对象。

类对象作为方法被调用,需要看到__call__() 方法,代码如需。

# flask/app.py/Flask
def __call__(self, environ, start_response):
    return self.wsgi_app(environ, start_response)
def wsgi_app(self, environ, start_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
       # 无论是否发生异常都需要从请求上下文中error pop出
       ctx.auto_pop(error)

主要逻辑在 wsgiapp () 方法中,一开始进行了请求上下文的处理 (后面文章单独剖析 Flask 上下文相关源码),随后通过 fulldispatch_request () 方法找到当前请求路由对应的方法,调用该方法,获得返回,如果请求路由不存在,则进行错误处理,返回 500 错误。

fulldispatchrequest () 方法代码如下。

# flask/app.py/Flask
def full_dispatch_request(self):
   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)

fulldispatchrequest () 方法中最关键的逻辑在于 dispatch_request () 方法,该方法会将调用对应路由的处理函数并获得该函数的结果。

此外还有 trytriggerbeforefirstrequestfunctions()、preprocessrequest()、finalizerequest () 方法,这些方法会执行我们自定义的各种钩子函数,这些钩子函数会存储在 beforerequestfuncs()、beforefirstrequestfuncs()、afterrequestfuncs () 方法中。

最后,需要注意,app.run () 方法仅在开发环境中会被使用,通过上面的分析,已经知道 app.run () 背后就是使用 werkzeug 构建了一个简单的 web 服务,但这个 web 服务并不牢靠,生产环境通常利用 uWSGI,通过配置的形式,指定 WSGI app 所在文件来启动 Flask 应用。

结尾

本文主要讨论了 Flask 应用主要的运行流程,后面将会继续讨论 flask 的上下文、路由等机制。

如果你觉得本文对你有用,记得点「在看」支持二两。

你可能感兴趣的:(Flask 源码剖析 (一):Flask 启动流程)