
框架只是为了简化开发,框架的目的很简单,也就是为了向程序员隐藏HTTP请求和相应的相关代码,让程序员专注于业务逻辑的开发。
flask主要实现了一个Web应用所需要的最小功能
Django则包括了Web应用的大部分功能
我们着重对Flask源码进行分析,通过对于其“简化开发工作”这个需求入手,分析代码为什么要这么实现,最终对Flask全部功能进行总结以及自己动手实现。
阅读本文需要有一下两点要求:
- 了解基本的Python语法,熟悉装饰器
- 有使用过Flask进行开发
首先,我们要明白,Flask是基于Werkzeug这个框架进行开发的。
Werkzeug是一个WSGI工具包,他可以作为一个Web框架的底层库。这里稍微说一下, werkzeug 不是一个web服务器,也不是一个web框架,而是一个工具包,官方的介绍说是一个 WSGI 工具包,它可以作为一个 Web 框架的底层库,因为它封装好了很多 Web 框架的东西,例如 Request,Response 等等。。
本篇文章着重分析Flask是如何实现的,而不会过多分析Werkzeug的实现方式,Werkzeug中的实现放在后面进行。
我们先放上一个简单的Werkzeug的demo。
import os
from werkzeug.wrappers import Request, Response
from werkzeug.routing import Map, Rule
from werkzeug.exceptions import HTTPException
from werkzeug.middleware.shared_data import SharedDataMiddleware
class Shortly:
def __init__(self):
self.url_map = Map([
Rule('/', endpoint='new_url'),
])
def on_new_url(self, request):
print(request.path)
result = [request.path]
return Response(''.join(result))
def dispatch_request(self, request):
adapter = self.url_map.bind_to_environ(request.environ)
try:
endpoint, values = adapter.match()
return getattr(self, 'on_' + endpoint)(request, **values)
except HTTPException as e:
return e
def wsgi_app(self, environ, start_response):
request = Request(environ)
response = self.dispatch_request(request)
return response(environ, start_response)
def __call__(self, environ, start_response):
return self.wsgi_app(environ, start_response)
def create_app(redis_host='localhost', redis_port=6379, with_static=True):
app = Shortly({
'redis_host': redis_host,
'redis_port': redis_port,
})
if with_static:
app.wsgi_app = SharedDataMiddleware(app.wsgi_app, {
'/static': os.path.join(os.path.dirname(__file__), 'static')
})
return app
if __name__ == '__main__':
from werkzeug.serving import run_simple
app = create_app()
run_simple('127.0.0.1', 5000, app, use_debugger=True, use_reloader=True)
首先我们要理解两个点
- werkzeug中使用Map对象和Rule对象进行路由分发
- 调用run_simple启动一个werkzeug项目
更多用法可以参考werkzeug官方文档
https://werkzeug-docs-cn.readthedocs.io/zh_CN/latest/quickstart.html#response
我们把目光转回Flask项目中来。
一下代码基于Flask 0.1版本源码进行分析
1- 启动一个Flask项目会发生什么?
当我们执行app.run的时候
实际会执行Flask的run方法。
class Flask:
# 省略掉一些函数,着重关注run方法
def run(self, host='localhost', port=5000, **options):
"""在本地开发服务器上运行程序。如果debug标志被设置,这个服务器
会在代码更改时自动重载,并会在异常发生时显示一个调试器。
:param host: 监听的主机名。设为'0.0.0.0'可以让服务器外部可见。
:param port: 服务器的端口。
:param options: 这些选项将被转发给底层的Werkzeug服务器。更多信息
参见werkzeug.run_simple。
"""
from werkzeug import run_simple
if 'debug' in options:
self.debug = options.pop('debug')
options.setdefault('use_reloader', self.debug)
options.setdefault('use_debugger', self.debug)
return run_simple(host, port, self, **options)
也就是说,执行的还是werkzeug的run_simple方法,用来监听一个端口。
2- Flask是怎么将路由分发到对应业务逻辑函数?
Flask在进行路由分发的时候主要运用了三个函数,以及werkzeug的Map对象
class MyFlask:
# 省略掉一些函数,着重关注路由分发
def __init__(self, package_name):
self.debug = False
# 包或模具哎的名称
self.package_name = package_name
# 定位程序的根目录
self.root_path = _get_package_path(package_name)
# 一个存储所有已经注册的视图函数字典,
# 要注册一个视图函数,就使用route装饰器
self.view_functions = {}
# 存储所有应该被转发的路由
self.url_map = Map()
def add_url_rule(self, rule, endpoint, **options):
# 将目前所有的路由加入werkzeug的Map里,用于路由分发
options[endpoint] = endpoint
options.setdefault('methods', ('GET', ))
self.url_map.add(Rule(rule, **options))
def route(self, rule, **options):
# 语法糖,方便使用装饰器来注册路由,用法如下
def decorator(f):
self.add_url_rule(rule, f.__name__, **options)
self.view_functions[f.__name__] = f
return f
return decorator
当我们在Flask项目中增加URL以及业务逻辑函数的时候
@app.route('/index', methods=['GET', 'POST'])
def team(timestamp):
return render_template("index.html")
实际会调用Flask.route方法,注册路由 装饰器的语法不在叙述,from werkzeug.routing import Map, Rule
- 调用Flask.add_url_url方法,将我们装饰器参数中的rule(也就是URL)和method构成一个Rule对象,然后添加到Map对象中。
- 将函数名称以及函数放入self.view_functions属性中。
这样做的优势在于解决了两个问题
- 不用把过多函数都放在我们的对象中,方便项目管理
- 使用装饰器模式,能将URL和业务函数写在一起,有助于提高开发效率。