一. 最小的应用
1
2
3
4
5
6
7
8
9
10
11
12
# -*- coding: utf-8 -*-
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello_world():
import ipdb
ipdb.set_trace()
return 'Hello World!'
if __name__ == '__main__':
app.run()
二. 分析工具
ipdb
ipdb exports functions to access the IPython debugger, which features tab completion, syntax highlighting, better tracebacks, better introspection with the same interface as the pdb module.
分析
1
2
3
4
5
6
7
8
9
10
11
12
# -*- coding: utf-8 -*-
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello_world():
import ipdb
ipdb.set_trace()
return 'Hello World!'
if __name__ == '__main__':
app.run()
在终端执行:
1
2
3
4
5
6
7
8
9
(flask_env) xiezhigang@ pro 1$ ipdb hello.py
> /home/xiezhigang/PycharmProjects/flask_env/pro/hello.py(1)<module>()
----> 1 from flask import Flask
2 app = Flask(__name__)
3
ipdb> return
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
Tips: 在python2.7,可以使用python -m ipdb hello.py
另起一个终端访问:
1
(flask_env) xiezhigang@ pro 19$ curl 127.0.0.1:5000
此时,第一个终端返回:
1
2
3
4
> /home/xiezhigang/PycharmProjects/flask_env/pro/hello.py(8)hello_world()
7 ipdb.set_trace()
----> 8 return 'Hello World!'
9
然后,在第一个终端查看断点的函数调用过程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
ipdb> w
/home/xiezhigang/PycharmProjects/flask_env/bin/ipdb(11)<module>()
9 if __name__ == '__main__':
10 sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
---> 11 sys.exit(main())
/home/xiezhigang/PycharmProjects/flask_env/local/lib/python2.7/site-packages/ipdb/__main__.py(198)main()
197 try:
--> 198 pdb._runscript(mainpyfile)
199 if pdb._user_requested_quit:
/usr/lib/python2.7/pdb.py(1233)_runscript()
1232 statement = 'execfile(%r)' % filename
-> 1233 self.run(statement)
1234
/usr/lib/python2.7/bdb.py(400)run()
399 try:
--> 400 exec cmd in globals, locals
401 except BdbQuit:
<string>(1)<module>()
/home/xiezhigang/PycharmProjects/flask_env/pro/hello.py(11)<module>()
9
10 if __name__ == '__main__':
---> 11 app.run()
/home/xiezhigang/PycharmProjects/flask_env/local/lib/python2.7/site-packages/flask/app.py(841)run()
840 try:
--> 841 run_simple(host, port, self, **options)
842 finally:
/home/xiezhigang/PycharmProjects/flask_env/local/lib/python2.7/site-packages/werkzeug/serving.py(736)run_simple()
735 else:
--> 736 inner()
737
/home/xiezhigang/PycharmProjects/flask_env/local/lib/python2.7/site-packages/werkzeug/serving.py(699)inner()
698 log_startup(srv.socket)
--> 699 srv.serve_forever()
700
/home/xiezhigang/PycharmProjects/flask_env/local/lib/python2.7/site-packages/werkzeug/serving.py(536)serve_forever()
535 try:
--> 536 HTTPServer.serve_forever(self)
537 except KeyboardInterrupt:
/usr/lib/python2.7/SocketServer.py(238)serve_forever()
237 if self in r:
--> 238 self._handle_request_noblock()
239 finally:
/usr/lib/python2.7/SocketServer.py(295)_handle_request_noblock()
294 try:
--> 295 self.process_request(request, client_address)
296 except:
/usr/lib/python2.7/SocketServer.py(321)process_request()
320 """
--> 321 self.finish_request(request, client_address)
322 self.shutdown_request(request)
/usr/lib/python2.7/SocketServer.py(334)finish_request()
333 """Finish one request by instantiating RequestHandlerClass."""
--> 334 self.RequestHandlerClass(request, client_address, self)
335
/usr/lib/python2.7/SocketServer.py(649)__init__()
648 try:
--> 649 self.handle()
650 finally:
/home/xiezhigang/PycharmProjects/flask_env/local/lib/python2.7/site-packages/werkzeug/serving.py(232)handle()
231 try:
--> 232 rv = BaseHTTPRequestHandler.handle(self)
233 except (socket.error, socket.timeout) as e:
/usr/lib/python2.7/BaseHTTPServer.py(340)handle()
339
--> 340 self.handle_one_request()
341 while not self.close_connection:
/home/xiezhigang/PycharmProjects/flask_env/local/lib/python2.7/site-packages/werkzeug/serving.py(267)handle_one_request()
266 elif self.parse_request():
--> 267 return self.run_wsgi()
268
/home/xiezhigang/PycharmProjects/flask_env/local/lib/python2.7/site-packages/werkzeug/serving.py(209)run_wsgi()
208 try:
--> 209 execute(self.server.app)
210 except (socket.error, socket.timeout) as e:
/home/xiezhigang/PycharmProjects/flask_env/local/lib/python2.7/site-packages/werkzeug/serving.py(197)execute()
196 def execute(app):
--> 197 application_iter = app(environ, start_response)
198 try:
/home/xiezhigang/PycharmProjects/flask_env/local/lib/python2.7/site-packages/flask/app.py(1997)__call__()
1996 """Shortcut for :attr:`wsgi_app`."""
-> 1997 return self.wsgi_app(environ, start_response)
1998
/home/xiezhigang/PycharmProjects/flask_env/local/lib/python2.7/site-packages/flask/app.py(1982)wsgi_app()
1981 try:
-> 1982 response = self.full_dispatch_request()
1983 except Exception as e:
/home/xiezhigang/PycharmProjects/flask_env/local/lib/python2.7/site-packages/flask/app.py(1612)full_dispatch_request()
1611 if rv is None:
-> 1612 rv = self.dispatch_request()
1613 except Exception as e:
/home/xiezhigang/PycharmProjects/flask_env/local/lib/python2.7/site-packages/flask/app.py(1598)dispatch_request()
1597 # otherwise dispatch to the handler for that endpoint
-> 1598 return self.view_functions[rule.endpoint](**req.view_args)
1599
> /home/xiezhigang/PycharmProjects/flask_env/pro/hello.py(8)hello_world()
7 ipdb.set_trace()
----> 8 return 'Hello World!'
9
Pycharm
PyCharm provides smart code completion, code inspections, on-the-fly error highlighting and quick-fixes, along with automated code refactorings and rich navigation capabilities.
这里使用的Pycharm的版本是2017.1,早在Pycharm4.5就提供了一个Profile功能。
- 在Pycharm的右上角有一排按钮,找一下即可,或者在文件上右击,点击选项“Profile xxx”。
- 默认支持cProfile,提供两种视图Statistics和Call Graph。
- 在Call Graph视图中,右击视图中的方框可以跳转到对应的函数。
下面看看上面这个简单应用的调用过程:
三. 调用顺序
由ipdb输出的日志,可得知执行hello.py的函数调用链如下:
1
2
3
4
5
6
7
8
run()[flask/app.py] --> run_simple()[werkzeug/serving.py] --> inner()[werkzeug/serving.py]
--> serve_forever()[werkzeug/serving.py] --> serve_forever()[python2.7/SocketServer.py]
--> _handle_request_noblock()[python2.7/SocketServer.py]
--> process_request()[python2.7/SocketServer.py] --> handle()[werkzeug/serving.py]
--> handle()[python2.7/BaseHTTPServer.py] --> handle_one_request()[werkzeug/serving.py]
--> run_wsgi()[werkzeug/serving.py] --> __call__()[flask/app.py]--> wsgi_app()[flask/app.py]
--> full_dispatch_request()[flask/app.py] --> dispatch_request()[flask/app.py]
--> hello_world()[pro/hello.py];
由pychamr的Profile功能,可得知执行hello.py的函数调用链如下:
1
run()-->run_simple()-->innner()-->serve_forever()-->serve_forever()-->_entry_retry()-->select.select-->fileno
四. 抽象
从上面的函数调用链,让我们回归到flask本身,抽象werkzeug和werkzeug调用的底层函数,那么抽象后的函数调用链应如下:
1
2
run()[flask/app.py] --> run_simple()[werkzeug/serving.py]--> full_dispatch_request()[flask/app.py]
--> dispatch_request()[flask/app.py] --> hello_world()[pro/hello.py]
五. 启动流程
应用启动app.run(), 代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def run(self, host=None, port=None, debug=None, **options):
"""comments have been removed.
"""
from werkzeug.serving import run_simple
if host is None:
host = '127.0.0.1'
if port is None:
server_name = self.config['SERVER_NAME']
if server_name and ':' in server_name:
port = int(server_name.rsplit(':', 1)[1])
else:
port = 5000
if debug is not None:
self.debug = bool(debug)
options.setdefault('use_reloader', self.debug)
options.setdefault('use_debugger', self.debug)
try:
run_simple(host, port, self, **options)
finally:
# reset the first request information if the development server
# reset normally. This makes it possible to restart the server
# without reloader and that stuff from an interactive shell.
self._got_first_request = False
这段代码非常简单,无非是处理一下参数,然后调用werkzeug的run_simple函数来处理创建的Flask的appication,注意:run_simple的第三个参数是self。 此处只研究Flask,werkzeug的内在逻辑就不深入探讨了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def full_dispatch_request(self):
"""Dispatches the request and on top of that performs request
pre and postprocessing as well as HTTP exception catching and
error handling.
.. versionadded:: 0.7
"""
self.try_trigger_before_first_request_functions()
try:
request_started.send(self)
rv = self.preprocess_request()
if rv is None:
rv = self.dispatch_request()
except Exception as e:
rv = self.handle_user_exception(e)
return self.finalize_request(rv)
这段代码的核心在于处理请求hooks处理的逻辑和dispatch_request(),以及错误处理和返回响应。
那我们来看看这些hooks函数都做了哪些操作。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def try_trigger_before_first_request_functions(self):
"""Called before each request and will ensure that it triggers
the :attr:`before_first_request_funcs` and only exactly once per
application instance (which means process usually).
:internal:
"""
if self._got_first_request:
return
with self._before_request_lock:
if self._got_first_request:
return
for func in self.before_first_request_funcs:
func()
self._got_first_request = True
这段代码的核心在于self._before_request_lock和self.before_first_request_funcs,self._before_request_lock是一个线程锁,保证了一次性只有一个app实例,self.before_first_request_funcs是一个字典,把一系列第一次请求处理前的函数注册在self.before_first_request_funcs中,这个hook是通过before_first_request定义。
执行完第一次请求前的hook函数后,开始发出一个信号(signal,下面的文章会研究一下这个模块),有一个请求进来了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
def preprocess_request(self):
"""Called before the actual request dispatching and will
call each :meth:`before_request` decorated function, passing no
arguments.
If any of these functions returns a value, it's handled as
if it was the return value from the view and further
request handling is stopped.
This also triggers the :meth:`url_value_preprocessor` functions before
the actual :meth:`before_request` functions are called.
"""
bp = _request_ctx_stack.top.request.blueprint
funcs = self.url_value_preprocessors.get(None, ())
if bp is not None and bp in self.url_value_preprocessors:
funcs = chain(funcs, self.url_value_preprocessors[bp])
for func in funcs:
func(request.endpoint, request.view_args)
funcs = self.before_request_funcs.get(None, ())
if bp is not None and bp in self.before_request_funcs:
funcs = chain(funcs, self.before_request_funcs[bp])
for func in funcs:
rv = func()
if rv is not None:
return rv
这个hook是在每个请求处理前处理的,通过before_request定义,主要是找到app中定义的bp(蓝图),并注册到self.before_request_funcs。
然后开始正常的处理请求函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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`.
.. versionchanged:: 0.7
This no longer does the exception handling, this code was
moved to the new :meth:`full_dispatch_request`.
"""
req = _request_ctx_stack.top.request
if req.routing_exception is not None:
self.raise_routing_exception(req)
rule = req.url_rule
# if we provide automatic options for this URL and the
# request came with the OPTIONS method, reply automatically
if getattr(rule, 'provide_automatic_options', False) \
and req.method == 'OPTIONS':
return self.make_default_options_response()
# otherwise dispatch to the handler for that endpoint
return self.view_functions[rule.endpoint](**req.view_args)
这段代码主要是找到处理函数,返回处理结果,这是路由的全过程。
此外,正常请求处理之后的hook函数–self.finalize_request(rv),这个函数是通过after_request定义的,还有异常处理的函数。
当一个请求结束时,会执行pop操作,并触发do_teardown_request和teardown_request,因此不管请求是否异常,都会执行teardown_request。 此时一个简单的Flask的hello_world程序正常输出了“Hello World”。