Flask 源码解析:启动流程

werkzeug 示例

werkzeug 三种启动方式

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
from werkzeug.wrappers import Request, Response
from werkzeug.serving import run_simple


# 方式一:实例化Response,最后 self.request = encode('Hello World application1!')
application1 = Response('Hello World application1!')


#方式二
def application2(environ, start_response):
request = Request(environ)
response = Response("Hello %s!" % request.args.get('name', 'World!'))
return response(environ, start_response)


#方式三
@Request.application
def hello(request):
return Response('Hello World Request!')


if __name__ == '__main__':
# run_simple('localhost', 4000, application1)
# run_simple('localhost', 4000, application2)
run_simple('localhost', 4000, hello)

我们在浏览器输入 http://localhost:4000/ 就会得到 response 信息

接下来我们就简单的分析下,该模块的请求、响应流程

werkzeug 的 request 流程

看 demo 示例,在文件起始处我们引入了 from werkzeug.serving import run_simple。我们跟踪代码看下serving.py 模块下的 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
####################### serving.py 文件下的 run_simple 函数 ############################
def run_simple(
hostname, # 应用程序的主机
port, # 端口
application, # WSGI 应用程序
use_reloader=False, # 如果程序代码修改,是否需要自动启动服务
use_debugger=False, # 程序是否要使用工具和调试系统
use_evalex=True, # 应用是否开启异常评估
extra_files=None, # 重载器应该查看的文件列表附加到模块。例如配置文件夹
reloader_interval=1, # 秒重载器的间隔
reloader_type="auto", # 重载器的类型
threaded=False, # 进程是否处理单线程的每次请求
processes=1, # 如果大于1,则在新进程中处理每个请求。达到这个最大并发进程数
request_handler=None, # 可以自定义替换BaseHTTPRequestHandler
static_files=None, # 静态文件路径的列表或DICT
passthrough_errors=False,# 将此设置为“真”以禁用错误捕获。这意味着服务器会因错误而死亡
ssl_context=None, # 如何进行传输数据加密,可以设置的环境
):

"省略部分代码"
def inner():
try:
fd = int(os.environ["WERKZEUG_SERVER_FD"])
except (LookupError, ValueError):
fd = None
srv = make_server( # 通过 make_server 方法,创建不同的 server 实例。
hostname,
port,
application,
threaded,
processes,
request_handler,
passthrough_errors,
ssl_context,
fd=fd,
)
if fd is None:
log_startup(srv.socket)

# 把服务运行起来。
# serve_forever() 是 HTTPserver 的方法。
# 当有请求过来之后,server_forever 会将 run_simple() 中的第三个参数加括号执行,并传入参数(environ, start_response)
srv.serve_forever()


if use_reloader: # # run_simple 函数中,最后会执行到
"省略部分代码"
else:
inner()

run_simple 调用了 make_server,看下 make_server

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
################ serving.py 文件下的 make_server 方法 #####################
# 根据参数不同,实例化不同的 server
def make_server(host=None, port=None, app=None, threaded=False, processes=1,
request_handler=None, passthrough_errors=False,
ssl_context=None, fd=None):
"""Create a new server instance that is either threaded, or forks
or just processes one request after another.
"""
if threaded and processes > 1:
raise ValueError("cannot have a multithreaded and multi process server.")
elif threaded:
return ThreadedWSGIServer(host, port, app, request_handler, passthrough_errors, ssl_context, fd=fd)
elif processes > 1:
return ForkingWSGIServer(host, port, app, processes, request_handler, passthrough_errors, ssl_context, fd=fd)
else:
return BaseWSGIServer(host, port, app, request_handler, passthrough_errors, ssl_context, fd=fd)

run_simple 调用了 serve_forever,看下 serve_forever

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
############### BaseWSGIServer 类下的 serve_forever 方法 #######################
from http.server import HTTPServer


class BaseWSGIServer(HTTPServer):
def serve_forever(self):
self.shutdown_signal = False
try:
# class HTTPServer(socketserver.TCPServer)未实现 serve_forever ————>
# class TCPServer(BaseServer) 未实现 serve_forever :
# BaseServer 实现 serve_forever
HTTPServer.serve_forever(self)
except KeyboardInterrupt:
pass
finally:
self.server_close()

