詳解Paste deploy


  

  原創作品,轉載注明本文出處:http://www.cnblogs.com/Security-Darren/p/4087587.html

  

  談到WSGI,就免不了要了解paste,其中paste deploy是用來發現和配置WSGI應用的一套系統,對於WSGI應用的使用者而言,可以方便地從配置文件匯總加載WSGI應用(loadapp);對於WSGI應用的開發人員而言,只需要給自己的應用提供一套簡單的入口點即可。

  PasteDeploy的官方文檔在這里,其發布在Pypi上的發行包在這里下載。借助Python的pypi包管理機制,我們可以非常方便地安裝PasteDeploy:

# pip install PasteDeploy

  

  下面的詞語每組內的等價:

  1. 應用,app,application;

  2. 過濾器,filter;

  3. 管道,pipeline;

  4. 工廠函數,factory;

  5. 組合, composite;

  6. 類型, type;

  7. 段, section

  這些都是PasteDeploy的基本概念,非常容易識別。

 

  既然PasteDeploy是用來配置和管理WSGI 應用的,不妨首先看一下PasteDeploy配置文件的格式。

  一、PasteDeploy配置文件格式

  

  PasteDeploy配置文件由若干section組成,section的聲明格式如下:

  [type:name]

  其中,方括號括起的section聲明一個新section的開始,section的聲明由兩部分組成,section的類型(type)和section的名稱(name),如:[app:main]等。section的type可以有:app、composite、filter、pipeline、filter-app等。

  每個section中具體配置項的格式就是基本的ini格式: key = value ,所有從PasteDeploy配置文件中提取的參數鍵、值都以字符串的形式傳入底層實現。

  此外,PasteDeploy的配置文件中使用“#”標注注釋。

  在基本了解PasteDeploy配置文件的書寫格式后,我們不妨看一個實例,來具體了解不同type的section。

  

  二、PasteDeploy中section的不同type

  

  示例1:PasteDeploy配置文件

  [composite:main]
  use = egg:Paste#urlmap
  / = home
  /blog = blog
  /wiki = wiki
  /cms = config:cms.ini

  [app:home]
  use = egg:Paste#static
  document_root = %(here)s/htdocs

  [app:wiki] 
  use = call:mywiki.main:application 
  database = sqlite:/home/me/wiki.db

  [filter-app:blog] 
  use = egg:Authentication#auth 
  next = blogapp 
  roles = admin 
  htpasswd = /home/me/users.htpasswd 

  [app:blogapp]
  use = egg:BlogApp
  database = sqlite:/home/me/blog.db

  [app:main]
  use = egg:MyEgg
  filter-with = printdebug

  [filter:printdebug] 
  use = egg:Paste#printdebug 

  [pipeline:main]
  pipeline = filter1 filter2 filter3 app
  ...

  上面的示例文件列出了若干不同type的section示意,下面就一一探討PasteDeploy可定義的section type.

  

  2.1  Type = composite(組合應用)

 

  顧名思義,組合應用由若干WSGI應用組成,composite為這些應用提供更高一層的分配工作。

  下面具體分析示例1中的如下部分:

[composite:main]
use = egg:Paste#urlmap
/ = home
/blog = blog
/wiki = wiki
/cms = config:cms.ini

  該段配置文件定義了一個名為main、類型為composite的section,方括號的聲明以下是該section的具體配置,遵循 key = value 的統一格式。

  Composite類型的section將URL請求分配給其他的WSGI應用。 use = egg:Paste#urlmap 意味着使用 Paste 包中的 urlmap 應用。urlmap是Paste提供的一套通用的composite應用,作用就是根據用戶請求的URL前綴,將用戶請求映射到對應的WSGI應用上去。這里的WSGI應用有:"home", "blog", "wiki" 和 "config:cms.ini"。

  最后一項僅僅是參考了同一個目錄中的另一個文件"cms.ini"

  

  2.2  Type = app(WSGI應用)

  回到示例1中的下一部分:

[app:home]
use = egg:Paste#static
document_root = %(here)s/htdocs

