什么是Werkzeug


上一節介紹了什么是WSGI,這一節我們看看Werkzeug

按照官方的說法,Werkzeug(源自德語,工具的意思)是一個WSGI工具庫,它開始於一個適用於WSGI的多樣化的工具集,后來發展成了現在非常流行的WSGI工具庫。Werkzeug可以在程序中單獨使用,也作為許多Python Web框架的底層庫,例如現在非常流行的Flask Web框架。

Werkzeug的基本功能

正如官方的說法,Werkzeug提供了非常豐富的功能,但是其功能總的可分為兩個方面:開發測試方面的功能和其用於Web程序中的工具函數及工具類

開發測試方面

一、Werkzeug提供了一個簡易的開發用服務器
二、Werkzeug提供了一些測試工具,如Client類、EnvironBuilder類。
三、Werkzeug提供了Debug的工具,提供了可用於Debug的中間件。當程序出錯時,並不會返回500錯誤,而是顯示程序出錯的地方以及出錯的原因,這就為程序的開發提供了方便。

工具方面

Werkzeug主要提供了如下幾種工具

一、請求和相應對象。

提供了RequestResponseRequest可以包裝WSGI服務器傳入的environ參數,並對其進行進一步的解析,以使我們更容易的使用請求中的參數。Response可以根據傳入的參數,來發起一個特定的響應。你可以認為Response是你可以創建的另一個標准的WSGI應用,這個應用可以根據你傳入的參數,來幫你做發起響應這件事。

from werkzeug.wrappers import Request, Response

def application(environ, start_response):
    request = Request(environ)
    response = Response("Hello %s!" % request.args.get('name', 'World!'))
    return response(environ, start_response)

二、路由解析。

Werkzeug提供了強大的路由解析功能。比如Flask框架中經常用到的RuleMap類等。

如下面一個程序。

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

url_map = Map([
    Rule('/', endpoint='blog/index'),
    Rule('/<int:year>/<int:month>/<int:day>/', endpoint='blog/archive'),
    Rule('/about', endpoint='blog/about_me'),
    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)]

我們創建了一個Map類實例url_map來保存一系列的URL規則。並且給它傳遞了一個Rule對象的列表。其中,每個Rule對象都包含兩個參數:一個字符串和endpoint。字符串代表了URL匹配的規則(也叫路由規則),endpoint(也叫端點)代表了該路由規則對應的視圖函數。即當對一個URL匹配成功后,便可獲取到它對應的視圖函數。不同的規則可以對應相同的endpoint,但是必須有不同的參數用於URL的構建,不能產生歧義,類似於函數的重載。

application函數中,我們使用Mapbind_to_environ方法將url_mapenviron綁定,這會返回給我們一個新的MapAdapter對象,這個對象可用於URL的匹配。隨后,我們調用MapAdapter對象中的match()方法,獲取當前請求的URL匹配到的endpoint和其參數信息,最后,我們用獲取到的endpoint和參數信息發起一個響應。

用於匹配URL的路由規則字符串是由基本的URL加上占位符組成的。

例如Rule('/pages/<path:page>'),尖括號中,冒號后面為變量名,前面為變量的類型。path類型表示只匹配路徑。這里的path也可以是int,表示匹配一個整型以及float等。

當不包含尖括號中的變量不寫明類型時,如Rule('/pages/<page>'),這里的page可以匹配任何字符串,但是只能就受一個路徑段,因此不能含有/

更詳細的規則還請參見文檔

三、本地上下文

在許多Web程序中,本地上下文是個非常重要的概念。而實現本地上下文需要用到不同線程間數據的隔離。werkzeug.local中定義了LocalLocalStackLocalProxy等類用於實現全局數據的隔離。

在Python中,我們可以使用thread locals來保證多線程狀態下數據的隔離,但是這在Web程序中,卻並不是很好使。

  • 一是因為有些Web應用是使用協程實現的,無法保證數據的隔離。
  • 二是即使使用的是線程,WSGI也不能保證每次請求使用的線程都是一個全新的線程,可能是一個之前請求的線程,而里面的數據也是原線程剩下的。

所以,Werkzeug給我們提供了Local這個更好用的解決工具。

