本文主要對Flask的URL處理功能進行分析。文章原載於個人技術博客,歡迎你們訪問。水平有限,若有不當之處還請指正,謝謝~正則表達式
在文章 一個Flask應用運行過程剖析中,在一個上下文環境中能夠處理請求。若是不考慮在處理請求先後作的一些操做,Flask源碼中真正處理請求的是dispatch_request()
方法。其源碼以下:flask
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()
方法作了以下的工做:app
對請求的URL進行匹配;函數
若是URL能夠匹配,則返回相對應視圖函數的結果;url
若是不能夠匹配,則進行錯誤處理。spa
對於錯誤的處理,本文暫不作介紹。本文主要對Flask應用的URL模式以及請求處理過程當中的URL匹配進行剖析。debug
url_map
Flask應用實例化的時候,會爲應用增添一個url_map
屬性。這個屬性是一個Map
類,這個類在werkzeug.routing
模塊中定義,其主要的功能是爲了給應用增長一些URL規則,這些URL規則造成一個Map
實例的過程當中會生成對應的正則表達式,能夠進行URL匹配。相關的概念和內容能夠參考:Werkzeug庫——routing模塊code
在Flask源碼中,它經過兩個方法能夠很方便地定製應用的URL。這兩個方法是:route
裝飾器和add_url_rule
方法。get
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
實例中。源碼
爲了更加方便、優雅地寫應用的URL,Flask實現了一個route
裝飾器。
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
和視圖函數的對應關係。這種對應關係能夠在請求成功時方便地調用對應的視圖函數。
咱們用一個簡單的例子來講明以上過程的實現:
>>> 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
長啥樣:
>>> 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
:
>>> 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
:
>>> 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>
這條規則爲例:
>>> 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
找到對應的視圖函數,返回的參數能夠傳遞給視圖函數。具體的過程就是:
try:
# match_request()能夠進行URL匹配
endpoint, values = self.match_request()
return self.view_functions[endpoint](**values)
...複製代碼