前言
Flask是目前為止我最喜歡的一個Python Web框架了,為了更好的掌握其內部實現機制,這兩天准備學習下Flask的源碼,將由淺入深跟大家分享下,其中Flask版本為1.1.1。
上次了解了Flask服務的啟動流程,今天我們來看下路由的內部實現機理。
Flask系列文章:
關於路由
所謂路由,就是處理請求URL和函數之間關系的程序。
Flask中也是對URL規則進行統一管理的,創建URL規則有兩種方式:
- 使用@app.route修飾器,並傳入URL規則作為參數,將函數綁定到URL,這個過程便將一個函數注冊為路由,這個函數則被稱為視圖函數。
- 使用app.add_url_rule()。
在開始閱讀源碼之前,我是有這幾點疑問的?
- 注冊路由的過程是什么?
- Flask內部是如何進行URL規則管理的?
- 一個視圖函數綁定多個URL內部是如何實現的?
- 動態URL是如何進行視圖函數匹配的呢?
- 匹配路由的過程是怎樣的呢?
那就讓我們帶着這幾點疑問一起去學習源碼吧!
正文
注冊路由
首先,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()有兩個參數,rule表示url規則。該函數對參數進行處理之后,調用方法add_url_role(),這里也就驗證了兩種注冊路由的方法等價。我們來看下代碼:
def add_url_rule(
self,
rule,
endpoint=None,
view_func=None,
provide_automatic_options=None,
**options
):
if endpoint is None:
endpoint = _endpoint_from_view_func(view_func)
options["endpoint"] = endpoint
methods = options.pop("methods", None)
# if the methods are not given and the view_func object knows its
# methods we can use that instead. If neither exists, we go with
# a tuple of only ``GET`` as default.
if methods is None:
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)
# Methods that should always be added
required_methods = set(getattr(view_func, "required_methods", ()))
# starting with Flask 0.8 the view_func object can disable and
# force-enable the automatic options handling.
if provide_automatic_options is None:
provide_automatic_options = getattr(
view_func, "provide_automatic_options", None
)
if provide_automatic_options is None:
if "OPTIONS" not in methods:
provide_automatic_options = True
required_methods.add("OPTIONS")
else:
provide_automatic_options = False
# Add the required methods now.
methods |= required_methods
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
入參包括:
- rule: url規則
- endpoint : 要注冊規則的endpoint,默認是視圖函數的名兒
- view_func: 視圖函數
- provide_automatic_options: 請求方法是否添加OPTIONS方法的一個標志
- options: 關於請求處理的一些方法等
可以看到,add_url_rule()首先進行參數處理,包括:
- endpoint默認為視圖函數的name
- url請求的方法默認為GET
- 若請求方法中沒有設置OPTIONS,添加該方法。
在處理完所有的參數后,將該URL規則寫入url_map(創建好Rule對象,並添加到Map對象中),將視圖函數寫入view_function字典中。
其中,url_map 是werkzeug.routing:Map
類的對象,rule是 werkzeug.routing:Rule
類的對象,也就是Flask的核心路由邏輯是在werkzeug中實現的。
werkzeug
werkzeug是使用Python編寫的一個WSGI工具集,werkzeug.routing模塊主要用於url解析。
Rule類
Rule類繼承自RuleFactory類,一個Rule實例代表一個URL模式,一個WSGI應用會處理很多個不同的URL模式,與此同時產生很多個Rule實例,這些實例將作為參數傳給Map類。
Map類
Map類構造的實例存儲所有的url規則,解析並匹配請求對應的視圖函數。
路由匹配
在應用初始化的過程中,會注冊所有的路由規則,可以調用(app.url_map)查看,當服務收到URL請求時,就需要進行路由匹配,以找到對應的視圖函數,對應的流程和原理是什么呢?
當用戶請求進入Flask應用時,調用Flask類的wsgi_app方法:
def wsgi_app(self, environ, start_response):
ctx = self.request_context(environ)
error = None
try:
try:
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 self.should_ignore_error(error):
error = None
ctx.auto_pop(error)
該函數的處理過程包括:
- 創建RequestContext對象,在對象初始化的過程中調用app.create_url_adapter()方法,將請求參數environ傳給Map對象創建MapAdapter對象,保存在url_adapter字段中
- 將RequestContext對象推入_request_ctx_stack棧中
- 通過RequestContext的match_request方法,調用MapAdapter對象的match方法找到匹配的Rule並解析出參數,保存在request的url_rule和view_args字段中
- 調用full_dispatch_request()
接下來我們看下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()
except Exception as e:
rv = self.handle_user_exception(e)
return self.finalize_request(rv)
可以看到,重點執行dispatch_request():
def dispatch_request(self):
req = _request_ctx_stack.top.request
if req.routing_exception is not None:
self.raise_routing_exception(req)
rule = req.url_rule
# 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()
# otherwise dispatch to the handler for that endpoint
return self.view_functions[rule.endpoint](**req.view_args)
處理的過程是:獲取請求對象的request,找到對應的endpoint,繼而從view_functions中找到對應的視圖函數,傳遞請求參數,視圖函數處理內部邏輯並返回,完成一次請求分發。
以上,就是Flask路由的內部實現原理。