通常有3种定义路由函数的方法:
- 使用flask.Flask.route() 修饰器。
- 使用flask.Flask.add_url_rule()函数。
- 直接访问基于werkzeug路由系统的flask.Flask.url_map.
Part 1
让我们从最常用的@app.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
可以看到修饰器是对add_url_rule函数的包装,当我们写如下代码时:
@app.route('/index.html')
def index():
return "Hello World!"
实际上上面的代码转换成:
def index():
return "Hello World!"
index = app.route('/index.html')(index)
也就是,rule = '/index.html', options = { }, 执行decorator(index) 时会执行self.add_url_rule(rule, endpoint, f, **options):
@setupmethod
def add_url_rule(self, rule, endpoint=None, view_func=None, **options):
if endpoint is None:
endpoint = _endpoint_from_view_func(view_func)
options['endpoint'] = endpoint
methods = options.pop('methods', None)
if methods is None:
# View Function Options (视图函数选项)
methods = getattr(view_func, 'methods', None) or ('GET',)
if isinstance(methods, string_types):
raise TypeError('Allowed methods have to be iterables of strings, '
'for example: @app.route(..., methods=["POST"])')
methods = set(item.upper() for item in methods)
# View Function Options (视图函数选项)
required_methods = set(getattr(view_func, 'required_methods', ()))
# View Function Options (视图函数选项)
provide_automatic_options = getattr(view_func,
'provide_automatic_options', None)
# View Function Options (视图函数选项)
if provide_automatic_options is None:
if 'OPTIONS' not in methods:
provide_automatic_options = True
required_methods.add('OPTIONS')
else:
provide_automatic_options = False
# View Function Options (视图函数选项)
methods |= required_methods
# url_rule_class默认为Werkzeug的Rule类,
rule = self.url_rule_class(rule, methods=methods, **options)
rule.provide_automatic_options = provide_automatic_options
self.url_map.add(rule)
# view_func 不能重复定义
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参数为None,那么:
def _endpoint_from_view_func(view_func):
"""Internal helper that returns the default endpoint for a given
function. This always is the function name.
"""
assert view_func is not None, 'expected view func if endpoint ' \
'is not provided.'
return view_func.__name__
endpoint就设置为view_func.name视图函数的名字。然后将endpoint添加到options字典中,对于methods = options.pop('methods', None),当我们指定时,@app.route('/login', methods=['GET', 'POST']),methods = ['GET', 'POST'] 否则methods = None. 如果methods == None, 同时,view_func 没有methods属性,则methods默认设置为('GET', ). 当然,methods不能设置为字符串类型,‘POST’可以不区分大小写。
关于View Function Options的代码暂时忽略。
add_url_rule执行完毕后,我们获得了Flask.url_map, 以及填充了Flask.view_functions.
我们可以做实验看看url_map里面都有啥,详见示例代码。
Part 2
下面回过头,来看看当Flask运行时,一个Request来了,会发生什么,仍然从Flask.wsgi_app开始阅读。
已经知道,当一个Request到来时,会首先push RequestContext和AppContext,在RequestContext中的init函数中有:
...
self.url_adapter = app.create_url_adapter(self.request)
...
self.match_request()
def create_url_adapter(self, request):
if request is not None:
return self.url_map.bind_to_environ(request.environ,
server_name=self.config['SERVER_NAME'])
...
首先将Flask.url_map与当前到来的Request中environ进行绑定,获得一个url_adapter。
def match_request(self):
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
获得url_adaptor之后,调用match_request,url_adapter.match()会返回一个元组view_args就是url_rule中的参数,比如Rule(/<int:year>/, endpoint='blog/archive')这个Rule,而请求是/2016/,那么view_args={year: 2016}. url_rule和view_args被储存在Request中。在Request类中,我们可以直接Request.endpoint将返回url_rule.endpoint.
在url_rule和view_args被装载到Request中后,我们继续对wsgi_app中的response = self.full_dispatch_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()
...
在preprocess_request()中处理与蓝本和@before_request有关的东西.暂时忽略。
def dispatch_request(self):
# 从_request_ctx_stack获得当前request.
req = _request_ctx_stack.top.request
# 如果有异常,处理异常
if req.routing_exception is not None:
self.raise_routing_exception(req)
# 获得储存在request中的url_rule.
rule = req.url_rule
# 视图函数选项(暂时忽略)
if getattr(rule, 'provide_automatic_options', False) \
and req.method == 'OPTIONS':
return self.make_default_options_response()
# 如果没有设定视图函数选项,直接调用视图函数,在view_functions中查找
# 键值为rule.endpoint的函数,并传入req.view_args(字典)作为
# key-word参数。
return self.view_functions[rule.endpoint](**req.view_args)
dispatch_request()处理完毕,将返回值储存在rv变量中。通常,视图函数会return render_template(...). 返回值接下来经过一系列处理,发送到客户端。
Part 3
视图函数选项,蓝本?(to be contiued...)