serve_forever 最终是调用的 BaseServer.serve_forever

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
########## BaseServer 下的 serve_forever #######
# 具体的监听 socket ,当有请求到来时,执行传入的第三个参数,执行格式为 func(self, environ, start_response),并接受返回值
class BaseServer:
def serve_forever(self, poll_interval=0.5):
"""Handle one request at a time until shutdown.

Polls for shutdown every poll_interval seconds. Ignores
self.timeout. If you need to do periodic tasks, do them in
another thread.
"""
self.__is_shut_down.clear()
try:
# XXX: Consider using another file descriptor or connecting to the
# socket to wake this up instead of polling. Polling reduces our
# responsiveness to a shutdown request and wastes cpu at all other
# times.
with _ServerSelector() as selector: # 具体监听过程,就是个无限循环 接收一个请求处理一个请求
selector.register(self, selectors.EVENT_READ)

while not self.__shutdown_request:
ready = selector.select(poll_interval)
if ready:
# 接收到具体请求后 进行处理
self._handle_request_noblock()

self.service_actions()
finally:
self.__shutdown_request = False
self.__is_shut_down.set()

def _handle_request_noblock(self):
"""Handle one request, without blocking.

I assume that selector.select() has returned that the socket is
readable before this function was called, so there should be no risk of
blocking in get_request().
"""
try:
request, client_address = self.get_request()
except OSError:
return
if self.verify_request(request, client_address):
try:
# 接收到具体请求后 进行处理
self.process_request(request, client_address)
except Exception:
self.handle_error(request, client_address)
self.shutdown_request(request)
except:
self.shutdown_request(request)
raise
else:
self.shutdown_request(request)

def process_request(self, request, client_address):
# 接收到具体请求后 进行处理
self.finish_request(request, client_address)
self.shutdown_request(request)

def finish_request(self, request, client_address):
# 每次都实例化了 WSGIRequestHandler 进行请求处理
# 注意第三个参数把当前的 server 实例传入
"""Finish one request by instantiating RequestHandlerClass."""
self.RequestHandlerClass(request, client_address, self)

最终请求的处理是在 WSGIRequestHandler 实例化过程中处理的

那我们来看看 WSGIRequestHandler 的代码

WSGIRequestHandler 的初始化其实走父类的父类 BaseRequestHandler.__init__() 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class BaseRequestHandler:

def __init__(self, request, client_address, server):
self.request = request
self.client_address = client_address
self.server = server
self.setup()
try:
# 具体的处理方法 其实是调用的子类的实现
self.handle()
finally:
self.finish()

def setup(self):
pass

def handle(self):
pass

def finish(self):
pass

调用了 WSGIRequestHandler 下的 handle()

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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
class WSGIRequestHandler(BaseHTTPRequestHandler):

# 处理请求的核心方法
def run_wsgi(self) -> None:
if self.headers.get("Expect", "").lower().strip() == "100-continue":
self.wfile.write(b"HTTP/1.1 100 Continuer\n\r\n")

# 此处经典的将 http 内容转化为符合 wsgi 格式的内容 因为后面是个 wsgi 的 app 啊
self.environ = environ = self.make_environ()
status_set = None
headers_set = None
status_sent = None
headers_sent = None
chunk_response: bool = False

def write(data: bytes) -> None:
nonlocal status_sent, headers_sent, chunk_response
assert status_set is not None, "write() before start_response"
assert headers_set is not None, "write() before start_response"
if status_sent is None:
status_sent = status_set
headers_sent = headers_set
try:
code_str, msg = status_sent.split(None, 1)
except ValueError:
code_str, msg = status_sent, ""
code = int(code_str)
self.send_response(code, msg)
header_keys = set()
for key, value in headers_sent:
self.send_header(key, value)
header_keys.add(key.lower())

