Python Web 應用:WSGI基礎


在Django,Flask,Bottle和其他一切Python web 框架底層的是Web Server Gateway Interface,簡稱WSGI。WSGI對Python來說就像 Servlets對Java一樣——一種用於web服務器並允許不同web服務器和應用框架基於通用API交互的通用規范。然而,對於大多數事情,Python版本實現相當簡單。

WSGI被定義在PEP 3333協議里面,如果在讀完本文之后你想學到更多東西的話,作者建議讀者先閱讀一下簡介。

本文將從一個應用開發者的角度來向你介紹WSGI說明,並且向你展示怎樣直接通過WSGI來開發應用程序(如果你迫不及待的話)。

你的第一個WSGI應用

下面是最基本的Python web應用:

?
def  app(environ, start_fn):
     start_fn( '200 OK' , [( 'Content-Type' 'text/plain' )])    
     return  [ "Hello World!\n" ]

就是這樣!整個文件。先命名為app.py然后在任何WSGI可編譯服務器上面運行,然后你就可以得到一個Hello World並伴隨一個200的響應狀態碼。你可以使用gunicorn來完成,通過pip(pip install gunicorn)來安裝並執行gunicorn app:app。這條命令告訴gunicorn從應用模塊里的應用變量去獲取可調用的WSGI。

剛才,十分興奮吧。僅僅三行代碼就可以運行一個應用?那一定是某種意義上的記錄(不包括PHP,因為mod_php在起作用)。我敢打賭你現在一定想更加深入了解下去了。

所以一個WSGI應用最重要的部分是什么呢?

  • 一個WSGI應用是Python可調用的,就像一個函數,一個類,或者一個有__call__方法的類實例

  • 可調用的應用程序必須接受兩個參數:environ,一個包含必要數據的Python字典,start_fn,它自己是可調用的。

  • 應用程序必須能調用start_fn和兩個參數:狀態碼(字符串),和一個頭部以兩個元組表述的列表。

  • 應用程序返回一個在返回體里包含bytes的方便的可迭代對象,流式的部分——例如,一個個只包含“Hello,World!”字符串的列表。(如果app是一個類的話,可以在__iter__方法里完成)

例如下面兩個例子和第一個是等價的:

?
class  app( object ):
 
     def  __init__( self , environ, start_fn):
         self .environ  =  environ
         self .start_fn  =  start_fn    
     def  __iter__( self ):
         self .start_fn( '200 OK' , [( 'Content-Type' 'text/plain' )])        
         yield  "Hello World!\n"
?
class  Application( object ):
     def  __call__( self , environ, start_fn):
         start_fn( '200 OK' , [( 'Content-Type' 'text/plain' )])        
         yield  "Hello World!\n"
app  =  Application()

你可能已經開始思考能用這些東西來做什么了,但是最可能相關的一項是用來寫中間件。

讓它活躍起來

中間件是一種方便擴展WSGI應用功能性的方法。因為你只需提供一個可調用的對象,你可以任意把它包裹在其他函數里。

例如,假設我們想檢測一下environ里面的內容。我們可以輕易地創建一個中間件來完成,如下所示:

?
import  pprint
def  handler(environ, start_fn):
     start_fn( '200 OK' , [( 'Content-Type' 'text/plain' )])    
     return  [ "Hello World!\n" ]
def  log_environ(handler):
     def  _inner(environ, start_fn):
         pprint.pprint(environ)        
         return  handler(environ, start_fn)    
     return  _inner
 
app  =  log_environ(handler)

這里,log_environ是一個返回函數的函數,它在environ參數延遲原始回調之前裝飾該參數。

這樣寫中間件的優點是中間件和處理器不需要知道或者關心對方。你可以輕易地綁定log_environ到一個Flask應用上面,例如,因為Flask應用是WSGI應用。

其他一些有用的中間件設計:

