本篇分爲如下幾個部分java
- paste 是什麼
- 怎樣使用paste
- paste of neutron
WSGI 是python 中application 和 web server互通的標準。 咱們知道了wsgi 中包括 app, middleware , server並且middleware能夠有不少個。wsgi結構的系統最大的好處就是middleware像積木同樣,能夠靈活的添加組成不一樣的功能。python
咱們上一篇文章中,把2個middleware和一個app組合到了一塊兒,但咱們採用的寫法是在代碼中這樣寫:c++
httpd = make_server('10.79.99.86', 8051, CheckError(AuthToken(check_number)))
把各個模塊之間的關係硬編碼到了代碼中,這很顯然不夠靈活,假設有一天想從新組織middleware的結構,咱們還須要找到相關的代碼去修改代碼。最好的辦法是經過配置文件來組織各個模塊之間的關係,經過修改配置文件來影響系統中各個模塊的組合與調用。 Paste 就是解決這個問題的。程序員
paste 是python的一個module,經過paste, 你能夠把wsgi的模塊寫入ini風格的配置文件,靈活部署。web
下面咱們用paste構建一個小系統,進一步瞭解設計模式
首先,咱們將構建三個wsgi組件, 一個application叫check_number, 該application的功能是check http req中的數字是奇數仍是偶數,若是是奇數返回odd不然返回even。 代碼以下:api
class CheckNumber(object): def __call__(self, env, start_response): number = int(env.get('PATH_INFO').split('/')[-1]) response_body = 'even' if number % 2 == 0 else 'odd' status = '200 OK' response_headers = [('Content-Type', 'text/plain'), ('Content-Length', str(len(response_body)))] start_response(status, response_headers) return [response_body] @classmethod def factory(cls, global_config, **local_config): return cls()
__call__是wsgi application的入口,邏輯很是簡單,這裏不作解釋。服務器
why use factory
看factory函數的邏輯,其實至關於一個構造函數。 下面兩行代碼其實是同樣的效果app
CheckNumber() CheckNumber.factory()
但爲何還要多加一個factory呢?兩個緣由curl
無論怎樣,咱們知道這是個構造函數便可。
接下來,咱們構造一個middleware,名字叫AuthToken。 該middleware將驗證http請求header中的token,若是知足要求則放行request繼續傳遞給application,不然返回錯誤,代碼以下:
class AuthToken(object): def __init__(self, app): self.app = app def __call__(self, env, start_response): if env.get('HTTP_TOKEN') == '222': return self.app(env, start_response) else: response_body = 'Auth failed' status = '403 forbidden' response_headers = [('Content-Type', 'text/plain'), ('Content-Length', str(len(response_body)))] start_response(status, response_headers) return [response_body] @classmethod def factory(cls, global_config, **local_config): def _factory(app): return cls(app) return _factory
Middleware的特性就是能夠調用application,同時自身能夠像application同樣被調用。因此__init__中接受一個app做爲參數,而且在__call__中調用這個參數。所以
AuthToken(CheckNumber)
的效果是返回一個可調用的object。 在調用該object的__call__方法時,首先會檢查token,若是token符合要求則繼續調用CheckNumber。一樣要注意的是factory跟以前同樣,徹底能夠用構造函數替代。
咱們繼續構造第三個middleware,叫CheckError。 該middleware 用於檢查系統中其它application , middleware返回的錯誤異常等信息,並轉化成用戶友好的信息。 邏輯以下:
class CheckError(object): def __init__(self, app): self.app = app def __call__(self, env, start_response): try: return self.app(env, start_response) except Exception as e: response_body = 'Server is maintaining ' status = '503 service unavailable now' response_headers = [('Content-Type', 'text/plain'), ('Content-Length', str(len(response_body)))] start_response(status, response_headers) return [response_body] @classmethod def factory(cls, global_config, **local_config): def _factory(app): return cls(app) return _factory
CheckError 的構造函數接受一個app做爲參數並返回一個callable對象。調用該對象,即運行__call__方法時會用try catch去包裹一個對app的調用。若是調用有異常則進入異常處理返回,不然返回app調用
若是沒有paste deploy,咱們須要像下面這樣寫:
CatchError(AuthToken(CheckNumber(env, callback)))
經過程序來組織各個wsgi組件。這樣很是不方便,一旦想添加或者刪除甚至更改各個component的關係,須要修改代碼。 經過paste deploy,咱們能夠利用配置文件,以下:
[DEFAULT] security = high [composite: my_pipeline] use = call:my_module:pipeline_factory auth = check_error auth_token check_number noauth = check_error check_number [app:check_number] paste.app_factory = my_module:CheckNumber.factory [filter:auth_token] paste.filter_factory = my_module:AuthToken.factory [filter:check_error] paste.filter_factory = my_module:CheckError.factory
paste 配置文件採用INI風格。每一箇中括號及其下面跟着的一個區域叫作 section 如:
[filter:check_error] paste.filter_factory = my_module:CheckError.factory
這裏filter爲section type。paste中有不少種類型。app類型表明wsgi application, filter 表明wsgi middleware,server表明server。其它類型咱們後面遇到再說。 check_error 是section的name。下面跟着的是section的變量,paste.filter_factory = my_module:CheckError.factory 告訴paste,能夠用my_module模塊的CheckError.factory load catch_error filter。 簡單的說,這段配置告訴paste 咱們有一個catch_error middleware 以及怎樣加載這個middleware。
paste中,每一個section(除了[DEFAULT])表明一個wsgi組件,paste 經過配置文件能夠加載該組件,在加載組件的同時能夠讀取該section下面的配置信息。這裏的信息是該section對應的wsgi組件獨享的。可是有個section除外,即[DEFAULT],DEFAULT section不是wsgi組件,只是用來存配置。這裏的配置是全局的,每一個section均可以訪問。
auth_token, check_number其實和這裏同樣,都至關於聲明一個wsgi組件而且告訴paste如何加載。不一樣的是check_number是一個app。
咱們重點看一下my_pipeline區域。這裏咱們用的是composite類型。 回顧wsgi 會發現咱們有middle/app/server 但就是沒有composite。composite不是wsgi原生的類型,它表明多個組件的組合。好比這裏是兩個pipeline auth和noauth。paste中pipeline也是一種類型,表明串聯多個wsgi組件,好比:auth 至關於 CatchError(AuthToken(CheckNumber)) 而 noauth表明CatchError(CheckNumber)。
use = call:my_module:pipeline_factory
這一行表示對應的代碼在my_module 模塊的pipline_factory裏。 表示從這行代碼加載composite組件。看一下代碼的詳細信息:
def pipeline_factory(loader, global_config, **local_config): if global_config.get('security') == 'high': pipeline = local_config.get('auth') else: pipeline = local_config.get('noauth') # space-separated list of filter and app names: pipeline = pipeline.split() filters = [loader.get_filter(n) for n in pipeline[:-1]] app = loader.get_app(pipeline[-1]) filters.reverse() # apply in reverse order! for filter in filters: app = filter(app) return app
由於配置文件中指定該區域是composite,因此該函數接受3個參數loader, global_config 和 local_config。這是paste語法規則指定的。
loader用於加載app,而global_config是配置文件中全局區的配置{security:high}。local_config是 {auth:"catch_error auth_token check_number"}和{noauth:"catch_error auth_token check_number"}。
if global_config.get('security') == 'high': pipeline = local_config.get('auth') else: pipeline = local_config.get('noauth')
這段的邏輯是,若是全局配置中指定了security爲high, 則pipeline中包含auth不然不包含。
pipeline = pipeline.split() filters = [loader.get_filter(n) for n in pipeline[:-1]]
這段的邏輯是從pipeline中找到filter的列表(pipeline最後一項是app,其他都是filter)。
app = loader.get_app(pipeline[-1]) filters.reverse() # apply in reverse order! for filter in filters: app = filter(app)
這段的邏輯是,獲取app,而且把filter列表中的中間件反過來逐層包裹,達到
CatchError(AuthToken(CheckNumber))
的效果。
接下來咱們看一下如何使用。 咱們把3個wsgi組件和這個pipeline_factory 放入config.ini。 而後運行以下代碼:
if __name__ == '__main__': from paste import httpserver from paste.deploy import loadapp httpserver.serve(loadapp('config:conf.ini', name='my_pipeline', relative_to='.'), host='127.0.0.1', port='8080')
如今經過下面的命令
curl http://127.0.0.1/check_number/100
訪問會返回noauth錯誤,由於你沒有加上 -H 「token:222」 的驗證信息。 但設置security=False重啓服務器,繼續訪問,則不用加驗證信息,由於根據composite section對應的代碼邏輯,AuthToken組件並無加到pipeline
neutron的paste配置文件在
cat /usr/share/neutron/api-paste.ini [composite:neutron] use = egg:Paste#urlmap /: neutronversions /v2.0: neutronapi_v2_0 [composite:neutronapi_v2_0] use = call:neutron.auth:pipeline_factory noauth = request_id catch_errors extensions neutronapiapp_v2_0 keystone = request_id catch_errors authtoken keystonecontext extensions neutronapiapp_v2_0 [filter:request_id] paste.filter_factory = oslo_middleware:RequestId.factory [filter:catch_errors] paste.filter_factory = oslo_middleware:CatchErrors.factory [filter:keystonecontext] paste.filter_factory = neutron.auth:NeutronKeystoneContext.factory [filter:authtoken] paste.filter_factory = keystonemiddleware.auth_token:filter_factory [filter:extensions] paste.filter_factory = neutron.api.extensions:plugin_aware_extension_middleware_factory [app:neutronversions] paste.app_factory = neutron.api.versions:Versions.factory [app:neutronapiapp_v2_0] paste.app_factory = neutron.api.v2.router:APIRouter.factory
整個neutron api server就是一個wsgi server 加一堆 wsgi middleware 和 wsgi app組成的一個大的pipeline。 啓動加載該配置文件是neutron 的wsgi server負責的事情。這部分咱們後面再看。咱們先看該配置文件。一樣,從下向上看發現有兩個APP neutronversions和neutronapiapp_v2_0 以及5個middleware。 在composite區域中又組合了兩個pipeline。一個不帶驗證,一個帶有keystone驗證。最後composite部分經過paste包中的urlmap函數指定,若是URL是/則訪問neutronversion中間件,若是是/v2.0則訪問composite部分的pipeline,而這也正是咱們真正API訪問所走的路徑。
大部分環境中所採用的pipeline是keystone這條。由於咱們經過devstack或者官方文檔安裝的環境通常都會帶有權限校驗。 對於咱們來講,真正須要關注的代碼在extensions和neutron_app_v2_0這兩部分,其餘部分都和業務無關。 由配置文件能夠很容易找到各個組件的代碼位置,如neutron.api.v2.router表明 neutron/api/v2/router。從這裏開始,就能夠逐步閱讀代碼追蹤每一個API的調用流程。