# Use chunked transfer encoding if there is no content
# length. Do not use for 1xx and 204 responses. 304
# responses and HEAD requests are also excluded, which
# is the more conservative behavior and matches other
# parts of the code.
# https://httpwg.org/specs/rfc7230.html#rfc.section.3.3.1
if (
not (
"content-length" in header_keys
or environ["REQUEST_METHOD"] == "HEAD"
or (100 <= code < 200)
or code in {204, 304}
)
and self.protocol_version >= "HTTP/1.1"
):
chunk_response = True
self.send_header("Transfer-Encoding", "chunked")

# Always close the connection. This disables HTTP/1.1
# keep-alive connections. They aren't handled well by
# Python's http.server because it doesn't know how to
# drain the stream before the next request line.
self.send_header("Connection", "close")
self.end_headers()

assert isinstance(data, bytes), "applications must write bytes"

if data:
if chunk_response:
self.wfile.write(hex(len(data))[2:].encode())
self.wfile.write(b"\r\n")

self.wfile.write(data)

if chunk_response:
self.wfile.write(b"\r\n")

self.wfile.flush()

# 眼熟吧 调用wsgi app的参数之一
def start_response(status, headers, exc_info=None): # type: ignore
nonlocal status_set, headers_set
if exc_info:
try:
if headers_sent:
raise exc_info[1].with_traceback(exc_info[2])
finally:
exc_info = None
elif headers_set:
raise AssertionError("Headers already set")
status_set = status
headers_set = headers
return write

# 经典的 wsgi app 调用 返回值可迭代
# 此处的 app 是 Flask 实例,实例可调用是因为 Flask 实现了 __call__ 方法
def execute(app: "WSGIApplication") -> None:
application_iter = app(environ, start_response) # 这里这里
try:
for data in application_iter:
write(data)
if not headers_sent:
write(b"")
if chunk_response:
self.wfile.write(b"0\r\n\r\n")
finally:
if hasattr(application_iter, "close"):
application_iter.close()

try:
# 执行此方法 参数为flask实例
execute(self.server.app)
except (ConnectionError, socket.timeout) as e:
self.connection_dropped(e, environ)
except Exception as e:
if self.server.passthrough_errors:
raise

if status_sent is not None and chunk_response:
self.close_connection = True

try:
# if we haven't yet sent the headers but they are set
# we roll back to be able to set them again.
if status_sent is None:
status_set = None
headers_set = None
execute(InternalServerError())
except Exception:
pass

from .debug.tbtools import DebugTraceback

msg = DebugTraceback(e).render_traceback_text()
self.server.log("error", f"Error on request:\n{msg}")

# 上一个类调用的handle就是这个
def handle(self) -> None:
"""Handles a request ignoring dropped connections."""
try:
# 调用父类的handle
super().handle()
except (ConnectionError, socket.timeout) as e:
self.connection_dropped(e)
except Exception as e:
if self.server.ssl_context is not None and is_ssl_error(e):
self.log_error("SSL error occurred: %s", e)
else:
raise
# 没错 下面的类又调用了这个方法
def handle_one_request(self):
"""Handle a single HTTP request."""
self.raw_requestline = self.rfile.readline()
if not self.raw_requestline:
self.close_connection = 1
elif self.parse_request():
# 最终最终最终 走了这个方法
return self.run_wsgi()

父类的 handle()

1
2
3
4
5
6
7
8
9
class BaseHTTPRequestHandler(socketserver.StreamRequestHandler):  
# 上一个类调用的就是这个
def handle(self):
"""Handle multiple requests if necessary."""
self.close_connection = True
# 此处又调用回了 WSGIRequestHandler 的 handle_one_request
self.handle_one_request()
while not self.close_connection:
self.handle_one_request()

上面的调用路线看起来稍微有点绕,是因为涉及到了继承方法的覆盖,有些调用走的父类的,有些走的子类的方法。

可以看到:其实就是监听端口,并开启一个无限的循环,每次接收到一个请求之后,就实例化 WSGIRequestHandler 进行处理,而 WSGIRequestHandler 主要做了 HTTP 格式数据到 WSGI 数据的转换,然后用 WSGI 的方式调用了 Flask 实例进行实际的逻辑处理并返回数据。

简单梳理

在我们示例代码中,当 run_simple('localhost', 4000, application1) 执行后,run_simple() 做了两件事:

  • 一件 make_server,创建服务器
  • 一件 server_forver,一直在监听端口,等待 request。