[app:wiki] 
use = call:mywiki.main:application 
database = sqlite:/home/me/wiki.db

  app類型的section聲明一個具體的WSGI應用。調用哪個python module中的app代碼則由的use后的值指定。

  這里的 egg:Paste#static 是另一個簡單應用,作用僅僅是呈現靜態頁面。它接收了一個配置項: document_root ,后面的值可以從全局配置[DEFAULT](大小寫敏感)中提取,提取方法s是使用變量替換:比如

  %(var_name)s 的形式。

  這里 %(here)s 的意思是這個示例配置文件所在的目錄,因為相對路徑在不同服務器中的解釋方法不同,出於移植性的考慮,官方文檔上推薦當前這種寫法。

  示例中定義了多個app類型的section,因為PasteDeploy的配置文件中允許定義多個app類型的section,同時要求每個WSGI應用也都應該擁有自己的section。這樣,每一個WSGI應用在配置文件中都有一個

app類型的section與之對應,默認地,"main"應用對應於 app:main 或 app 。

  應用的具體實現要在section中配置,有兩種方法專門用於指出應用對應的代碼:使用URI(用use標識)或 直接指向實現代碼(用protocol標識)。

  

  2.2.1  使用另一個URI

  采用該方法的特點是指出應用的實現代碼的那一條 key = value 配置項采用"use"作為鍵,該方法也有許多變種,官方的示例中給出了一些介紹:

[app:myapp]
use = config:another_config_file.ini#app_name

# 或任意URI:
[app:myotherapp]
use = egg:MyApp

# 或指明某個模塊中的可調用:
[app:mythirdapp]
use = call:my.project:myapplication

# 甚至是其他的section:
[app:mylastapp]
use = myotherapp

  最后指向其他的section的那個例子,看起來似乎沒有什么意義,似乎只是兩個相同的WSGI應用。但是這樣的定義允許我們在 [app:mylastapp] 這個應用中定義一些局部的配置項,從而在重用代碼的同時覆寫它引用的應用配置。

 

  2.2.2  直接指向應用的實現代碼

  采用該方法的特點是指出實現代碼的那一條 key = value 配置項采用“協議”作為鍵,所謂“協議”即protocol,告訴PasteDeploy即將加載的對象類型,如:

[app:myapp]
paste.app_factory = myapp.modulename:app_factory

  該例的protocol paste.app_factory 是一個應用的工廠函數,指明import對象的類型;值 myapp.modulename:app_factory 指明具體加載的模塊和方法。

  關於PasteDeploy的協議,進而可以定義的工廠函數類型,我們在下文對所有section可選的type有一定的了解后再進行探討。

  

  2.3  Type = filter(過濾器)

  filter是作用於WSGI應用上的函數或方法,以app為唯一的參數,並返回一個“過濾”后的app。歸功於WSGI接口的規范,不同的filter可以依次“過濾”某一app,事實上多個filter處理一個app也就是下文中提到的管道(pipeline)。

  在PasteDeploy的配置文件中有多種方法來“過濾”應用,比如示例1中:

[app:main]
use = egg:MyEgg
filter-with = printdebug

[filter:printdebug] 
use = egg:Paste#printdebug

  在 [app:main] 的 filter-with 字段指明用來處理該應用的filter,就指定了名為"printdebug"的filter來處理應用"main"。在 [filter:printdebug] 中還可以定義新的 filter-with 字段,從而將處理關系延續下去。

  

  2.4  Type = filter-app

  同樣是處理應用,在PasteDeploy配置文件中可以有着不同的寫法,比如示例1中的下面部分,就是使用filter-app類型的section來聲明一個filter:

[filter-app:blog] 
use = egg:Authentication#auth 
next = blogapp 
roles = admin 
htpasswd = /home/me/users.htpasswd 

[app:blogapp]
use = egg:BlogApp
database = sqlite:/home/me/blog.db

  該部分采用了[filter-app:NAME]類型的section聲明了一個filter,指定使用的代碼,以及要處理的應用: next 字段的值。從而PasteDeploy會自動地將過濾器"blog"作用在應用"blogapp"上。

 

  2.5  Type = pipeline

  pipeline便於對一個應用添加多個過濾器,比如示例1中:

