在文章:一個Flask應用執行過程剖析中,在一個上下文環境中可以處理請求。如果不考慮在處理請求前後做的一些操作,Flask原始碼中真正處理請求的是dispatch_request()方法。其原始碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
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`. """ try: endpoint, values = self.match_request() return self.view_functions[endpoint](**values) except HTTPException, e: handler = self.error_handlers.get(e.code) if handler is None: return e return handler(e) except Exception, e: handler = self.error_handlers.get(500) if self.debug or handler is None: raise return handler(e) |
從上面的原始碼中可以看到,dispatch_request()方法做了如下的工作:
- 對請求的URL進行匹配;
- 如果URL可以匹配,則返回相對應檢視函式的結果;
- 如果不可以匹配,則進行錯誤處理。
對於錯誤的處理,本文暫不做介紹。本文主要對Flask應用的URL模式以及請求處理過程中的URL匹配進行剖析。
Flask應用的url_map
Flask應用例項化的時候,會為應用增添一個url_map屬性。這個屬性是一個Map類,這個類在werkzeug.routing模組中定義,其主要的功能是為了給應用增加一些URL規則,這些URL規則形成一個Map例項的過程中會生成對應的正規表示式,可以進行URL匹配。相關的概念和內容可以參考:Werkzeug庫——routing模組。
在Flask原始碼中,它通過兩個方法可以很方便地定製應用的URL。這兩個方法是:route裝飾器和add_url_rule方法。
1. add_url_rule
1 2 3 4 |
def add_url_rule(self, rule, endpoint, **options): options['endpoint'] = endpoint options.setdefault('methods', ('GET',)) self.url_map.add(Rule(rule, **options)) |
add_url_rule方法很簡單,只要向其傳遞一條URL規則rule和一個endpoint即可。endpoint一般為和這條URL相關的檢視函式的名字,這樣處理就可以將URL和檢視函式關聯起來。除此之外,還可以傳遞一些關鍵字引數。呼叫該方法後,會呼叫Map例項的add方法,它會將URL規則新增進Map例項中。
2. route裝飾器
為了更加方便、優雅地寫應用的URL,Flask實現了一個route裝飾器。
1 2 3 4 5 6 |
def route(self, rule, **options): def decorator(f): self.add_url_rule(rule, f.__name__, **options) self.view_functions[f.__name__] = f return f return decorator |
route裝飾器會裝飾一個檢視函式。經route裝飾的檢視函式首先會呼叫add_url_rule方法,將裝飾器中的URL規則新增進Map例項中,檢視函式的名字會作為endpoint進行傳遞。然後在該應用的view_functions中增加endpoint和檢視函式的對應關係。這種對應關係可以在請求成功時方便地呼叫對應的檢視函式。
3. 一個簡單的例子
我們用一個簡單的例子來說明以上過程的實現:
1 2 3 4 5 6 7 8 9 10 11 |
>>> from flask import Flask >>> app = Flask(__name__) >>> @app.route('/') def index(): return "Hello, World!" >>> @app.route('/<username>') def user(username): return "Hello, %s" % username >>> @app.route('/page/<int:id>') def page(id): return "This is page %d" % id |
以上程式碼,我們建立了一個Flask應用app,並且通過route裝飾器的形式為app增加了3條URL規則。
首先: 我們看一下Flask應用的url_map長啥樣:
1 2 3 4 5 6 7 |
>>> url_map = app.url_map >>> url_map Map([<Rule '/' (HEAD, GET) -> index>, <Rule '/static/<filename>' -> static>, <Rule '/page/<id>' (HEAD, GET) -> page>, <Rule '/<username>' (HEAD, GET) -> user> ]) |
可以看到,url_map是一個Map例項,這個例項中包含4個Rule例項,分別對應4條URL規則,其中/static/<filename>在Flask應用例項化時會自動新增,其餘3條是使用者建立的。整個Map類便構成了Flask應用app的URL“地圖”,可以用作URL匹配的依據。
接下來: 我們看一下url_map中的一個屬性:_rules_by_endpoint:
1 2 3 4 5 6 7 |
>>> rules_by_endpoint = url_map._rules_by_endpoint >>> rules_by_endpoint {'index': [<Rule '/' (HEAD, GET) -> index>], 'page': [<Rule '/page/<id>' (HEAD, GET) -> page>], 'static': [<Rule '/static/<filename>' -> static>], 'user': [<Rule '/<username>' (HEAD, GET) -> user>] } |
可以看出,_rules_by_endpoint屬性是一個字典,反映了endpoint和URL規則的對應關係。由於用route裝飾器建立URL規則時,會將檢視函式的名字作為endpoint進行傳遞,所以以上字典的內容也反映了檢視函式和URL規則的對應關係。
再接下來: 我們看一下Flask應用的view_functions:
1 2 3 4 5 6 |
>>> view_functions = app.view_functions >>> view_functions {'index': <function __main__.index>, 'page': <function __main__.page>, 'user': <function __main__.user> } |
在用route裝飾器建立URL規則時,它還會做一件事情:self.view_functions[f.__name__] = f。這樣做是將函式名和檢視函式的對應關係放在Flask應用的view_functions。由於Map例項中儲存了函式名和URL規則的對應關係,這樣只要在匹配URL規則時,如果匹配成功,只要返回一個函式名,那麼便可以在view_functions中執行對應的檢視函式。
最後: 我們看一下URL如何和Map例項中的URL規則進行匹配。我們以/page/<int:id>這條規則為例:
1 2 3 4 5 6 7 |
>>> rule = url_map._rules[2] >>> rule <Rule '/page/<id>' (HEAD, GET) -> page> >>> rule._regex re.compile(ur'^\|\/page\/(?P<id>\d+)$', re.UNICODE) >>> rule._regex.pattern u'^\\|\\/page\\/(?P<id>\\d+)$' |
可以看到,在將一條URL規則的例項Rule新增進Map例項的時候,會為這個Rule生成一個正規表示式的屬性_regex。這樣當這個Flask應用處理請求時,實際上會將請求中的url和Flask應用中每一條URL規則的正規表示式進行匹配。如果匹配成功,則會返回endpoint和一些引數,返回的endpoint可以用來在view_functions找到對應的檢視函式,返回的引數可以傳遞給檢視函式。具體的過程就是:
1 2 3 4 5 |
try: # match_request()可以進行URL匹配 endpoint, values = self.match_request() return self.view_functions[endpoint](**values) ... |