Flask 源码分析-基本工作流程
本文主要分析了 Flask 的基本工作流程,由于处理 URL 请求与路由规则强相关,因此先介绍了Flask是怎样建立路由规则,然后详细分析了 Flask 的基本工作流程。
建立路由规则
客户端把请求发给 Web 服务器,Web 服务器再把请求发送给程序实例,程序实例需要知道每个 URL 请求运行哪些代码,所以保存了一个URL到处理函数的映射关系,处理 URL 和函数之间关系的程序称为路由。
Flask 建立路由规则的方法一般通过 @route
装饰器对视图函数进行装饰,例如:
app = Flask(__name__)
@app.route('/')
def index():
return 'Hello world!'
该方法也可以写成
app = Flask(__name__)
def index():
return 'Hello world!'
app.add_url_rule('/', 'hello', hello)
建立路由规则时,Flask 究竟做了什么,我们来看看 route
函数:
def route(self, rule, **options):
def decorator(f):
endpoint = options.pop('endpoint', None)
self.add_url_rule(rule, endpoint, f, **options)
return f
return decorator
route
函数是一个装饰器,获取参数 options
中的 endpoint
后,调用 add_url_rule
添加路由规则,返回被装饰的函数,这也验证了上面两种方法等价的说法。add_url_rule
是怎样添加路由规则的?
def add_url_rule(self, rule, endpoint=None, view_func=None, **options):
...
rule = self.url_rule_class(rule, methods=methods, **options)
rule.provide_automatic_options = provide_automatic_options
self.url_map.add(rule)
if view_func is not None:
old_func = self.view_functions.get(endpoint)
if old_func is not None and old_func != view_func:
raise AssertionError('View function mapping is overwriting an '
'existing endpoint function: %s' % endpoint)
self.view_functions[endpoint] = view_func
这段代码省略了获取 endpoint
和 methods
的部分,它主要做了两件事,一是根据 URL、methods
和 endpoint
创建 Rule
对象,将其添加到变量 url_map
中,Rule
对象是 werkzeug.routing:Rule
类的对象,url_map
是 werkzeug.routeing:Map
类的对象,这两个类的具体作用会在后面介绍。add_url_rule
函数做的第二件事是将视图函数添加到字典 view_functions
中,对应的键为 endpoint
,这样路由规则就创建完成。
根据上面的处理过程,我们可以猜想一下,当客户端发来 URL 请求,Flask 是怎样处理的:先根据请求中的 url
,method
与 url_map
中的 rule
进行匹配,若能匹配上,就能获取 rule
对应的 endpoint
,然后查找字典 view_funciton
,得到前面添加的视图函数,视图函数对请求进行处理,返回结果。
工作流程
当服务器收到 http 请求,调用 Flask 应用时,实际上是调用的 Flask 的 __call__
方法,从 __call__
方法的源码可以看出,最终是调用了 wsgi_app
方法并传入 environ
和 start_response
,environ
为请求头的所有信息,start_response
则是 Flask应用 处理完之后需要调用的函数,参数是状态码、响应头部还有错误信息。。
def __call__(self, environ, start_response):
"""Shortcut for :attr:`wsgi_app`."""
return self.wsgi_app(environ, start_response)
wsgi_app
的定义如下,基本上包含了整个流程的功能
def wsgi_app(self, environ, start_response):
"""
:param environ: a WSGI environment
:param start_response: a callable accepting a status code,
a list of headers and an optional
exception context to start the response
"""
ctx = self.request_context(environ)
ctx.push()
error = None
try:
try:
response = self.full_dispatch_request()
except Exception as e:
error = e
response = self.handle_exception(e)
return response(environ, start_response)
finally:
if self.should_ignore_error(error):
error = None
ctx.auto_pop(error)
1. 创建request上下文
ctx = self.request_context(environ)
语句生成了一个 request
上下文,具体做了什么,我们来看看 RequestContext
的初始化函数,
class RequestContext(object):
"""The request context contains all request relevant information. It is
created at the beginning of the request and pushed to the
`_request_ctx_stack` and removed at the end of it. It will create the
URL adapter and request object for the WSGI environment provided.
"""
def __init__(self, app, environ, request=None):
self.app = app
if request is None:
request = app.request_class(environ)
self.request = request
self.url_adapter = app.create_url_adapter(self.request)
self.flashes = None
self.session = None
self.match_request()
...
def match_request(self):
"""Can be overridden by a subclass to hook into the matching
of the request.
"""
try:
url_rule, self.request.view_args = \
self.url_adapter.match(return_rule=True)
self.request.url_rule = url_rule
except HTTPException as e:
self.request.routing_exception = e
在初始化的时候,首先创建了 request
对象,会调用 app.create_url_adapter
方法,把 app
的 url_map
绑定到 request
的 WSGI environ
变量上,创建 MapAdapter
对象,最后会调用 match_request
方法,返回匹配到的 url_rule
以及 url 中的参数。 request
上下文的具体实现以及栈结构实现较复杂,在后续文章中会专门分析。
2. 分发请求
这里省略了 full_dispatch_reques
函数中请求预处理,错误处理的流程,重点分析 dispatch_reques
函数。request
上下文栈结构弹出当前的 request
对象,取出先前匹配到的 url_rule
,根据 rule.endpoint
取出字典 view_functions
中的视图函数,完成对本次 URL 请求的处理。
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`.
"""
req = _request_ctx_stack.top.request
if req.routing_exception is not None:
self.raise_routing_exception(req)
rule = req.url_rule
if getattr(rule, 'provide_automatic_options', False) \
and req.method == 'OPTIONS':
return self.make_default_options_response()
return self.view_functions[rule.endpoint](**req.view_args)
3. 生成响应
分发请求完成后,已经取得了返回值,再看下一步是如何做,回到 full_dispatch_request
,它调用了 finalize_request
。该函数通过 make_response
函数,将刚才取得的 rv
生成响应,重新赋值 response
,self.process_response(response)
语句主要处理 after_request
,比如关闭数据库连接。
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)
def finalize_request(self, rv, from_error_handler=False):
response = self.make_response(rv)
try:
response = self.process_response(response)
request_finished.send(self, response=response)
except Exception:
if not from_error_handler:
raise
self.logger.exception('Request finalizing failed with an '
'error while handling an error')
return response
最后,在wsgi_app
函数中,将从 full_dispatch_request
返回的 response
加上参数environ
, start_response
并返回给服务器。