Python Web 應用:WSGI基礎

2016-09-07    分類:WEB開發、程式設計開發、首頁精華0人評論發表於2016-09-07

本文由碼農網 – 肖豪原創翻譯,轉載請看清文末的轉載要求,歡迎參與我們的付費投稿計劃

DjangoFlaskBottle和其他一切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或者相似的資料庫遷移庫。

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

關於伺服器呢?

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

譯文連結:http://www.codeceo.com/article/python-web-wsgi.html
英文原文:Python Web Applications: The basics of WSGI
翻譯作者:碼農網 – 肖豪
轉載必須在正文中標註並保留原文連結、譯文連結和譯者等資訊。]

相關文章