Flask 應用中的 URL 處理

本文主要對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

  1. 對請求的URL進行匹配;函數

  2. 若是URL能夠匹配,則返回相對應視圖函數的結果;url

  3. 若是不能夠匹配,則進行錯誤處理。spa

對於錯誤的處理,本文暫不作介紹。本文主要對Flask應用的URL模式以及請求處理過程當中的URL匹配進行剖析。debug

Flask應用的url_map

Flask應用實例化的時候,會爲應用增添一個url_map屬性。這個屬性是一個Map類,這個類在werkzeug.routing模塊中定義,其主要的功能是爲了給應用增長一些URL規則,這些URL規則造成一個Map實例的過程當中會生成對應的正則表達式,能夠進行URL匹配。相關的概念和內容能夠參考:Werkzeug庫——routing模塊code

在Flask源碼中,它經過兩個方法能夠很方便地定製應用的URL。這兩個方法是:route裝飾器和add_url_rule方法。get

1. add_url_rule

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裝飾器。

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. 一個簡單的例子

咱們用一個簡單的例子來講明以上過程的實現:

>>> 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)
    ...複製代碼
相關文章
相關標籤/搜索