run_simple 共有三个参数

  • environ:为初步处理的 request 请求
  • start_response:为回调函数
  • Response:的实例化对象,也是具体的 request 入口,负责具体的逻辑,不同的框架其实是第三个参数不同。

当有请求过来之后,server_forever 会调用 _handle_request_noblock() 函数,_handle_request_noblock() 会调用 process_request() 函数,process_request() 最终会将 run_simple() 中的第三个参数加括号执行,并传入参数 (environ, start_response)

所以当 http://localhost:4000/ 有请求过来时,就会触发并调用 application1(),即 application1 = Response('Hello World application1!').__call(self, environ, start_response)__

WSGI

在所有的 Python web 框架都要遵循 WSGI 协议,WSGI 中有一个非常重要的概念:

每个 Python web 应用都是一个可调用(callable)的对象(如上述的 Response),要运行 web 应用,必须有 web server,在 werkzeug 中提供了 WSGIServer,Server 和 Application 之间怎么通信,就是 WSGI 的功能。

WSGI 有两方,服务器方 和 应用程序

  1. 服务器方:调用应用程序,给应用程序传递(环境信息)和(回调函数),这个回调函数是用来将应用程序设置的 http header 和 status 等信息传递给服务器方。
  2. 应用程序:用来生成返回的 header、body 和 status,以便返回给服务器方。

werkzeug 的 response 流程

看完了请求,接下来看下返回。即 werkzeug.wrappers.py 模块下的 Response

1
2
3
4
################### response.py 文件下的 class Response ################
class Response(BaseResponse, ETagResponseMixin, ResponseStreamMixin, CommonResponseDescriptorsMixin,WWWAuthenticateMixin):
# 就这么多,没了,气不气
pass

该类是多重继承类,这里主要看下 BaseResponse,先看下初始方法

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
#################### base_responce.py 文件下的 class BaseResponse(object) #############
class BaseResponse(object):
def __init__(self, response=None, status=None, headers=None,
mimetype=None, content_type=None, direct_passthrough=False):
if isinstance(headers, Headers):
self.headers = headers
elif not headers:
self.headers = Headers()
else:
self.headers = Headers(headers)

if content_type is None:
if mimetype is None and 'content-type' not in self.headers:
mimetype = self.default_mimetype
if mimetype is not None:
mimetype = get_content_type(mimetype, self.charset)
content_type = mimetype
if content_type is not None:
self.headers['Content-Type'] = content_type
if status is None:
status = self.default_status
if isinstance(status, integer_types):
self.status_code = status
else:
self.status = status

self.direct_passthrough = direct_passthrough
self._on_close = []

# we set the response after the headers so that if a class changes
# the charset attribute, the data is set in the correct charset.
if response is None:
self.response = []
elif isinstance(response, (text_type, bytes, bytearray)):
self.set_data(response) #################看这里
else:
self.response = response

BaseResponse.__init__ 初始方法中,我们定义了返回的 Headers、content_type、status,最后通过 self.set_data(response),跟踪代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
####### base_responce.py 文件下的 class BaseResponse(object) 下的 def set_data ##########
## 主要讲传入的应答 编码
class BaseResponse(object):
def set_data(self, value):
if isinstance(value, text_type):
# 字符串编码
value = value.encode(self.charset)
else:
value = bytes(value)
self.response = [value] # 看这里,看这里
if self.automatically_set_content_length:
self.headers['Content-Length'] = str(len(value))

将我们示例中的 application1 = Response('Hello World application1!') 参数字符串,进行 bytes 类型转换并赋值给self.response,然后执行对象(),即调用 __call__ 方法,

1
2
3
4
5
6
7
8
9
10
####### base_responce.py 文件下的 class BaseResponse(object) 下的 def __call__ ##########
# 这个方法的作用就是,执行 具体的请求过程,然后调用回调函数,并提供返回值给调用者 HTTPServer.serve_forever(self)
class BaseResponse(object):
def __call__(self, environ, start_response):
print(start_response)
# get_wsgi_response ,是具体的请求处理过程,后面Flask源码解析会讲到
app_iter, status, headers = self.get_wsgi_response(environ)
# start_response ,提供的回调函数
start_response(status, headers)
return app_iter ### 把值返回个调用者

