Flask 源码剖析 :Flask 启动流程

Posted 懒编程

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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 框架对象,形象如图:

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

Flask 应用启动流程

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

 
   
   
 
  1. from flask import Flask


  2. app = Flask(__name__)


  3. @app.route('/')

  4. def hello():

  5. return "hello 懒编程"


  6. if __name__ == '__main__':

  7. app.run()

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

 
   
   
 
  1. # flask/app.py


  2. classs Flask(_PackageBoundObject):

  3. def __init__(...):

  4. # 对每次请求,创建一个处理通道。

  5. self.config = self.make_config()

  6. self.view_functions = {}

  7. self.error_handler_spec = {}

  8. self.before_request_funcs = {}

  9. self.before_first_request_funcs = []

  10. self.after_request_funcs = {}

  11. self.teardown_request_funcs = {}

  12. self.teardown_appcontext_funcs = []

  13. self.url_value_preprocessors = {}

  14. self.url_default_functions = {}

  15. self.url_map = Map()

  16. self.blueprints = {}

  17. self._blueprint_order = []

  18. self.extensions = {}

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

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

 
   
   
 
  1. # flask/app.py/Flask


  2. def route(self, rule, **options):

  3. def decorator(f):

  4. endpoint = options.pop('endpoint', None)

  5. self.add_url_rule(rule, endpoint, f, **options)

  6. return f

  7. return decorator

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

 
   
   
 
  1. # flask/app.py/Flask


  2. @setupmethod

  3. def add_url_rule(self, rule, endpoint=None, view_func=None,

  4. provide_automatic_options=None, **options):

  5. # ... 省略其他代码细节

  6. rule = self.url_rule_class(rule, methods=methods, **options)

  7. rule.provide_automatic_options = provide_automatic_options

  8. # 将路由rule添加到url_map中

  9. self.url_map.add(rule)

  10. if view_func is not None:

  11. old_func = self.view_functions.get(endpoint)

  12. # 每个方法的endpoint必须不同

  13. if old_func is not None and old_func != view_func:

  14. raise AssertionError('View function mapping is overwriting an '

  15. 'existing endpoint function: %s' % endpoint)

  16. # 将rule对应的endpoint与view_func通过view_functions字典对应上

  17. self.view_functions[endpoint] = view_func

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

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

 
   
   
 
  1. # flask/app.py/Flask


  2. def run(self, host=None, port=None, debug=None,

  3. load_dotenv=True, **options):

  4. # ... 省略

  5. from werkzeug.serving import run_simple

  6. try:

  7. # 利用

  8. run_simple(host, port, self, **options)

  9. finally:

  10. self._got_first_request = False

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

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

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

 
   
   
 
  1. # werkzeug/serving.py/WSGIRequestHandler


  2. def execute(app):

  3. application_iter = app(environ, start_response)

  4. # 省略其他代码

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

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

 
   
   
 
  1. # flask/app.py/Flask


  2. def __call__(self, environ, start_response):

  3. return self.wsgi_app(environ, start_response)


  4. def wsgi_app(self, environ, start_response):

  5. # 请求上下文

  6. ctx = self.request_context(environ)

  7. error = None

  8. try:

  9. try:

  10. ctx.push()

  11. # 正确的请求处理路径,会通过路由找到对应的处理函数

  12. # 通过路由找到相应的处理函数

  13. response = self.full_dispatch_request()

  14. except Exception as e:

  15. # 错误处理

  16. error = e

  17. response = self.handle_exception(e)

  18. except:

  19. error = sys.exc_info()[1]

  20. raise

  21. return response(environ, start_response)

  22. finally:

  23. if self.should_ignore_error(error):

  24. error = None

  25. # 无论是否发生异常都需要从请求上下文中error pop出

  26. ctx.auto_pop(error)

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

fulldispatchrequest () 方法代码如下。

 
   
   
 
  1. # flask/app.py/Flask


  2. def full_dispatch_request(self):

  3. self.try_trigger_before_first_request_functions()

  4. try:

  5. request_started.send(self)

  6. rv = self.preprocess_request()

  7. if rv is None:

  8. # 调用该路由对应的处理函数并将处理函数的结果返回。

  9. rv = self.dispatch_request()

  10. except Exception as e:

  11. rv = self.handle_user_exception(e)

  12. 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 启动流程的主要内容,如果未能解决你的问题,请参考以下文章

Flask源码流程剖析

01 flask源码剖析之werkzurg 了解wsgi

剖析Flask上下文管理(源码)

flask 上下文管理 &源码剖析

Flask学习-Flask app启动过程

Flask启动原理,源码流程分析