[pipeline:main]
pipeline = filter1 filter2 filter3 app

  就指定了在app上施加三個filter進行處理。

  總結起來,想要在某個應用前添加多個filter,共有 [filter-app:...]  [pipeline:...] 和 [app:...] filter-with = ... 等方法。

 

  三、局部配置與全局配置

 

  3.1  局部配置與全局配置的格式

  PasteDeploy配置文件的所有配置項均使用 key = value 格式,但是局部配置項和全局配置項定義的位置不同。如:

[app:blog]
use = egg:MyBlog
database = mysql://localhost/blogdb
blogname = This Is My Blog!

[app:otherblog]
use = blog
blogname = The other face of my blog

  每一個section內設置具體的鍵值關系,構成這些section自己的局部配置。

  為了便於不同的應用讀取某些固定的系統信息,PasteDeploy允許設置全局配置變量,所有的全局配置必須放在[DEFAULT]字段下設置,如:

[DEFAULT]
admin_email = webmaster@example.com

[app:main]
use = ...
set admin_email = bob@example.com

  注意[DEFAULT]段名是大小寫敏感的,因此必須嚴格大寫。

 

  3.2  局部配置和全局配置的覆寫

  3.1中的兩個例子,實際上展示了局部配置和全局配置的覆寫,這里詳細介紹,首先看局部配置的覆寫:

[app:blog]
use = egg:MyBlog
database = mysql://localhost/blogdb
blogname = This Is My Blog!

[app:otherblog]
use = blog
blogname = The other face of my blog

  從2.2.1中已經知道,一些section可以直接復用其他section的代碼,並定制配置信息,這里 [app:otherblog] 就采用了 [app:blog] 的代碼,同時將配置項 blogname 改為自己特定的。

  另一方面,應用在本地可以修改全局配置項的值:

[DEFAULT]
admin_email = webmaster@example.com 
[app:main] use = ... set admin_email = bob@example.com

  只需要在要覆寫的鍵前加 set 即可。

  

  至於為什么要探討局部配置與全局配置,是因為二者在傳遞給不同類型的factory function時對應的參數不同,這些將在下文詳細探討。

 

  四、實現factory函數

 

  本文的第二部分探討了PasteDeploy中的若干種“協議”,事實上對應了實現時的不同類型,包括 paste.app_factory  paste.composite_factory  paste.filter_factory  paste.server_factory 等。這些“協議”的 value 都必須是一個可調用(函數、方法、類、可調用對象等)。

  這些“協議”封裝WSGI應用,使其成為app、composite、filter等類型的組件,這些factory對應的格式有

 

  4.1  paste.app_factory

   這是最常見的factory,接收配置參數,用來返回一個WSGI應用,第三部分中介紹的全局配置以字典的形式傳入,局部配置則以關鍵字參數(keyword arguments)的形式傳入。

def app_factory(global_config, **local_conf):
    return wsgi_app

 

  4.2  paste.composite_factory

def composite_factory(loader, global_config, **local_conf):
    return wsgi_app

  是不是與 app_factory 非常相似,composite的factory函數就是在app factory的基礎上增加了 loader 參數,該參數有一些方法,比如 get_app(name_or_uri, global_conf=None) 返回指定名稱的WSGI應用, get_filter 、 get_server 也有着類似的作用。

  下面是官方給出的一個composite_factory的示例:

def pipeline_factory(loader, global_config, pipeline):
    # 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

  事實上,這也是一個pipeline factory,這個例子需要搭配PasteDeploy配置文件的具體配置工作,其中的filter、app等也都需要額外實現:

[composite:main]
use = <pipeline_factory_uri>
pipeline = egg:Paste#printdebug session myapp

[filter:session]
use = egg:Paste#session
store = memory