这里要先介绍一个 environ 参数,以上方式2中涉及到了environ,其实这个 environ 参数是包含了请求的所有信息,让我们在看下 __call__ 方法中, app_iter, status, headers = self.get_wsgi_response(environ) 输出通过请求系列参数,获取最后要返回的 get_wsgi_response,输出如下:

1
<werkzeug.wsgi.ClosingIterator object at 0x0589C0B0> --- 200 OK --- [('Content-Type'\\\省略]

然后在 start_response(status, headers) 代码中,start_response 是 application 处理完之后需要调用的函数,参数是状态码、响应头部还有错误信息,让我们来看下 start_response 输出,

1
<function WSGIRequestHandler.run_wsgi.<locals>.start_response at 0x05A32108>

跟踪代码如下 start_response

1
2
3
4
5
6
7
8
9
10
11
def start_response(status, response_headers, exc_info=None):
if exc_info:
try:
if headers_sent:
reraise(*exc_info)
finally:
exc_info = None
elif headers_set:
raise AssertionError('Headers already set')
headers_set[:] = [status, response_headers]
return write

start_response 返回 write 方法,然后跟踪该方法

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
def write(data):
assert headers_set, 'write() before start_response'
if not headers_sent:
status, response_headers = headers_sent[:] = headers_set
try:
code, msg = status.split(None, 1)
except ValueError:
code, msg = status, ""
code = int(code)
self.send_response(code, msg)
header_keys = set()
for key, value in response_headers:
self.send_header(key, value)
key = key.lower()
header_keys.add(key)
if not ('content-length' in header_keys or
environ['REQUEST_METHOD'] == 'HEAD' or
code < 200 or code in (204, 304)):
self.close_connection = True
self.send_header('Connection', 'close')
if 'server' not in header_keys:
self.send_header('Server', self.version_string())
if 'date' not in header_keys:
self.send_header('Date', self.date_time_string())
self.end_headers()

assert isinstance(data, bytes), 'applications must write bytes'
self.wfile.write(data)
self.wfile.flush()

最后就输出到浏览器,以上就是简单的请求、响应流程

Flask 集成 werkzeug

看完了 werkzeug 的源码,来看下 Flaks 是怎么集成 werkzeug 的

我们在 Flask 中经常会写成

1
2
3
4
5
6
7
8
9
10
from flask import Flask

app = Flask(__name__)

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

if __name__ == '__main__':
app.run() # run_simple(host,port,app)

看一下 run

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def run(self, host=None, port=None, debug=None, **options):
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) ## 是不是还是用的run_simple
finally:
self._got_first_request = False

最后依然是执行的 run_simple(host, port, self, **options),也就是 werkzeug.serving.py 下的 run_simple 方法,通过刚开始的分析,我们知道 run_simple 最终调用 Flask 实例的 __call__() 函数

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
####################### Flask  ########################
class Flask:

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)

def wsgi_app(self, environ: dict, start_response: t.Callable) -> t.Any:
# 重要 创建当前请求的上下文对象 flask.ctx.RequestContext 对象
# 此处已经对请求信息进行了处理(获得了 endpoint 和 view_func_args)
ctx = self.request_context(environ)
error = None
try:
try:
# 将当前请求的上下文入栈(LocalStack)
# 此处是Flask上下文的实现细节 通过共享的方式来传递上下文 给后面的 view_func
ctx.push()
# 分发执行
response = self.full_dispatch_request()
except Exception as e:
error = e
response = self.handle_exception(e)
except: # noqa: B001
error = sys.exc_info()[1]
raise
return response(environ, start_response)
finally:
if "werkzeug.debug.preserve_context" in environ:
environ["werkzeug.debug.preserve_context"](_cv_app.get())
environ["werkzeug.debug.preserve_context"](_cv_request.get())

if error is not None and self.should_ignore_error(error):
error = None
# 请求处理完成后 清理上下文 出栈
ctx.pop(error)

def full_dispatch_request(self) -> Response:
if not self._got_first_request:
with self._before_request_lock:
if not self._got_first_request:
for func in self.before_first_request_funcs:
self.ensure_sync(func)()

