阅读Flask源码
看到很多人推荐新手去阅读Flask,但其实个人并不推荐,因为单说Flask的话,代码量 确实很少,但是Flask完全建立在Werkzeug之上,如果把Werkzeug的代码加起来,代码量 可就不少了,而要完全弄懂Flask,就一定少不了看Werkzeug。
接下来,我们就一起来读一下Flask0.1的代码吧 :)
首先,我们把代码clone下来:
.. code:: bash
$ git clone https://github.com/pallets/flask
$ cd flask
$ git checkout 0.1
打开 flask.py
你就能看到,其实没多少行代码,按照惯例,找一下看有没有入口
函数,拉到最下面,没有 __main__
,但是我们发现了这个:
.. code:: python
# context locals
_request_ctx_stack = LocalStack()
current_app = LocalProxy(lambda: _request_ctx_stack.top.app)
request = LocalProxy(lambda: _request_ctx_stack.top.request)
session = LocalProxy(lambda: _request_ctx_stack.top.session)
g = LocalProxy(lambda: _request_ctx_stack.top.g)
LocalStack
??? LocalProxy
??? 这是什么东西。。。看一下文件,原来是从
Werkzeug导入的::
from werkzeug import Request as RequestBase, Response as ResponseBase, \
LocalStack, LocalProxy, create_environ, cached_property, \
SharedDataMiddleware
嗯,看来终究我们都逃脱不了Werkzeug的魔爪,不过,我们暂时先不理他,我们先来看看
flask的helloworld程序的调用栈是怎样的,这样有利于我们有看代码的目标,而不是拿着
代码毫无目的的随便看,首先我们新建一个virtualenv,然后装上jinja2和werkzeug,并
且把之前0.1版本的 flask.py
拷贝到那个目录,激活当前venv(这个我就不贴出来了):
.. code:: python
# coding: utf-8
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
import pdb; pdb.set_trace() # TODO remove it
return "hello world"
if __name__ == "__main__":
app.run()
然后运行,并且新开一个终端或者浏览器的tab,来访问::
$ python2 main.py
$ # 另一个终端
$ http localhost:5000
然后就会执行到断点,此时我们再pdb里打印出当前调用栈:
.. code:: python
(py2kenv) ➜ src python main.py
* Running on http://localhost:5000/ (Press CTRL+C to quit)
> /home/jiajun/Code/python/py2kenv/src/main.py(12)hello()
-> return "hello world"
(Pdb) w
/home/jiajun/Code/python/py2kenv/src/main.py(16)<module>()
-> app.run()
/home/jiajun/Code/python/py2kenv/src/flask.py(331)run()
-> return run_simple(host, port, self, **options)
/home/jiajun/Code/python/py2kenv/lib/python2.7/site-packages/werkzeug/serving.py(694)run_simple()
-> inner()
/home/jiajun/Code/python/py2kenv/lib/python2.7/site-packages/werkzeug/serving.py(659)inner()
-> srv.serve_forever()
/home/jiajun/Code/python/py2kenv/lib/python2.7/site-packages/werkzeug/serving.py(499)serve_forever()
-> HTTPServer.serve_forever(self)
/usr/lib64/python2.7/SocketServer.py(233)serve_forever()
-> self._handle_request_noblock()
/usr/lib64/python2.7/SocketServer.py(290)_handle_request_noblock()
-> self.process_request(request, client_address)
/usr/lib64/python2.7/SocketServer.py(318)process_request()
-> self.finish_request(request, client_address)
/usr/lib64/python2.7/SocketServer.py(331)finish_request()
-> self.RequestHandlerClass(request, client_address, self)
/usr/lib64/python2.7/SocketServer.py(652)__init__()
-> self.handle()
/home/jiajun/Code/python/py2kenv/lib/python2.7/site-packages/werkzeug/serving.py(216)handle()
-> rv = BaseHTTPRequestHandler.handle(self)
/usr/lib64/python2.7/BaseHTTPServer.py(340)handle()
-> self.handle_one_request()
/home/jiajun/Code/python/py2kenv/lib/python2.7/site-packages/werkzeug/serving.py(251)handle_one_request()
-> return self.run_wsgi()
/home/jiajun/Code/python/py2kenv/lib/python2.7/site-packages/werkzeug/serving.py(193)run_wsgi()
-> execute(self.server.app)
/home/jiajun/Code/python/py2kenv/lib/python2.7/site-packages/werkzeug/serving.py(181)execute()
-> application_iter = app(environ, start_response)
/home/jiajun/Code/python/py2kenv/src/flask.py(655)__call__()
-> return self.wsgi_app(environ, start_response)
/home/jiajun/Code/python/py2kenv/lib/python2.7/site-packages/werkzeug/wsgi.py(599)__call__()
-> return self.app(environ, start_response)
/home/jiajun/Code/python/py2kenv/src/flask.py(626)wsgi_app()
-> rv = self.dispatch_request()
/home/jiajun/Code/python/py2kenv/src/flask.py(544)dispatch_request()
-> return self.view_functions[endpoint](**values)
> /home/jiajun/Code/python/py2kenv/src/main.py(12)hello()
-> return "hello world"
我们从下往上看,或者从上往下看,都能推理出我们的调用链::
app.run[main.py] -> run_simple[flask.py] -> srv.serve_forever[werkzeug] \
-> HTTPServer.serve_forever -> SocketServer._handle_request_noblock -> \
SocketServer.process_request -> werkzeug.run_wsgi -> flask.dispatch_request \
-> app.view_functions[endpoint](**values)[也就是我们的 `def hello`]
哇,好长,不行,如果一个一个深究,恐怕是要很长时间,无论是看代码还是写代码,我们 都要学会抽象,也就是说,我们要把某一部分东西当做一个模块,我们不管他里面是怎么实现 的,只管,他会完成这样的功能。所以为了顺利的看完 flask 的代码,我们不管 werkzeug 以下的调用链,也就是不管 SocketServer, HTTPServer 之类的。简化一下上面的调用链 (其实在这里我们所说的抽象,就是自动忽略细节)::
app.run[main.py] -> run_simple[flask.py] -> srv.serve_forever[werkzeug] \
-> werkzeug.run_wsgi -> flask.dispatch_request \
-> app.view_functions[endpoint](**values)[也就是我们的 `def hello`]
现在我们是不是已经站在一个更高的角度来看待flask了,werkzeug会自动调用好底层的 SocketServer,有请求来了,就会发到werkzeug上,然后就会处理请求。不过,我们说过, 暂时不管werkzeug,所以我们再来进行一次“抽象”::
app.run[main.py] -> run_simple[flask.py] -> flask.dispatch_request \
-> app.view_functions[endpoint](**values)[也就是我们的 `def hello`]
哦~原来flask这么简单,没错,因为我们忽略了werkzeug做了那么多事情嘛。
好,接下来我们照着调用链,深入到代码里去,首先我们来看 app.run
:
.. code:: python
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)
哦,又是调用werkzeug。。。哎,反正是监听服务器就对了,好,接下来我们看下一个:
.. code:: python
def dispatch_request(self):
"""Does the request dispatching. Matches the URL and returns the
return value of the view or error handler. This does not have to
be a response object. In order to convert the return value to a
proper response object, call :func:`make_response`.
"""
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)
嗯,这个函数我没有把注释去掉,是因为注释还是很有说明性的,一般读代码,有注释 都要先看看注释,就像上面的run函数其实也有注释,但是为了篇幅,我把注释删掉了。 真正读代码的时候,请优先读注释。
这个函数,作用就是,看请求是否有匹配到,如果没有的话,就报错。这一步,同时看完了 调用链的后两部分,但是同时为我们引入了新的问题,那就是,WTF is endpoint???
为了调查真相,再一次进入pdb,然后看看这些都是啥。
.. code:: python
(Pdb) app.view_functions
{'hello': <function hello at 0x7fb9a3f5ce60>}
也就是说,endpoint是一个字符串,然后对应了一个函数,那endpoint是在哪里设置的呢?
.. code:: python
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匹配是这样的: url -> endpoint -> func
要问我为什么?
我也不清楚,可以看看 [#]_ 和 [#]_ ,分别是stackoverflow的一个解释,和我提出的
关于endpoint的作用的问题。如果你能找到endpoint的存在必要性,请务必告知我,谢谢。
看到这里,好像已经看完了flask是怎么工作的了?no,刚刚开始而已。提个问题, 我们知道flask里读取参数是这样的::
from flask import request
test = request.args.get("test", None)
如果有多线程存在,他是怎么做到线程安全的?暂且不说线程,我们是怎么从一个导入的 模块里读取我们当前请求的参数的?
还记得上面我们最开始贴的那段代码嘛?
.. code:: python
# context locals
_request_ctx_stack = LocalStack()
current_app = LocalProxy(lambda: _request_ctx_stack.top.app)
request = LocalProxy(lambda: _request_ctx_stack.top.request)
session = LocalProxy(lambda: _request_ctx_stack.top.session)
g = LocalProxy(lambda: _request_ctx_stack.top.g)
原来 flask.request 是这样一个东西,看来我们有必要深入到werkzeug里看看 LocalProxy
是如何工作的了。不过在此之前我猜我们需要先看看 LocalStack
这个东西,因为上面
的代码显示,他是作为一个参数传到 LocalProxy
里的。
好吧,接下来我们把werkzeug的代码搞下来,然后搜一下这货在哪:
.. code:: bash
(py2kenv) ➜ werkzeug git:(master) ack 'class LocalProxy'
werkzeug/local.py
254:class LocalProxy(object):
(py2kenv) ➜ werkzeug git:(master) ack 'class LocalStack'
werkzeug/local.py
89:class LocalStack(object):
(py2kenv) ➜ werkzeug git:(master)
所以接下来我们打开 local.py
来看看,看到 LocalStack
的注释,这就是一个
栈(名字其实就说明了它是栈)。看到 __init__
里最终存储数据的还是 Local
类,
所以接下来我们来看 Local
类。
.. code:: python
class Local(object):
__slots__ = ('__storage__', '__ident_func__')
def __init__(self):
object.__setattr__(self, '__storage__', {})
object.__setattr__(self, '__ident_func__', get_ident)
def __iter__(self):
return iter(self.__storage__.items())
def __call__(self, proxy):
"""Create a proxy for a name."""
return LocalProxy(self, proxy)
def __release_local__(self):
self.__storage__.pop(self.__ident_func__(), None)
def __getattr__(self, name):
try:
return self.__storage__[self.__ident_func__()][name]
except KeyError:
raise AttributeError(name)
def __setattr__(self, name, value):
ident = self.__ident_func__()
storage = self.__storage__
try:
storage[ident][name] = value
except KeyError:
storage[ident] = {name: value}
def __delattr__(self, name):
try:
del self.__storage__[self.__ident_func__()][name]
except KeyError:
raise AttributeError(name)
这个类的作用就是,存东西的时候,实际上存在 __storage__
里,而它是一个字典。
字典的key是线程id,value是另外的dict。而我们的 LocalStack
就是,之前说的value
里,以 “stack” 为key,list为value的一个键值对,我们来验证一下:
.. code:: python
(Pdb) from flask import _request_ctx_stack as test
(Pdb) test._local
<werkzeug.local.Local object at 0x7fec126179d0>
(Pdb) for i in test._local: print i
(140651991053184, {'stack': [<flask._RequestContext object at 0x7fec11d2d2d0>]})
(Pdb)
通过 LocalProxy
,我们执行如下代码的时候::
from flask import request
@app.route("/")
def hello():
name = request.args.get("name", None)
if name:
return "Hello %s" % name
else:
return "Hello World"
request总是能正确的指向当前所压入的请求。
小结
好了,Flask的代码我们暂时看到这里。下一篇,我准备探索一下, Gunicorn + Gevent的工作原理(也就是,他们俩使怎样让Flask的代码能够异步执行的)。
.. [#] http://stackoverflow.com/questions/19261833/what-is-an-endpoint-in-flask/19262349#19262349 .. [#] https://www.v2ex.com/t/304941#reply11
更多文章
- socks5 协议详解
- zerotier简明教程
- 搞定面试中的系统设计题
- frp 源码阅读与分析(一):流程和概念
- Golang(Go语言)中实现典型的fork调用
- 用peewee代替SQLAlchemy
- DNSCrypt简明教程
- 一个Gunicorn worker数量引发的血案
- Golang validator使用教程
- Docker组件介绍(一):runc和containerd
- Docker组件介绍(二):shim, docker-init和docker-proxy
- 使用Go语言实现一个异步任务框架
- 协程(coroutine)简介 - 什么是协程?
- SQLAlchemy简明教程
- Go Module 简明教程