Werkzeug 庫——routing 模組簡析

fanchunke1991發表於2017-04-25

Werkzeug是一個Python寫成的WSGI工具集。它遵循WSGI規範,對伺服器和Web應用之間的“中間層”進行了開發,衍生出一系列非常有用的Web服務底層模組。

Werkzeug庫的routing模組的主要功能在於URL解析。對於WSGI應用來講,不同的URL對應不同的檢視函式,routing模組則會對請求資訊的URL進行解析並匹配,觸發URL對應的檢視函式,以此生成一個響應資訊。routing模組的解析和匹配功能主要體現在三個類上:RuleMapMapAdapter

Rule

Rule類繼承自RuleFactory類。一個Rule的例項代表一個URL模式,一個WSGI應用可以處理很多不同的URL模式,這也就是說可以產生很多不同的Rule例項。這些Rule例項最終會作為引數傳遞給Map類,形成一個包含所有URL模式的物件,通過這個物件可以解析並匹配請求對應的檢視函式。

關於Rule類有一些常用的方法:

  • empty() ——在實際情況中,Rule例項會和一個Map例項進行繫結。通過empty()方法可以將Rule例項和Map例項解除繫結。

  • get_empty_kwargs() ——在empty()方法中呼叫,可以獲得之前Rule例項的引數,以便重新構造一個Rule例項。

  • get_rules(map) ——這個方法是對RuleFactory類中get_rules方法的重寫,返回Rule例項本身。

  • refresh() ——當修改Rule例項(URL規則)後可以呼叫該方法,以便更新Rule例項和Map例項的繫結關係。

  • bind(map, rebind=False) ——將Rule例項和一個Map例項進行繫結,這個方法會呼叫complie()方法,會給Rule例項生成一個正規表示式。

  • complie() ——根據Rule例項的URL模式,生成一個正規表示式,以便後續對請求的path進行匹配。

  • match(path) ——將Rule例項和給定的path進行匹配。在呼叫complie()方法生成的正規表示式將會對path進行匹配。如果匹配,將返回這個path中的引數,以便後續過程使用。如果不匹配,將會由其他的Rule例項和這個path進行匹配。

注意: 在對給定的URL進行匹配的過程中,會使用一些Converters。關於Converters的資訊後續加以介紹。

Map

通過Map類構造的例項可以儲存所有的URL規則,這些規則是Rule類的例項。Map例項可以 通過後續的呼叫和給定的URL進行匹配。

關於Map類有一些常用的方法:

  • add(rulefactory) ——這個方法在構造Map例項的時候就會呼叫,它會將所有傳入Map類中的Rule例項和該Map例項建立繫結關係。該方法還會呼叫Rule例項的bind方法。

  • bind方法 ——這個方法會生成一個MapAdapter例項,傳入MapAdapter的包括一些請求資訊,這樣可以呼叫MapAdapter例項的方法匹配給定URL。

  • bind_to_environ方法 ——通過解析請求中的environ資訊,然後呼叫上面的bind方法,最終會生成一個MapAdapter例項。

MapAdapter

MapAdapter類執行URL匹配的具體工作。關於MapAdapter類有一些常用的方法:

  • dispatch方法 ——該方法首先會呼叫MapAdapter例項的match()方法,如果有匹配的Rule,則會執行該Rule對應的檢視函式。

  • match方法 ——該方法將會進行具體的URL匹配工作。它會將請求中的url和MapAdapter例項中的所有Rule進行匹配,如果有匹配成功的,則返回該Rule對應的endpoint和一些引數rvendpoint一般會對應一個檢視函式,返回的rv可以作為引數傳入檢視函式中。

一個簡單的例子

為了說明routing模組的工作原理,這裡使用Werkzeug文件中的一個例子,稍加改動後如下所示:

from werkzeug.routing import Map, Rule, NotFound, RequestRedirect, HTTPException

url_map = Map([
    Rule('/', endpoint='blog/index'),
    Rule('/<int:year>/', endpoint='blog/archive'),
    Rule('/<int:year>/<int:month>/', endpoint='blog/archive'),
    Rule('/<int:year>/<int:month>/<int:day>/', endpoint='blog/archive'),
    Rule('/<int:year>/<int:month>/<int:day>/<slug>',
         endpoint='blog/show_post'),
    Rule('/about', endpoint='blog/about_me'),
    Rule('/feeds/', endpoint='blog/feeds'),
    Rule('/feeds/<feed_name>.rss', endpoint='blog/show_feed')
])

def application(environ, start_response):
    urls = url_map.bind_to_environ(environ)
    try:
        endpoint, args = urls.match()
    except HTTPException, e:
        return e(environ, start_response)
    start_response('200 OK', [('Content-Type', 'text/plain')])
    return  ['Rule points to %r with arguments %r' % (endpoint, args)]

if __name__ == '__main__':
    from werkzeug.serving import run_simple
    run_simple('localhost', 4000, application)複製程式碼

這裡我們使用werkzeug自帶的伺服器模組構造了一個Web伺服器,並且設計了一個簡單的WSGI應用——application。這個Web伺服器可以根據URL的不同返回不同的結果。關於伺服器的構造這裡不再贅述,以下部分簡單對URL Routing過程進行分析:

1. 設計URL模式

設計URL模式的過程就是構造Rule例項的過程。上面的例子中我們構造了8個Rule例項,分別對應8個不同的URL模式。每個Rule例項還對應一個endpoint,這個endpoint可以和檢視函式進行對應,以便訪問某個URL時,可以觸發與之對應的檢視函式。下面的例子展示了endpoint和檢視函式的對應關係。

from werkzeug.wrappers import Response
from werkzeug.routing import Map, Rule

def on_index(request):
    return Response('Hello from the index')

url_map = Map([Rule('/', endpoint='index')])
views = {'index': on_index}複製程式碼

2. 構造Map例項

構造Map例項時,會呼叫它的add(rulefactory)方法。這個方法會在Map例項和各個Rule例項之間建立繫結關係,並通過呼叫Rule例項的bind()方法為每個Rule例項生成一個正規表示式。

例如,對於'/about'這個URL,它對應的正規表示式為:

'^\\|\\/about$'

對於'/<int:year>/<int:month>/<int:day>/'這個URL,它對應的正規表示式為:

'^\\|\\/(?P<year>\\d+)\\/(?P<month>\\d+)\\/(?P<day>\\d+)(?<!/)(?P<__suffix__>/?)$'

3. 構造MapAdapter例項

在設計WSGI應用時,上述例子通過url_map.bind_to_environ(environ)構建了一個MapAdapter例項。這個例項將請求的相關資訊和已經建立好的Map例項放在一起,以便進行URL匹配。

進行URL匹配的過程是通過呼叫MapAdapter例項的match()方法進行的。實質上,這個方法會將請求中的path傳入到所有Rule例項的match(path)方法中,經過正規表示式的匹配來分析path是否和某個Rule例項匹配。如果匹配則返回對應的endpoint和其他的引數,這可以作為引數傳入檢視函式。

4. 訪問URL可得相關結果

之後,訪問URL可以得到相對應的結果。

例如,訪問http://localhost:4000/2017/,可以得到:

Rule points to 'blog/archive' with arguments {'year': 2017}

訪問http://localhost:4000/2017/3/20/,可以得到:

Rule points to 'blog/archive' with arguments {'month': 3, 'day': 20, 'year': 2017}

訪問http://localhost:4000/about,可以得到:

Rule points to 'blog/about_me' with arguments {}


本文章原載於我的技術部落格,歡迎大家訪問。水平有限,如有不當之處還請指正,謝謝~

相關文章