self._got_first_request = True

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)

def dispatch_request(self) -> ft.ResponseReturnValue:
# # 直接从上下文栈中获取当前的请求上下文
req = request_ctx.request
if req.routing_exception is not None:
self.raise_routing_exception(req)
rule = req.url_rule # type: ignore[assignment]
# 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()

view_args = req.view_args # type: ignore[assignment]

# 根据endpoint获取对应的view_func 执行并返回
return self.view_functions[rule.endpoint](**view_args)

Flask 路由相关

@app.route("/") 是一个接收参数的装饰器工厂函数,返回一个闭包了特定参数的装饰器

1
2
3
4
5
6
7
8
9
def route(self, rule: str, **options):
# 典型的注册装饰器
def decorator(f):
endpoint = options.pop("endpoint", None)
# 此方法将 url 信息和 view_func 注册到 Flask 实例中
self.add_url_rule(rule, endpoint, f, **options)
return f

return decorator

self.add_url_rule(rule, endpoint, f, **options) 方法,add_url_rule 主要作用就是将 rule 和 view_func 信息维护到 Flask 实例的 url_mapview_functions` 属性中

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
def add_url_rule(  
self,
rule,
endpoint=None,
view_func=None,
provide_automatic_options=None,
**options
):
# 此处 endpoint 是一个重要的概念
# 在 Flask 中是 url_map 和 view_functions 关联的重要纽带
# 每次请求来时,去 url_map 中搜索得到 endpoint,args 然后走 view_functions[endpoint](args) 拿到结果
# 若不传 默认为 view_func.__name__
if endpoint is None:
endpoint = _endpoint_from_view_func(view_func)
options["endpoint"] = endpoint
methods = options.pop("methods", None)
# ...

# 此处的 url_rule_class 是一个Flask类属性 值为 werkzeug.routing.Rule
# 所以此处即为构建 werkzeug.routing.Rule 对象
rule = self.url_rule_class(rule, methods=methods, **options)
rule.provide_automatic_options = provide_automatic_options

# 构建好的 rule对象保存到 url_map实例属性中
# url_map是werkzeug.routing.Map 对象
# flask路由部分其实借助了werkzeug的能力
self.url_map.add(rule)
if view_func is not None:
# ...
# endpoint为key将对应的view_func保存在 view_functions属性中
# view_functions 是个 dict
self.view_functions[endpoint] = view_func

总结

  1. 创建一个 Flask 实例 app
  2. urlview_func 通过 app.add_url_rule() 维护到 app 的 url_mapview_functions 属性中。url_map 包含了路由逻辑,view_functions 存储了对应的逻辑函数,二者通过 endpoint 相关联。
  3. 步骤 1 和 2 完成后,其实一个遵循 WSGI 协议的 web application 已经准备好了,接下来将其挂到一个同样支持 WSGI 协议的 web server 下面。web server 接受 HTTP 协议的请求,并将其转化为 WSGI 格式的内容,然后调用 app(environ, start_response) 执行具体逻辑处理,并返回 WSGI 格式的结果。之后再把 WSGI 格式的结果转换为 HTTP 格式返回给客户端就可以啦。
  4. werkzeug 中的 BaseWSGIServer 继承了内置库的 http.server.HTTPServer,从中获得了 HTTPServer 监听端口并获取请求的能力,并整合了 app 和 WSGIRequestHandler。每当一个请求就绪时,就交给一个 WSGIRequestHandler 实例处理。WSGIRequestHandler 做了 HTTP 格式数据到 WSGI 格式的转换,并执行 app(environ, start_response) ,返回响应。
  5. app(environ, start_response) 这步就又回到 Flask 的请求处理逻辑,根据 environ 的信息配合事先已经绑定好的 url_map,得到具体的路由信息和参数 (endpoint,view_func_args),然后从 view_functions 字典中取出对应的view_function 运行并返回结果。

Reference


Flask 源码解析:启动流程
https://flepeng.github.io/021-Python-32-框架-Flask-Flask-源码解析:启动流程/
作者
Lepeng
发布于
2024年9月25日
许可协议