下面是一個如何使用werkzeug.local例子:

from werkzeug.local import Local, LocalManager

local = Local()
local_manager = LocalManager([local])

def application(environ, start_response):
    local.request = request = Request(environ)
    ...

application = local_manager.make_middleware(application)

可以看到,我們把一個Request對象賦值給了全局對象local.request,這樣,我們就可以在全局范圍內使用local.request,而且獲取到的僅僅是當前請求的數據。因為Local對象不會在請求結束后自動清除本地上下文,所以這里我們需要使用LocalManager來管理。我們需要將管理的Local對象以列表的方式傳給LocalManager,並在最后使用LocalManagermake_middleware方法為WSGI程序添加中間件,來使請求結束后自動清除本次請求的數據。

那么Local是如何實現的呢?其實很簡單,在Local中,重寫了__getattr____setattr__方法,使得在獲取數據和存儲數據之前,先獲取到線程id(或協程id),以線程id(或協程id)為鍵,數據為值,存儲在一個字典中。這樣我們在操作數據的時候,操作的只會是當前線程(或協程)的數據,從而實現了數據隔離。感興趣的同學可以查看一下文末Local的源碼。

LocalStackLocal進行了封裝,使其可以以棧的方式使用。如下:

>>> ls = LocalStack()
>>> ls.push(42)
>>> ls.top
42
>>> ls.push(23)
>>> ls.top
23
>>> ls.pop()
23
>>> ls.top
42

LocalProxy類用於實現werkzeug本地代理,將所有的操作轉發給代理對象。如果你熟悉C++的話,你會發現這和C++的引用很像,但比引用更強大。使用方法如下:

from werkzeug.local import Local
l = Local()

# 以下是兩個代理
request = l('request') # Local中實現了__call__方法,用於返回一個代理,具體可以查看文末Local的源碼
user = l('user')


from werkzeug.local import LocalStack
_response_local = LocalStack()

# 這也是個代理
response = _response_local() # 同理,LocalStack返回的也是代理

除了以上創建代理的方式外,還可以手動創建一個代理

from werkzeug.local import Local, LocalProxy
local = Local()
request = LocalProxy(local, 'request')

如果你想擁有一個根據指定函數來返回不同的對象代理,也是支持的。

session = LocalProxy(lambda: get_current_request().session)

但我們為什么要使用代理呢。這里簡單說一下,我們知道,一個變量被賦值后如果不重新賦值,它的值是不會改變的,那么這在程序的某些地方就會變得很不方便。但是如果使用代理的話,那么我們在使用這個變量的時候就能動態的獲取到它所代理的對象的最新的值。

四、其他

除了上面三個方面外,Werkzeug還提供了很多工具,例如WSGI中間件、HTTP異常類、數據結構等。這里就不在一一詳述,感興趣的同學可以參考文檔


Local對象部分源碼:

try:
    from greenlet import getcurrent as get_ident
except ImportError:
    try:
        from thread import get_ident
    except ImportError:
        from _thread import get_ident

class Local(object):
    __slots__ = ('__storage__', '__ident_func__')

    def __init__(self):
        object.__setattr__(self, '__storage__', {})
        object.__setattr__(self, '__ident_func__', get_ident)

    def __iter__(self):
        return iter(self.__storage__.items())

    def __call__(self, proxy):
        """Create a proxy for a name."""
        return LocalProxy(self, proxy)

    def __release_local__(self):
        self.__storage__.pop(self.__ident_func__(), None)  # 清除數據

    def __getattr__(self, name):
        try:
            return self.__storage__[self.__ident_func__()][name]
        except KeyError:
            raise AttributeError(name)

    def __setattr__(self, name, value):
        ident = self.__ident_func__()
        storage = self.__storage__
        try:
            storage[ident][name] = value
        except KeyError:
            storage[ident] = {name: value}

    def __delattr__(self, name):
        try:
            del self.__storage__[self.__ident_func__()][name]
        except KeyError:
            raise AttributeError(name)

參考:

本篇參考Werkzeug文檔寫成,如有錯誤或與文檔不符的地方,還請以文檔為准,也歡迎您反饋給我。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM