前言

Flask 是一个较为流行的 Python web framework ,其代码以精巧,可读性高著称。 虽然 flask 号称 framework ,但是 Flask 没有 web framework 中普遍存在的模板引擎以及 ORM 或者 ODM 模块。私以为 flask 定位更像是一个 web 应用的脚手架,它允许开发者灵活自由地插拔配件。Flask 的设计理念或许跟 Node.js 中的 express 有些类似,express 同样也只是一个脚手架级别的 web 框架,没有自己的模板引擎、http server 以及 ORM 等插件。两者最大的区别应该是扩展的串联方式不同, 因为 Node.js 特有地异步方式,express 使用 middleware 像链条一样把扩展串联起来,而 flask 的串联方式更像是乐高积木一般的组合。值得一提的是,werkzeug 是 flask 中无法取代的一个‘扩展’。werkzeug 为 flask 封装了 wsgi 与 http server 相关的功能。
任何一个 web framework 都不会脱离 serverurlrequestresponsesession 这几个模块,我的关注点也主要集中在这几个模块,当然,Flask 的上下文和 werkzeug 也会涉及。

阅读本文需要的 Python 环境:

1
2
3
4
5
(.open_the_flask_env) ➜  flask git:(8605cc3) ✗ pip freeze
Flask==0.1
Jinja2==2.9.6
MarkupSafe==1.0
Werkzeug==0.12.2

How to run

我们先构建一个最小应用:

1
2
3
4
5
6
7
8
9
10
11
12
13
# hello.py
from flask import Flask
app = Flask(__name__)


@app.route('/')
def hello_world():
return 'Hello World!'


if __name__ == '__main__':
app.run()

app 就是一个 flask 应用,我们通过调用 app 的方法 app.run 来启动应用。

1
2
3
4
5
6
7
def run(self, host='localhost', port=5000, **options):
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)

run 方法接受了 app 调用时传递的 host 、监听的 port以及是否开启 debug 模式,并把这几个参数传递给 werkzeug 的 run_simple 函数,run_simple 的第三个参数 self 是我们创建的 flask app 。 run_simple 函数通过层层调用,终于把参数传递到 BaseWSGIServer ,从而启动了一个 wsgi 应用。BaseWSGIServer 的作用主要是 HTTP 的请求格式转换成 WSGI 格式。 BaseWSGIServer 在启动的同时,也启用了 WSGIRequestHandler , WSGIRequestHandler 会处理客户端的请求,并且发送响应给客户端。但是 WSGIRequestHandlerwerkzeug 层面的类,它是怎么处理 flask 的 request 的呢?其实 wsgi 应用是可嵌套的。我们尝试在 WSGIRequestHandlerexecute 方法打印一下参数:

1
2
3
4
5
6
7
8
9
10
def execute(app):
application_iter = app(environ, start_response)
print '-------'
print "app is {}".format(app)
print '-------'
try:
for data in application_iter:
write(data)
if not headers_sent:
write(b'')

打印的结果是:

1
2
3
4
 * Running on http://localhost:5000/ (Press CTRL+C to quit)
-------
app is <flask.Flask object at 0x107604150>
-------

我们发现这个 app 参数就是 hello.py 中的 flask app。 flask 实现了一个 __call__ 方法,使得 werkzeug 能够调用 flask 实例。

1
2
3
4
5
6
# flask.py
# flask.__call__

def __call__(self, environ, start_response):
"""Shortcut for :attr:`wsgi_app`"""
return self.wsgi_app(environ, start_response)

Route

上面的步骤就算是成功启动了 wsgi 应用,应用启动之后,就应该想办法根据用户请求的 uri 或者 http method 来调用应用中响应的代码块了。

继续看上一部分中的 __call__ method ,注意它返回的 wsgi_app ,我们先来看看 wsgi_app 长什么样子:

1
2
3
4
5
6
7
8
def wsgi_app(self, environ, start_response):
with self.request_context(environ):
rv = self.preprocess_request()
if rv is None:
rv = self.dispatch_request()
response = self.make_response(rv)
response = self.process_response(response)
return response(environ, start_response)

我们可以看到 wsgi_app 中的先接受了用户请求,然后将用户请求加入 request_context ,(我们先跳过请求上下文的内容),将请求加入请求上下文之后,先预处理一下用户请求。
重点来了,我们继续看 wsgi_app 是怎么分发请求给响应函数的,这里调用了一个 dispatch_request() method ,我们来看看 dispatch_request 长什么样子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def dispatch_request(self):
try:
endpoint, values = self.match_request()
return self.view_functions[endpoint](**values)
except HTTPException, e:
handler = self.error_handlers.get(e.code)
if handler is None:
return e
return handler(e)
except Exception, e:
handler = self.error_handlers.get(500)
if self.debug or handler is None:
raise
return handler(e)

dispatch_request 函数会根据请求的 url 规则去匹配响应的 endpoint ,上面提到的request_context 会将用户请的 url 记录,dispatch_request() 中调用的 match_request() 方法会将之前记录的 url和视图函数中的 endpoint 相对应起来。 而 endpoint 是怎么让 dispatch_request() 发现的呢?答案是 werkzeugflask 有一套很神奇的 url match 机制。我们来看一下 flask 中的 route method :

1
2
3
4
5
6
7
8
9
10
11
12
def route(self, rule, **options):
"""A decorator that is used to register a view function for a
given URL rule. This does the same thing as :meth:`add_url_rule`
but is intended for decorator usage.
"""

def decorator(f):
endpoint = options.pop('endpoint', None)
self.add_url_rule(rule, endpoint, f, **options)
return f

return decorator

route 方法内嵌了一个 decorator ,装饰器将路由规则, endpoint以及视图函数传递给 add_url_rule 方法,通过调用 add_url_rule 方法的调用,就实现了一个完整 url 的 register 。我们可以来看一下例子:

1
2
3
@app.route('/')
def hello()
return "hello world!"

例子中的 hello() 函数被 route 装饰器修饰,route 装饰器中的参数 '/' 就是 add_url_rule 方法中的形参 rule ,函数名 hello 就是参数列表中的 endpoint ,而函数 hello() 本身,就是参数列表中的 f (f -> function) 。通过装饰器实现路由注册的机制非常优雅,想要注册新路由的时候只要调用一下 @ 一下 route ,并给 route 方法响应的参数就行了,不用每次都给视图函数传递包含用户请求信息 environ 参数,以及处理完视图函数之后需要处理的 start_response 操作,写一个路由就像写一个普通的函数一样容易,非常干净得体。