文章目录
转载请注明出处即可
源码地址github flask
主要参考文档为flask
环境为MacOS, Python 3.7+, IDE Pycharm
注意:文章中的源码存在删减,主要是为了减少篇幅和去除非核心逻辑,但不会影响对执行流程的理解。
如果对Werkzeug不是很了解,请先看Flask源码分析系列(1) -Werkzeug源码分析这篇文章
一、从一个最简单的Demo开始
Flask是Python语言编写的一个优秀的开源Web框架。我们先从一个最小的Demo开始,逐步来分析Flask是如何实现相关功能的。
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello_world():
return 'Hello, World!'
def main():
app.run(host='0.0.0.0', port=8080, debug=True)
if __name__ == '__main__':
main()
首先app变量或者说Flask类创建的对象,其实是一个WSGI Application,也就是说是一个符合上篇文件中描述的一个符合WSGI规则的一个函数,具体是Flask类的wsgi_app方法来实现。
# app.py 2366行, Flask类的方法
def __call__(self, environ, start_response):
return self.wsgi_app(environ, start_response)
# app.py 2323行, Flask类的方法
def wsgi_app(self, environ, start_response):
pass
虽然app.run方法提供了Werkzeug的serving.make_server的实现,但是你依然可以选择其他支持WSGI协议的Server来运行Flask应用,比如gunicorn等。在实践中,我们在开发环境可以选择一些基本的WSGI Server用于本地调试。而在生产环境中在使用gunicorn等来实现多进程运行。当然这都直接取决于你自己根据实际的环境进行选择。以下代码是使用tornado的httpserver的一个例子。
from tornado.wsgi import WSGIContainer
from tornado.httpserver import HTTPServer
from tornado.ioloop import IOLoop
from demo import app
import sys
reload(sys)
sys.setdefaultencoding("utf-8")
def main():
http_server = HTTPServer(WSGIContainer(app))
http_server.listen(8080)
IOLoop.instance().start()
app.config['SESSION_TYPE'] = 'filesystem'
app.config['APIURL'] = '/api'
if __name__ == "__main__":
main()
如果使用gunicorn,那么可以通过以下指令来运行Flask应用。
export FLASK_ENV=development
THREAD_COUNT=8
gunicorn -k gevent -w $THREAD_COUNT -b 0.0.0.0:8080 demo:app -t 6000000
扯了一些基本应用,下面开始进入正题。
二、Route实现原理
Route的实际作用是将url path和具体要执行的函数进行映射。Flask并没有把这些能力自己实现,而是使用了Werkzeug的Map、Rule和MapAdapter来实现。
首先先看下@app.route(’/’)装饰器的实现。
(Python的装饰器在这里不详细解释,如果不明白请查看廖雪峰的Python教程)
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方法的参数rule是url path,而options则对应着Werkzeug中Rule类的参数,比如endpoint,methods等。除了endpoint做了一些特殊的处理以外,其他的参数原封不动的传到了Rule的__init__
。
在decorator函数中的第一行从options dict中pop出了endpoint,这里是因为在add_url_rule进行了一些其他处理(其实就是判断是否是None,然后选择是否使用函数名称而已)。
add_url_rule方法的第三个参数f,则为被装饰的函数,在Demo的例子中就是hello_world函数。
Flask默认使用的endpoint是方法的名称,但依然保留了这个参数,方便用户自定义endpoint。
然后我们来看下Flask.add_url_rule方法的实现。具体源码在app.py的1099行。由于方法略长,我们来拆分即可来分析。方法的参数列表没有什么需要过多解释的。
def add_url_rule(
self,
rule,
endpoint=None,
view_func=None,
provide_automatic_options=None,
**options,
):
pass
函数的第一段,是处理endpoint,如果用户没有在route中设置endpoint参数的话,则默认使用了view_func.__name__
来获取函数的名称。然后获取了methods的参数。
if endpoint is None:
endpoint = _endpoint_from_view_func(view_func)
options["endpoint"] = endpoint
函数的第二段,是对methods参数的处理,如果用户没有设置methods列表(或元组)的话,默认设置为(“Get”,)。并且对methods进行了是否是字符串的检查, 最后将所有的method都变成大写和去重(放入了Set中)。
methods = options.pop("methods", None)
if methods is None:
methods =