flask基本流程

一、WSGI程序

在分析flask的启动流程之前,我们有必要了解一些web程序的基础——WSGI。flask的核心Werkzeug是一个WSGI工具库,WSIGI(Python Web Server Gateway Interface)是为了让web服务器与python程序能够进行数据交流而定义的一套接口标准。

根据WSGI标准的规定,web程序必须是一个可调用对象,该对象接收两个参数:

  1. environ: 包含请求的所有信息,是一个字典对象
  2. start_response: 需在可调用对象中调用的函数,用来发起响应,参数是状态码,响应头等

WSGI服务器会在调用这个可调用对象时传入这两个参数。另外,该可调用对象还需要返回一个可迭代的对象。

一个简单的WSGI程序如下:

1
2
3
4
5
6
7
8
9
10
11
from wsgiref.simple_server import make_server

def hello(environ, start_response):
status = '200 OK'
response_head = [('Content-type', 'text/html')]
start_response(status, response_head)
return [b'hello world']

if __name__ == '__main__':
server = make_server('localhost', 5000, hello)
server.serve_forever()

hello()函数就是一个可调用对象,也就是web程序,返回含有一个字节串的列表,这是因为WSGI规定了请求和响应主体应该为字节串。

python提供了wsgiref库,可作为一个简易的WSGI服务器在开发时使用。make_server()方法创建了一个本地服务器,分别传入主机地址、端口和可调用对象。最后通过server_forver()启动。

WSGI服务器启动后会监听传入主机对应的端口,当收到请求时,会把请求报文解析成一个environ字典,然后调用可调用对象,将environ、start_response作为参数传入。

运行该程序,在浏览器输入localhost:5000后便能看见一行’hello world’。

Gunicorn、uWSGI等都是实现了WSGI规范的服务器,正是因为遵循了统一的规范,所以这些服务器都可以运行flask程序。

二、flask的工作流程

1.程序启动

flask提供了两种启动服务器的方式:

  1. 命令行中输入flask run(实际调用flask.cli.run_command())
  2. 调用flask.Flask.run()方法

无论哪种方法,最后都是调用了werkzeug.serving模块中的run_simple()函数

函数如下:

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
def run_simple(hostname, port, application, use_reloader=False,
use_debugger=False, use_evalex=True,
extra_files=None, reloader_interval=1,
reloader_type='auto', threaded=False,
processes=1, request_handler=None, static_files=None,
passthrough_errors=False, ssl_context=None):

if not isinstance(port, int):
raise TypeError('port must be an integer')
if use_debugger:
from werkzeug.debug import DebuggedApplication
application = DebuggedApplication(application, use_evalex)
if static_files:
from werkzeug.wsgi import SharedDataMiddleware
application = SharedDataMiddleware(application, static_files)

def log_startup(sock):
display_hostname = hostname not in ('', '*') and hostname or 'localhost'
if ':' in display_hostname:
display_hostname = '[%s]' % display_hostname
quit_msg = '(Press CTRL+C to quit)'
port = sock.getsockname()[1]
_log('info', ' * Running on %s://%s:%d/ %s',
ssl_context is None and 'http' or 'https',
display_hostname, port, quit_msg)

def inner():
try:
fd = int(os.environ['WERKZEUG_SERVER_FD'])
except (LookupError, ValueError):
fd = None
srv = make_server(hostname, port, application, threaded,
processes, request_handler,
passthrough_errors, ssl_context,
fd=fd)
if fd is None:
log_startup(srv.socket)
srv.serve_forever()

if use_reloader:

if os.environ.get('WERKZEUG_RUN_MAIN') != 'true':
if port == 0 and not can_open_by_fd:
raise ValueError('Cannot bind to a random port with enabled '
'reloader if the Python interpreter does '
'not support socket opening by fd.')

address_family = select_ip_version(hostname, port)
s = socket.socket(address_family, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(get_sockaddr(hostname, port, address_family))
if hasattr(s, 'set_inheritable'):
s.set_inheritable(True)


if can_open_by_fd:
os.environ['WERKZEUG_SERVER_FD'] = str(s.fileno())
s.listen(LISTEN_QUEUE)
log_startup(s)
else:
s.close()

from werkzeug._reloader import run_with_reloader
run_with_reloader(inner, extra_files, reloader_interval,
reloader_type)
else:
inner()

此方法最后会调用inner()函数,它使用make_server()创建服务器,之后调用server_forever()方法运行服务器。当收到请求时,WSGI服务器会调用web程序中提供的可调用对象,该对象就是实例app。

2.请求和响应

Flask类实现了__call__方法,当实例被调用时会执行该方法,这个方法内部调用了wsgi_app()方法:

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
def wsgi_app(self, environ, start_response):
# 生成请求上下文
ctx = self.request_context(environ)
error = None
try:
try:
# 请求上下文入栈
ctx.push()
# 尝试通过full_dispatch_request()获取响应
response = self.full_dispatch_request()
except Exception as e:
error = e
# 若获取响应失败,则通过错误类型生成错误响应
response = self.handle_exception(e)
except:
error = sys.exc_info()[1]
raise
return response(environ, start_response)
finally:
if self.should_ignore_error(error):
error = None
ctx.auto_pop(error)

def __call__(self, environ, start_response):
"""The WSGI server calls the Flask application object as the
WSGI application. This calls :meth:`wsgi_app` which can be
wrapped to applying middleware."""
return self.wsgi_app(environ, start_response)

通过wsgi_app方法的参数就能看出,wsgi_app()方法就是隐藏在Flask中的WSGI程序。

该方法包括了上下文的生成,请求的分发,返回请求的响应。

其中,比较重要的一个就是请求上下文的生成,也就是 request_context() 以及 push() 到栈中,其中的 class RequestContext 就是保持一个请求的上下文的变量,在请求到来之前 _request_ctx_stack 一直是空的,当请求到来的时候会调用 ctx.push() 向栈中添加上下文信息。

在每次请求调用结束后,也就是在 wsgi_app() 中的 finally 中会调用 ctx.auto_pop(error),该函数中会根据情况判断是否清除放在 _request_ctx_stack 中的 ctx 。

关于上下文的详细内容可以参考 这篇文章

接下来是处理请求的分发。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def full_dispatch_request(self):
# 判断是否为第一次执行
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)

在 dispatch_request() 函数中就会匹配并调用对应的视图函数,获取其返回值,将返回值赋值给rv。最后,接收视图函数返回值的finalize_request会用这个值来生成响应