本文由碼農網 – 蕭豪原創翻譯,轉載請看清文末的轉載要求,歡迎參與我們的付費投稿計畫!
在Django,Flask,Bottle和其他一切Python web 框架底層的是Web Server Gateway Interface,簡稱WSGI。 WSGI對Python來說就像 Servlets對Java一樣——一種用於web伺服器並允許不同web伺服器和應用框架基於通用API交互的通用規範。然而,對於大多數事情,Python版本實作相當簡單。
WSGI被定義在PEP 3333協議裡面,如果在讀完本文之後你想學到更多東西的話,作者建議讀者先閱讀一下簡介。
本文將從一個應用開發者的角度來向你介紹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或相似的資料庫遷移庫。
當然,對於非專業的應用,你也可能想使用一下框架,那樣的話,一些特殊的諸如此類的例子也可以得到合理解決。
有許許多多服務WSGI應用的方法。我們已經討論過Gunicorn了,一個相當好的選擇。 uWSGI是另一個不錯的選擇。但請確保在服務這些靜態東西之前設定好nginx一類的東西,並且你應該有一個固定的開始節點。
以上是Python Web 應用:WSGI基礎的詳細內容。更多資訊請關注PHP中文網其他相關文章!