上一節介紹了什么是WSGI,這一節我們看看Werkzeug
按照官方的說法,Werkzeug(源自德語,工具的意思)是一個WSGI工具庫,它開始於一個適用於WSGI的多樣化的工具集,后來發展成了現在非常流行的WSGI工具庫。Werkzeug可以在程序中單獨使用,也作為許多Python Web框架的底層庫,例如現在非常流行的Flask Web框架。
Werkzeug的基本功能
正如官方的說法,Werkzeug提供了非常豐富的功能,但是其功能總的可分為兩個方面:開發測試方面的功能和其用於Web程序中的工具函數及工具類
開發測試方面
一、Werkzeug提供了一個簡易的開發用服務器
二、Werkzeug提供了一些測試工具,如Client
類、EnvironBuilder
類。
三、Werkzeug提供了Debug的工具,提供了可用於Debug的中間件。當程序出錯時,並不會返回500錯誤,而是顯示程序出錯的地方以及出錯的原因,這就為程序的開發提供了方便。
工具方面
Werkzeug主要提供了如下幾種工具
一、請求和相應對象。
提供了Request
和Response
。Request
可以包裝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框架中經常用到的Rule
、Map
類等。
如下面一個程序。
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
函數中,我們使用Map
的bind_to_environ
方法將url_map
與environ
綁定,這會返回給我們一個新的MapAdapter
對象,這個對象可用於URL的匹配。隨后,我們調用MapAdapter
對象中的match()
方法,獲取當前請求的URL匹配到的endpoint
和其參數信息,最后,我們用獲取到的endpoint
和參數信息發起一個響應。
用於匹配URL的路由規則字符串是由基本的URL加上占位符組成的。
例如Rule('/pages/<path:page>')
,尖括號中,冒號后面為變量名,前面為變量的類型。path
類型表示只匹配路徑。這里的path
也可以是int
,表示匹配一個整型以及float
等。
當不包含尖括號中的變量不寫明類型時,如Rule('/pages/<page>')
,這里的page
可以匹配任何字符串,但是只能就受一個路徑段,因此不能含有/
。
更詳細的規則還請參見文檔
三、本地上下文
在許多Web程序中,本地上下文是個非常重要的概念。而實現本地上下文需要用到不同線程間數據的隔離。werkzeug.local
中定義了Local
、LocalStack
和LocalProxy
等類用於實現全局數據的隔離。
在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
,並在最后使用LocalManager
的make_middleware
方法為WSGI程序添加中間件,來使請求結束后自動清除本次請求的數據。
那么Local
是如何實現的呢?其實很簡單,在Local
中,重寫了__getattr__
和__setattr__
方法,使得在獲取數據和存儲數據之前,先獲取到線程id(或協程id),以線程id(或協程id)為鍵,數據為值,存儲在一個字典中。這樣我們在操作數據的時候,操作的只會是當前線程(或協程)的數據,從而實現了數據隔離。感興趣的同學可以查看一下文末Local
的源碼。
LocalStack
對Local
進行了封裝,使其可以以棧的方式使用。如下:
>>> 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文檔寫成,如有錯誤或與文檔不符的地方,還請以文檔為准,也歡迎您反饋給我。