Flask是一个使用 Python 编写的轻量级 Web 应用框架。其 WSGI 工具箱采用 Werkzeug ,模板引擎则使用 Jinja2 。
我们先从flask 0.1版本阅读起。
一、安装Flask 0.1
因为flask 0.1暂时不支持python3,所以我们使用python2.7版本:
|
|
二、Hello World
这是一个很简单的示例,编写hello.py,参考官方网站的示例:
|
|
运行hello.py后,打开浏览器访问 localhost:5000可以看到浏览器输出Hello World!。
三、知识准备
在对flask有了一个比较简单的认识之后,我们知道,flask是基于Jinja2和Werkzeug的一个框架,其WSGI工具箱采用Werkzeug,模板引擎则使用 Jinja2。
3.1 WSGI
WSGI是Web Server Gateway Interface的缩写,是为Python语言定义的Web服务器和Web应用程序或框架之间的一种简单而通用的接口。引用一张图说明WSGI的位置:
WSGI接口定义非常简单,它只要求Web开发者实现一个函数,就可以响应HTTP请求。我们来看一个hello world:
|
|
- 上面的application()函数就是符合WSGI标准的一个HTTP处理函数,函数必须由WSGI服务器来调用。
- environ是一个字典,该字典可以包含了客户端请求的信息以及其他信息,可以认为是请求上下文,一般叫做environment(编码中多简写为environ、env)
- start_response返回了http响应的header,Header只能发送一次,也就是这个函数只能调用一次。
有很多符合WSGI规范的服务器,我们可以挑选一个来用。比如:
|
|
3.2 Jinja2
Jinja2是一个模板引擎,jinja2内部使用Unicode,一个比较简单的示例如下:
|
|
或者
|
|
更高级的用法可以参考官方文档 : http://docs.jinkan.org/docs/jinja2/
3.3 Werkzeug
Werkzeug是一个WSGI工具包,官网将其描述为:The Python WSGI Utility Library。
一个简单的例子实现一个小的 Hello World 应用。显示用户输入的名字:
|
|
另外不用 request 和 response 对象也可以实现这个功能,那就是借助 werkzeug 提供的 解析函数:
|
|
通常我们更倾向于使用高级的 API(request 和 response 对象)。但是也有些情况你可能更 想使用低级功能。
四、Flask-0.1源码分析
有了上面的基础之后,我们可以开始分析Flask的源码了。完整的源码可以参考这里:Flask.py
首先我们再来回顾一下Hello World是怎么样的:
|
|
先初始化一个Flask的类,然后路由hello方法,最后再run起来。
4.1 Flask init()
我们来分析一下Flask类的初始化函数:
|
|
template_context_processors的内容涉及context,这个后面再讲。
self.url_map这个函数保存了URI(访问的后缀URL,比如/,/index/等)到视图函数字典的映射,我们可以看到路由这个函数是如何实现的:
|
|
假如我们写了:
|
|
相当于执行了:
|
|
对装饰器不太熟悉的同学,可以参考我的另外一篇文章:Python 装饰器decorator
4.2 wsgi_app
当我们执行app.run函数的时候,最终会执行到wsgi_app函数,这个函数是Flask的入口核心函数:
|
|
最终会执行wsgi_app,这个函数是flask的入口,也是核心:
|
|
4.2.1 生成request_context
我们一个一个来分析,先看是如何创建request context的:
|
|
URL Adappter更多,可以点击这里 : Werkzeug 文档说明
关于Session和Cookie,我这里多补充几句:
- Session是在服务端保存的一个数据结构,用来对用户会话进行跟踪的一个机制,根据不同的Session ID来标识不同的用户,这个数据可以保存在集群、数据库、文件中。常见的使用场景,比如购物车等。
- Cookie是客户端保存用户信息的一种机制,用来记录用户的一些信息。每次HTTP请求的时候,客户端都会发送相应的Cookie信息到服务端。
- Session ID一般是存在Cookie中,所以如果浏览器禁用了Cookie,同时Session 也会失效(但是可以通过其它方式实现,比如在 url 中传递 session_id)
4.2.2 预处理preprocess_request
我们再来看看先调用预处理函数preprocess_request:
|
|
会遍历before_request_funcs这个列表的函数并且执行,这个列表函数通过装饰器@app.before_request来赋值:
|
|
4.2.3 分发请求dispatch_request()
简单一句话,这个函数的作用就是匹配到Request对应的URL和视图函数,并执行这个函数:
|
|
4.2.4 生成response对象
生成一个response对象,并返回:
|
|
如果不是response对象,则转化response对象,response对象继承自ResponseBase对象,其实就是Response对象。
若需要进一步了解,可以访问:http://werkzeug-docs-cn.readthedocs.io/zh_CN/latest/quickstart.html#response
4.2.5 self.process_response(response)
生成response对象后,保存session并执行一些request后的函数,最后再返回response对象:
|
|
在所有都处理完成后,把需要返回的内容返回给客户端:
|
|
至此,flask 0.1主框架大致分析完成了。
五、Flask Context机制
细心的同学在上面应该注意到了,我们还有一个点没有讲,那就是Flask的Context机制。
Flask 中有分为请求上下文和应用上下文:
对象 | Context类型 | 说明 |
---|---|---|
current_app | AppContext | 当前应用的对象 |
g | AppContext | 处理请求时用作临时存储的对象 |
request | RequestContext | 请求request对象 |
session | RequestContext | 请求的session对象 |
拉到Flask.py最后面,我们可以看到有以下几行:
|
|
所有的对象都交由request_ctx_stack这个堆栈来管理了。
5.1 LocakStack()
LocalStack()会返回一个栈。栈肯定会有push 、pop和top函数,如下所示:
|
|
按照我们的理解,要实现一个栈,那么LocalStack类应该有一个成员变量,是一个list,然后通过 这个list来保存栈的元素。然而,LocalStack并没有一个类型是list的成员变量, LocalStack仅有一个成员变量self._local = Local()。
我们再看push,pop,top函数可以知道,具体是通过self._local.stack这个来实现栈的操作。
5.2 Local()
当我们操作self._local.stack时,会调用Local()的getattr和setattr方法。
|
|
Local类有两个成员变量,分别是storage和ident_func,其中,前者 是一个字典,后者是一个函数。
这个函数的含义是,获取当前线程的id。
例如,当我们执行:
|
|
注意,这里赋值的时候,最终会调用:
|
|
所以最终看起来会是这样一个数据结构:
|
|
至此,Flask0.1版本的源码已经大致分析完成,其实如果继续下去的话,还有很多值得深究的地方,待后续有时间继续深入分析。