通常有3種定義路由函數(shù)的方法:
- 使用flask.Flask.route() 修飾器聘殖。
- 使用flask.Flask.add_url_rule()函數(shù)。
- 直接訪問基于werkzeug路由系統(tǒng)的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函數(shù)的包裝,當我們寫如下代碼時:
@app.route('/index.html')
def index():
return "Hello World!"
實際上上面的代碼轉(zhuǎn)換成:
def index():
return "Hello World!"
index = app.route('/index.html')(index)
也就是,rule = '/index.html', options = { }, 執(zhí)行decorator(index) 時會執(zhí)行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 (視圖函數(shù)選項)
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 (視圖函數(shù)選項)
required_methods = set(getattr(view_func, 'required_methods', ()))
# View Function Options (視圖函數(shù)選項)
provide_automatic_options = getattr(view_func,
'provide_automatic_options', None)
# View Function Options (視圖函數(shù)選項)
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 (視圖函數(shù)選項)
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參數(shù)為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視圖函數(shù)的名字。然后將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’可以不區(qū)分大小寫。
關(guān)于View Function Options的代碼暫時忽略拟糕。
add_url_rule執(zhí)行完畢后判呕,我們獲得了Flask.url_map, 以及填充了Flask.view_functions.
我們可以做實驗看看url_map里面都有啥倦踢,詳見示例代碼。
Part 2
下面回過頭侠草,來看看當Flask運行時辱挥,一個Request來了,會發(fā)生什么边涕,仍然從Flask.wsgi_app開始閱讀晤碘。
已經(jīng)知道,當一個Request到來時功蜓,會首先push RequestContext和AppContext园爷,在RequestContext中的init函數(shù)中有:
...
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之后童社,調(diào)用match_request,url_adapter.match()會返回一個元組view_args就是url_rule中的參數(shù)著隆,比如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中后弦赖,我們繼續(xù)對wsgi_app中的response = self.full_dispatch_request()這個過程與路由相關(guān)的內(nèi)容進行分析。
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有關(guān)的東西.暫時忽略浦辨。
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
# 視圖函數(shù)選項(暫時忽略)
if getattr(rule, 'provide_automatic_options', False) \
and req.method == 'OPTIONS':
return self.make_default_options_response()
# 如果沒有設定視圖函數(shù)選項,直接調(diào)用視圖函數(shù)流酬,在view_functions中查找
# 鍵值為rule.endpoint的函數(shù)币厕,并傳入req.view_args(字典)作為
# key-word參數(shù)。
return self.view_functions[rule.endpoint](**req.view_args)
dispatch_request()處理完畢康吵,將返回值儲存在rv變量中劈榨。通常,視圖函數(shù)會return render_template(...). 返回值接下來經(jīng)過一系列處理晦嵌,發(fā)送到客戶端。
Part 3
視圖函數(shù)選項拷姿,藍本惭载?(to be contiued...)