[app:myapp]
use = egg:MyApp

  這個factory將PasteDeploy配置文件中某個pipeline section中的filter與app分離,將位於管道末端的app(myapp)作為參數依次傳遞給filter(printdebug, session),由於filter一旦處理成功也返回一個app,否則攔截用戶請求,所以這個過程可以一直持續下去,直至所有的filter對app進行完處理。

 

  4.3  paste.filter_factory

  filter factory與app factory非常相似,只是返回filter而不是WSGI app。filter必須是可調用的,接收WSGI app為唯一的參數,返回處理過的該app。paste deploy官方也給出了一個這樣的例子:

def auth_filter_factory(global_conf, req_usernames):
    # space-separated list of usernames:
    req_usernames = req_usernames.split()
    def filter(app):
        return AuthFilter(app, req_usernames)
    return filter

class AuthFilter(object):
    def __init__(self, app, req_usernames):
        self.app = app
        self.req_usernames = req_usernames

    def __call__(self, environ, start_response):
        if environ.get('REMOTE_USER') in self.req_usernames:
            return self.app(environ, start_response)
        start_response(
            '403 Forbidden', [('Content-type', 'text/html')])
        return ['You are forbidden to view this resource']

  該filter factory產生一個簡單的認證過濾器,過濾器工廠函數接收配置參數,進行處理后和app一起傳給真正實現過濾器功能的類 AuthFilter 。

   auth_filter_factory 中的 filter 方法就是封裝了具體實現的過濾器,它只接收WSGI app作為自己的參數,具體的配置參數和認證邏輯則由 class AuthFilter 的實例完成。

   class AuthFilter 的實例是一個可調用對象,事實上其 __call__ 方法就是實現了一個符合WSGI 規范的app。WSGI app以CGI形式的環境變量environ和回調函數為參數。本例的邏輯很簡單:如果在server發來的環境信息中包含了外部認證時設置的"REMOTE_USER",則直接返回傳進來的app,否則拒絕用戶的認證請求。

  WSGI app通過 return 語句返回HTTP Response body,由參數中的回調函數返回HTTP 響應狀態碼和HTTP Response header,這都屬於WSGI的規范,這里不做深入探討。

 

  4.4  paste.filter_app_factory

  與filter_factory 非常相似,只是接收和返回的均為WSGI app,沒有filter。

  4.3示例中如果進行如下修改:

class AuthFilter(object):
    def __init__(self, app, global_conf, req_usernames):
        ....

  那么 class AuthFilter 就是一個filter_app_factory, 對比4.1的 paste.app_factory 和4.3的 paste.filter_app_factory 即可理解。

 

  4.5  paste.server_factory

  返回WSGI server。

  WSGI server接收唯一的參數——WSGI app,並為其服務。一個簡單的 paste.server_factory 例子如下:

def server_factory(global_conf, host, port):
    port = int(port)
    def serve(app):
        s = Server(app, host=host, port=port)
        s.serve_forever()
    return serve

  該工廠函數同樣封裝了配置參數等信息,返回一個只接收WSGI app做參數的可調用,至於  class Server 就留待具體實現。

 

  4.6  paste.server_runner

  與paste.server_factory 類似,只是WSGI app應該以第一參數傳入,同時返回的server要立即運行。

 

  總結

 

  本文是學習PasteDeploy時的筆記,資料文檔的示例非常豐富通俗,就是邏輯比較凌亂,讀起來總覺的別扭。經過整理,看起來覺得思路清晰多了。結合WSGI規范,對Python下網絡開發又有了進一步的理解。

 

2014.12.17 補充:

paste.deploy.wsgi

paste.deploy.loadwsgi.loadapp(uri, name=None, **kw) 
  
  
paste.deploy.loadwsgi.loadserver(uri, name=None, **kw) 
  
  
paste.deploy.loadwsgi.loadfilter(uri, name=None, **kw) 
  
  
paste.deploy.loadwsgi.appconfig(uri, name=None, relative_to=None, global_conf=None) 
  這個項目的文檔真是不怎樣,loadapp 到底怎么用,這里說是在paste.deploy的wsgi模塊下,在實際項目和文檔的其他位置中直接位於 paste.deploy 下的。這里直接扔了四個函數不給任何說明,服了服了。


免責聲明!

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



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