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

1,471 阅读4分钟

前言

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

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

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

前置背景知识

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

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

如果依旧不清晰,可以参考此前的旧文「实现满足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()方法就是一个简单的装饰器,具体处理逻辑在add_url_rule()方法中。

# 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

从add_url_rule()方法可以看出,@app.route('/')的主要作用就是将路由保存到url_map中,将装饰的方法保存到view_functions中。需要注意的是,每个方法的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。

逐层深入,run_simple() -> make_server() -> 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)

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

full_dispatch_request()方法代码如下。

# 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)

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

此外还有try_trigger_before_first_request_functions()、preprocess_request()、finalize_request()方法,这些方法会执行我们自定义的各种钩子函数,这些钩子函数会存储在before_request_funcs()、before_first_request_funcs()、after_request_funcs()方法中。

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

结尾

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

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