Flask 應用中的 URL 處理
在文章:一個Flask應用執行過程剖析中,在一個上下文環境中可以處理請求。如果不考慮在處理請求前後做的一些操作,Flask原始碼中真正處理請求的是dispatch_request()方法。其原始碼如下:
Python12345678910111213141516171819 | defdispatch_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 |
從上面的原始碼中可以看到,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
Python1234 | defadd_url_rule(self,rule,endpoint,**options):options['endpoint']=endpointoptions.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裝飾器。
Python123456 | defroute(self,rule,**options):defdecorator(f):self.add_url_rule(rule,f.__name__,**options)self.view_functions[f.__name__]=freturnfreturndecorator |
route裝飾器會裝飾一個檢視函式。經route裝飾的檢視函式首先會呼叫add_url_rule方法,將裝飾器中的URL規則新增進Map例項中,檢視函式的名字會作為endpoint進行傳遞。然後在該應用的view_functions中增加endpoint和檢視函式的對應關係。這種對應關係可以在請求成功時方便地呼叫對應的檢視函式。
3. 一個簡單的例子
我們用一個簡單的例子來說明以上過程的實現:
Python1234567891011 | >>>fromflask importFlask>>>app=Flask(__name__)>>>@app.route('/')defindex():return"Hello, World!">>>@app.route('/<username>')defuser(username):return"Hello, %s"%username>>>@app.route('/page/<int:id>')defpage(id):return"This is page %d"%id |
以上程式碼,我們建立了一個Flask應用app,並且通過route裝飾器的形式為app增加了3條URL規則。
首先: 我們看一下Flask應用的url_map長啥樣:
Python1234567 | >>>url_map=app.url_map>>>url_mapMap([<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:
Python1234567 | >>>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:
Python123456 | >>>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>這條規則為例:
Python1234567 | >>>rule=url_map._rules[2]>>>rule<Rule'/page/<id>'(HEAD,GET)->page>>>>rule._regexre.compile(ur'^\|\/page\/(?P<id>\d+)$',re.UNICODE)>>>rule._regex.patternu'^\\|\\/page\\/(?P<id>\\d+)$' |
可以看到,在將一條URL規則的例項Rule新增進Map例項的時候,會為這個Rule生成一個正則表示式的屬性_regex。這樣當這個Flask應用處理請求時,實際上會將請求中的url和Flask應用中每一條URL規則的正則表示式進行匹配。如果匹配成功,則會返回endpoint和一些引數,返回的endpoint可以用來在view_functions找到對應的檢視函式,返回的引數可以傳遞給檢視函式。具體的過程就是:
Python12345 | try:# match_request()可以進行URL匹配endpoint,values=self.match_request()returnself.view_functions[endpoint](**values)... |