?
import  pprint
def  handle_error(handler):
     def  _inner(environ, start_fn):
         try :            
             return  handler(environ, start_fn)
         except  Exception as e:            
             print  e   # Log error
             start_fn( '500 Server Error' , [( 'Content-Type' 'text/plain' )])            
             return  [ '500 Server Error' ]    
          return  _inner
def  wrap_query_params(handler):
     def  _inner(environ, start_fn):
         qs  =  environ.get( 'QUERY_STRING' )
         environ[ 'QUERY_PARAMS' =  urlparse.parse_qs(qs)        
         return  handler(environ, start_fn)    
      return  _inner

如果你不想讓你的文件有一個很大的金字塔底的話,你可以使用reduce一次應用到多個中間件上。

?
# Applied from bottom to top on the way in, then top to bottom on the way out
MIDDLEWARES  =  [wrap_query_params,
                log_environ,
                handle_error]
 
app  =  reduce ( lambda  h, m: m(h), MIDDLEWARES, handler)

利用start_fn參數的優點你也可以寫裝飾響應體的中間件。下面是一個內容類型頭部是text/plain反轉輸出結果的中間件。

?
def  reverser(handler):
 
     # A reverse function
     rev  =  lambda  it: it[:: - 1 ]    
     def  _inner(environ, start_fn):
         do_reverse  =  []   # Must be a reference type such as a list
 
         # Override start_fn to check the content type and set a flag
         def  start_reverser(status, headers):
             for  name, value  in  headers:                
                 if  (name.lower()  = =  'content-type'
                         and  value.lower()  = =  'text/plain' ):
                     do_reverse.append( True )                    
                     break
 
             # Remember to call `start_fn`
             start_fn(status, headers)
 
         response  =  handler(environ, start_reverser)        
         try :           
             if  do_reverse:                
                 return  list (rev( map (rev, response)))            
             return  response        
         finally :            
             if  hasattr (response,  'close' ):
                 response.close()    
      return  _inner

由於start_fn和響應體的分離有一點混亂,但是還是完美運行的。

同時請注意,為了嚴格遵循WSGI規范,如果存在的話,我們必須在響應體里檢查close方法並且調用它。WSGI應用程序有可能返回一個write函數而不是一個調用處理器的可迭代對象,如果你想要你的中間件支持更早的應用的話,你可能需要處理這個問題。

一旦你開始一點點把玩原生WSGI,你就開始理解為什么Python有一堆web框架。WSGI讓從底層造一些東西變得十分簡單。例如,你也許正在考慮下面的路由問題:

?
routes  =  {    
     '/' : home_handler,    
     '/about' : about_handler,
}
class  Application( object ):
     def  __init__( self , routes):
         self .routes  =  routes
             
     def  not_found( self , environ, start_fn):
         start_fn( '404 Not Found' , [( 'Content-Type' 'text/plain' )])        
         return  [ '404 Not Found' ]    
     def  __call__( self , environ, start_fn):
         handler  =  self .routes.get(environ.get( 'PATH_INFO' ))  or  self .not_found        
         return  handler(environ, start_fn)

如果你喜歡下面資源集合的靈活性的話,直接使用WSGI造輪子還是十分方便的。

  • 模板庫:投入任何你喜歡的模板(例如,Jinja2,Pystashe)然后從你的處理器返回渲染后的模板!

  • 用一個庫幫助你的路由,如Routes或者Werkzeug’s routing。實際上,如果你想輕松使用WSGI的話,看一看Werkzeug吧。

  • 使用任何Flask或者相似的數據庫遷移庫。悅德財富:https://yuedecaifu.com

當然,對於非專業的應用,你也可能想使用一下框架,那樣的話,一些特殊的諸如此類的例子也可以得到合理解決。

關於服務器呢?

有許許多多服務WSGI應用的方法。我們已經討論過Gunicorn了,一個相當好的選擇。uWSGI是另外一個不錯的選擇。但是請確保在服務這些靜態東西之前設置好nginx一類的東西,並且你應該有一個固定的開始節點。


免責聲明!

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



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